This object is in archive! 

As promised: my weather information-gathering system

David Pritchard shared this idea 6 years ago
Under Consideration
Alberto Macias asked me to post my system for getting external weather information, which is a Google App Script that calls many different weather APIs and then sends the info to an array of virtual meters. Finally, a series of rules creates a set of "consensus" weather information in another virtual meter, incorporating the Zipabox's own weather information. This is the complete Google App Script, minus API keys and so forth. I've marked each missing bit of information with a label showing what's missing. I've also attached the .txt file below for convenience. I also attach the rules that put together the "consensus weather" meter info. Don't pay much attention to the rule numbers - they keep changing as I add and delete rules, so they're not consistent. I took the screenshots at different times. The sunset and sunrise times are time values in seconds. I posted elsewhere about my date-time meter. Basically it has a date value measured in hours (inc. fractions of hours) since 1/1/1970 and a time value in seconds. I convert the sunrise and sunset times to compatible values so I can compare them. The north side temperature (where there's little sun) is taken from a sensor I have, unless the value is outdated, in which case I take the general temperature value and adjust it downwards a bit. For some weather values, I just take the source I consider to be most reliable for which I have a recent update. Wunderground seems to be the most accurate for most of the values, but as with all the weather sources, you need to be careful about null values. Sometimes min and max temperatures are the same, or there's some other obvious error. I try to filter these out in my rules. For other values, I calculate a value incorporating all sources, but weighting the ones I like most. Each rule has a virtual switch to trigger it. Because of my long names, the names are not always visible. "Zipabox Rule 050 - Subroutine - Set vars - Calculate consensus min-max temperatures" calls "Zipabox Rule 051 - Subroutine - Set vars - Calculate min-max temperature estimate" at the bottom. "Zipabox Rule 052 - Subroutine - Set vars - Calculate consensus cloud cover" calls "Zipabox Rule 053 - Subroutine - Set vars - Calculate consensus cloud cover 2" at the bottom. I also attach screenshots of my virtual meters. [co][li][/li][li]var bIsCloseToSunriseOrSunset = false;[/li][li]var nRecentFailuresZP = 0;[/li][li]var nSkippedCallsZP = 0;[/li][li] [/li][li]function doGet(e) [/li][li]{[/li][li] Logger.log("doGet GetWeatherData");[/li][li] return doPost(e);[/li][li]}[/li][li] [/li][li]function doPost(e) [/li][li]{[/li][li] Logger.log("doPost GetWeatherData");[/li][li] return GetWeatherData();[/li][li]};[/li][li] [/li][li]// Send response with code/message[/li][li]function sendResponse(code, message) [/li][li]{[/li][li] return {[/li][li] status: [/li][li] {[/li][li] code: code,[/li][li] message: message[/li][li] },[/li][li] payload: [/li][li] {[/li][li] }[/li][li] };[/li][li]}[/li][li] [/li][li]function GetWeatherData() [/li][li]{ [/li][li] //Logger.log("GetWeatherData");[/li][li] [/li][li]// var lock = LockService.getPublicLock();[/li][li]// lock.waitLock(30000); // wait 30 seconds before conceding defeat.[/li][li] [/li][li] const sZIPABOX_REMOTING_PREFIX = "https://my.zipato.com/zipato-web/remoting/attribute/set?serial=XXXXXZIPABOXSERIALXXXXXXX&apiKey=";[/li][li] const sZIP_LASTSENSORUPDATE_SUFFIX = "XXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXX";[/li][li] const sZIP_DATETIMEMETER_SUFFIX = "XXXXXXXXXXXXXXXep=XXXXXXXXXXXXX"; [/li][li] [/li][li] var bOWMDataSentOK = false;[/li][li] var bWundergroundDataSentOK = false;[/li][li] var bDarkSkyDataSentOK = false;[/li][li] var bAirVisualDataSentOK = false;[/li][li] var bWeather2DataSentOK = false;[/li][li] var bAerisDataSentOK = false;[/li][li] var bZipaboxDataSentOK = false;[/li][li] [/li][li] // Prepare standard HTTP POST stuff[/li][li] var options =[/li][li] {[/li][li] "contentType" : "text/plain",[/li][li] "method" : "post"[/li][li] };[/li][li] [/li][li] // Basic time info[/li][li] var nMinuteInMilliseconds = 60*1000; [/li][li] var nHourInMilliseconds = 60*60*1000; [/li][li] var nDayInMilliseconds = 60*60*24*1000; [/li][li] [/li][li] // Get current date/time[/li][li] var dateNow = new Date();[/li][li] //var nYear = dateNow.getFullYear();[/li][li] //var nMonth = dateNow.getMonth() + 1;[/li][li] //var nDay = dateNow.getDate();[/li][li] var nHour = dateNow.getHours();[/li][li] var nMinute = dateNow.getMinutes();[/li][li] [/li][li] // Create date-time value for the Zipabox (number of hours since 1 January 1970)[/li][li] var nZipaboxDateTime = dateNow.valueOf() / 1000 / 60 / 60;[/li][li] [/li][li] var bOddMinutes = true;[/li][li] var bCallZipabox = false;[/li][li] var bCallAirVisual = false;[/li][li] var bCallWunderground = false;[/li][li] var bCallOWM = false;[/li][li] var bCallWeather2 = false;[/li][li] var bCallAeris = false;[/li][li] var bCallDarkSky = false;[/li][li] var bIsEarlyHours = false;[/li][li] [/li][li] /*var nRainMMNow = 0; [/li][li] var nPoPForecast = 0;[/li][li] var nRainMMForecast = 0;[/li][li] var sWindChillTemp = '-100';*/[/li][li] [/li][li] // Global var (re)initialisations, just in case[/li][li] bIsCloseToSunriseOrSunset = false;[/li][li] nRecentFailuresZP = 0;[/li][li] nSkippedCallsZP = 0;[/li][li] [/li][li] // For debugging purposes only, declare local equivalents of the global variables[/li][li] var bIsCloseToSunriseOrSunsetLocal = false;[/li][li] var nRecentFailuresZPLocal = 0;[/li][li] var nSkippedCallsZPLocal = 0;[/li][li] [/li][li] // Take minutes and divide by ten, removing fractions[/li][li] var nMinuteTens = Math.floor(nMinute / 10);[/li][li] if (nMinuteTens % 2 == 0)[/li][li] {[/li][li] bOddMinutes = false;[/li][li] }[/li][li] [/li][li] // If it's the early hours of the morning, also restrict the call frequency[/li][li] if (nHour > 2 && nHour < 7)[/li][li] {[/li][li] bIsEarlyHours = true;[/li][li] } [/li][li] else[/li][li] {[/li][li] bCallWunderground = true; // Wunderground is called very 10 minutes except during the early hours, when it's called once every 20min[/li][li] }[/li][li] [/li][li] if (bOddMinutes)[/li][li] {[/li][li] // Call OWM and Weather2 once every 20min, except during the early hours, when we just call them once per 30min[/li][li] if (!bIsEarlyHours)[/li][li] {[/li][li] bCallOWM = true;[/li][li] bCallWeather2 = true;[/li][li] }[/li][li] [/li][li] bCallWunderground = true; // Wunderground is called every 10 minutes except during the early hours, when it's called once every 20min[/li][li] }[/li][li] else[/li][li] {[/li][li] bCallDarkSky = true;[/li][li] bCallAeris = true;[/li][li] } [/li][li] [/li][li] // Restrict Zipabox & AirVisual calls to 2 per hour[/li][li] if (nMinuteTens == 0 || nMinuteTens == 3)[/li][li] {[/li][li] bCallZipabox = true;[/li][li] bCallAirVisual = true;[/li][li] [/li][li] // Call OWM and Weather2 once every 20min, except during the early hours, when we just call them once per 30min[/li][li] if (bIsEarlyHours)[/li][li] {[/li][li] bCallOWM = true;[/li][li] bCallWeather2 = true;[/li][li] } [/li][li] }[/li][li] [/li][li] // Open API call failure spreadsheet to log call failures and record skipped call attempts so as to minimise the chewing up of daily script quotas[/li][li] var doc = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/XXXXXXXXXSHEETIDXXXXXXXXXXXXX/edit');[/li][li] var sheet = doc.getSheets()[0]; [/li][li] [/li][li] // First check if we have had many Zipabox failures recently[/li][li] nRecentFailuresZP = sheet.getRange(2,4,2).getCell(1,1).getValue();[/li][li] nSkippedCallsZP = sheet.getRange(2,4,2).getCell(2,1).getValue();[/li][li] [/li][li] // Copy to local vars so we can see them in the watch window[/li][li] nRecentFailuresZPLocal = nRecentFailuresZP;[/li][li] nSkippedCallsZPLocal = nSkippedCallsZP;[/li][li] [/li][li] // Reset skipped calls if over 5. We retry at least once per hour[/li][li] if (nSkippedCallsZP > 5)[/li][li] {[/li][li] nSkippedCallsZP = 0;[/li][li] }[/li][li] [/li][li] // Copy to local vars so we can see them in the watch window[/li][li] nSkippedCallsZPLocal = nSkippedCallsZP;[/li][li] [/li][li] // The more failures, the more calls we skip[/li][li] if (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0)[/li][li] { [/li][li] nSkippedCallsZP = 0;[/li][li] [/li][li] // Create time value (seconds since midnight)[/li][li] var nZipaboxTimeValue = ExtractTimeValueInSecondsFromDate(dateNow);[/li][li] //var dateTodayMidnight = new Date(dateNow.getFullYear(), dateNow.getMonth(), dateNow.getDate(), 0, 0, 0);[/li][li] //var nZipaboxTimeValue = (dateNow.valueOf() - dateTodayMidnight.valueOf()) / 1000;[/li][li] [/li][li] // Send values to Zipabox date-time meter[/li][li] var resultZipaboxDateTime = UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_DATETIMEMETER_SUFFIX+[/li][li] "&value9=" + parseFloat(nZipaboxDateTime) + "&value1=" + parseInt(nZipaboxTimeValue), options);[/li][li] [/li][li] if (resultZipaboxDateTime.getResponseCode() == 200) [/li][li] {[/li][li] nRecentFailuresZP = 0;[/li][li] }[/li][li] else[/li][li] {[/li][li] nRecentFailuresZP = nRecentFailuresZP + 1;[/li][li] }[/li][li] }[/li][li] else[/li][li] {[/li][li] nSkippedCallsZP = nSkippedCallsZP + 1;[/li][li] } [/li][li] [/li][li] // Copy to local vars so we can see them in the watch window[/li][li] nRecentFailuresZPLocal = nRecentFailuresZP;[/li][li] nSkippedCallsZPLocal = nSkippedCallsZP;[/li][li] [/li][li] // OpenWeatherMap [/li][li] bOWMDataSentOK = CallOWMWeatherAPI(bCallOWM, nDayInMilliseconds, nHourInMilliseconds, nMinuteInMilliseconds, options,sZIPABOX_REMOTING_PREFIX);[/li][li] [/li][li] // Zipabox (just to get last synch time)[/li][li] bZipaboxDataSentOK = CallZipaboxWeatherAPI(bCallZipabox, sZIPABOX_REMOTING_PREFIX, sZIP_LASTSENSORUPDATE_SUFFIX, options);[/li][li] [/li][li] // Wunderground[/li][li] bWundergroundDataSentOK = CallWundergroundWeatherAPI(bCallWunderground, sZIPABOX_REMOTING_PREFIX, sZIP_DATETIMEMETER_SUFFIX, options, nDayInMilliseconds, nHourInMilliseconds, nMinuteInMilliseconds);[/li][li] [/li][li] // DarkSky[/li][li] bDarkSkyDataSentOK = CallDarkSkyWeatherAPI(bCallDarkSky, sZIPABOX_REMOTING_PREFIX, options, nDayInMilliseconds, nMinuteInMilliseconds);[/li][li] [/li][li] // AirVisual[/li][li] bAirVisualDataSentOK = CallAirVisualWeatherAPI(bCallAirVisual, sZIPABOX_REMOTING_PREFIX, options, sheet, nDayInMilliseconds, nMinuteInMilliseconds);[/li][li] [/li][li] // Weather2[/li][li] bWeather2DataSentOK = CallWeather2WeatherAPI(bCallWeather2, sZIPABOX_REMOTING_PREFIX, options, sheet, nZipaboxDateTime);[/li][li] [/li][li] // Copy to local vars so we can see them in the watch window[/li][li] nRecentFailuresZPLocal = nRecentFailuresZP;[/li][li] nSkippedCallsZPLocal = nSkippedCallsZP;[/li][li] bIsCloseToSunriseOrSunsetLocal = bIsCloseToSunriseOrSunset;[/li][li] [/li][li] // If we are close to sunrise or sunset, call Aeris unless we have recent failures, so we get the light% value[/li][li] if (bIsCloseToSunriseOrSunset)[/li][li] {[/li][li] bCallAeris = true;[/li][li] }[/li][li] [/li][li] // Aeris[/li][li] bAerisDataSentOK = CallAerisWeatherAPI(bCallAeris, sZIPABOX_REMOTING_PREFIX, options, sheet);[/li][li] [/li][li] // Update the spreadsheet with Zipabox failures[/li][li] sheet.getRange(2,4,2).getCell(1,1).setValue(nRecentFailuresZP);[/li][li] sheet.getRange(2,4,2).getCell(2,1).setValue(nSkippedCallsZP);[/li][li] [/li][li]// lock.releaseLock();[/li][li] [/li][li] var response;[/li][li] [/li][li] if (bOWMDataSentOK && bWundergroundDataSentOK && bDarkSkyDataSentOK && bAirVisualDataSentOK && bWeather2DataSentOK && bAerisDataSentOK)[/li][li] {[/li][li] //Logger.log("GetWeatherData completed successfully.");[/li][li] response = sendResponse("200", "Function succeeded.")[/li][li] }[/li][li] else[/li][li] {[/li][li] //Logger.log("GetWeatherData completed but with errors.");[/li][li] response = sendResponse("200", "Function did not completely succeed.")[/li][li] }[/li][li] [/li][li] return ContentService.createTextOutput(JSON.stringify(response))[/li][li] .setMimeType(ContentService.MimeType.JSON);[/li][li]}[/li][li] [/li][li]function convertAerisWeatherCode(sCodedWeather)[/li][li]{[/li][li] var nIcon = 0;[/li][li] [/li][li] var arrCodeParts = sCodedWeather.split(":");[/li][li] [/li][li] if (arrCodeParts.length == 3)[/li][li] {[/li][li] var sCoverage = arrCodeParts[0];[/li][li] var sIntensity = arrCodeParts[1];[/li][li] var sWeather = arrCodeParts[2];[/li][li] [/li][li] // Weather:[/li][li] /* Cloud codes: CLClearCloud coverage is 0-7% of the sky.[/li][li] FWFair/Mostly sunnyCloud coverage is 7-32% of the sky.[/li][li] SCPartly cloudyCloud coverage is 32-70% of the sky.[/li][li] BKMostly CloudyCloud coverage is 70-95% of the sky.[/li][li] OVCloudy/OvercastCloud coverage is 95-100% of the sky.[/li][li] Weather codes: AHail[/li][li] BDBlowing dust[/li][li] BNBlowing sand[/li][li] BRMist[/li][li] BSBlowing snow[/li][li] BYBlowing spray[/li][li] FFog[/li][li] FRFrost[/li][li] HHaze[/li][li] ICIce crystals[/li][li] IFIce fog[/li][li] IPIce pellets / Sleet[/li][li] KSmoke[/li][li] LDrizzle[/li][li] RRain[/li][li] RWRain showers[/li][li] RSRain/snow mix[/li][li] SISnow/sleet mix[/li][li] WMWintry mix (snow, sleet, rain)[/li][li] SSnow[/li][li] SWSnow showers[/li][li] TThunderstorms[/li][li] UPUnknown precipitationMay occur in an automated observation station, which cannot determine the precipitation type falling.[/li][li] VAVolcanic ash[/li][li] WPWaterspouts[/li][li] ZFFreezing fog[/li][li] ZLFreezing drizzle[/li][li] ZRFreezing rain[/li][li] ZYFreezing spray[/li][li] [/li][li] Intensity codes: VLVery light[/li][li] LLight[/li][li] HHeavy[/li][li] VHVery heavy[/li][li] [/li][li] Coverage codes: ARAreas of[/li][li] BRBrief[/li][li] CChance of[/li][li] DDefinite[/li][li] FQFrequent[/li][li] INIntermittent[/li][li] ISIsolated[/li][li] LLikely[/li][li] NMNumerous[/li][li] OOccasional[/li][li] PAPatchy[/li][li] PDPeriods of[/li][li] SSlight chance[/li][li] SCScattered[/li][li] VCIn the vicinity/Nearby[/li][li] WDWidespread [/li][li] */[/li][li] [/li][li] switch (sWeather) [/li][li] {[/li][li] case 'CL': nIcon = 1; break;[/li][li] case 'FW': nIcon = 2; break;[/li][li] case 'FR': [/li][li] case 'IC': [/li][li] case 'IF': [/li][li] {[/li][li] nIcon = 3; break; // Icy[/li][li] }[/li][li] case 'K': nIcon = 4; break;[/li][li] case 'BD': nIcon = 5; break; // Dusty[/li][li] case 'H': nIcon = 6; break;[/li][li] case 'SC': nIcon = 7; break;[/li][li] case 'BK': nIcon = 8; break;[/li][li] case 'L': [/li][li] case 'RW': [/li][li] case 'ZL': [/li][li] {[/li][li] nIcon = 10; break; // Showers[/li][li] }[/li][li] case 'BR': nIcon = 11; break;[/li][li] case 'R': [/li][li] case 'ZR': [/li][li] {[/li][li] nIcon = 12; break; // Rain[/li][li] }[/li][li] case 'BS': [/li][li] case 'S': [/li][li] case 'SW': [/li][li] {[/li][li] nIcon = 14; break; // Snow[/li][li] }[/li][li] case 'OV': nIcon = 15; break;[/li][li] case 'IP': [/li][li] case 'RS': [/li][li] case 'SI': [/li][li] case 'WM': [/li][li] {[/li][li] nIcon = 16; break; // Rain/snow/sleet[/li][li] }[/li][li] case 'F': [/li][li] case 'ZF': [/li][li] {[/li][li] nIcon = 17; break; // Fog[/li][li] }[/li][li] case 'A': nIcon = 20; break; // Storm[/li][li] case 'T': nIcon = 21; break;[/li][li] } [/li][li] [/li][li] }[/li][li] [/li][li] return parseInt(nIcon);[/li][li]}[/li][li] [/li][li]function convertWeather2WeatherCode(nWeather2Code) [/li][li]{[/li][li] var nIcon = -1;[/li][li] [/li][li] switch (nWeather2Code) [/li][li] {[/li][li] case 0: nIcon = 1; break; // Sunny[/li][li] case 1: nIcon = 7; break; // Partly cloudy[/li][li] case 2: nIcon = 8; break; // Cloudy[/li][li] case 3: nIcon = 15; break; // Overcast[/li][li] case 10: nIcon = 11; break; // Mist[/li][li] case 21: nIcon = 9; break; // Patchy rain possible[/li][li] case 22: nIcon = 13; break; // Patchy snow possible[/li][li] case 23: nIcon = 16; break; // Patchy sleet possible[/li][li] case 24: nIcon = 16; break; // Freezing drizzle possible [/li][li] case 29: nIcon = 21; break; // Thundery outbreaks possible[/li][li] case 38: nIcon = 14; break; // Blowing snow[/li][li] case 39: nIcon = 14; break; // Blizzard[/li][li] case 45: nIcon = 17; break; // Fog[/li][li] case 49: nIcon = 17; break; // Freezing fog [/li][li] case 50: nIcon = 10; break; // Patchy light drizzle[/li][li] case 51: nIcon = 10; break; // Light drizzle[/li][li] case 56: nIcon = 16; break; // Freezing drizzle [/li][li] case 57: nIcon = 16; break; // Heavy freezing drizzle [/li][li] case 60: nIcon = 10; break; // Patchy light rain[/li][li] case 61: nIcon = 10; break; // Light rain[/li][li] case 62: nIcon = 12; break; // Moderate rain at times[/li][li] case 63: nIcon = 12; break; // Moderate rain[/li][li] case 64: nIcon = 12; break; // Heavy rain at times[/li][li] case 65: nIcon = 12; break; // Heavy rain[/li][li] case 66: nIcon = 10; break; // Light freezing rain[/li][li] case 67: nIcon = 12; break; // Moderate or heavy freezing rain[/li][li] case 68: nIcon = 16; break; // Light sleet[/li][li] case 69: nIcon = 16; break; // Moderate or heavy sleet[/li][li] case 70: [/li][li] case 71: [/li][li] case 72: [/li][li] case 73: [/li][li] case 74: [/li][li] case 75: [/li][li] {[/li][li] nIcon = 14; break; // Various degrees of snow[/li][li] }[/li][li] case 79: nIcon = 3; break; // Ice pellets[/li][li] case 80: nIcon = 10; break; // Light rain shower[/li][li] case 81: nIcon = 12; break; // Moderate or heavy rain shower[/li][li] case 82: nIcon = 20; break; // Torrential rain shower[/li][li] case 83: nIcon = 16; break; // Light sleet showers[/li][li] case 84: nIcon = 16; break; // Moderate or heavy sleet showers [/li][li] case 85: nIcon = 14; break; // Light snow showers[/li][li] case 86: nIcon = 14; break; // Moderate or heavy snow showers[/li][li] case 87: nIcon = 3; break; // Light showers of ice pellets[/li][li] case 88: nIcon = 3; break; // Moderate or Heavy showers of ice pellets[/li][li] case 91: [/li][li] case 92: [/li][li] case 93: [/li][li] case 94: [/li][li] {[/li][li] nIcon = 21; break; // Various degrees of thunderstorm with rain or snow[/li][li] }[/li][li] }[/li][li] return parseInt(nIcon);[/li][li]};[/li][li] [/li][li]// From https://code.google.com/p/google-apps-script-issues/issues/detail?id=106[/li][li]/**[/li][li]* make a hex sha1 string[/li][li]* @param {string} content some content[/li][li]* @return {string} the hex result[/li][li]*/[/li][li]function makeSha1Hex (content) [/li][li]{[/li][li] return byteToHexString(Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_1, content));[/li][li]}[/li][li] [/li][li]/**[/li][li]* convert an array of bytes to a hex string[/li][li]* @param {Array.byte} bytes the byte array to convert[/li][li]* @return {string} the hex encoded string[/li][li]*/[/li][li]function byteToHexString (bytes) [/li][li]{[/li][li] return bytes.reduce(function (p,c) [/li][li] {[/li][li] return p += padLeading ((c < 0 ? c+256 : c).toString(16), 2 );[/li][li] },'');[/li][li]}[/li][li]/**[/li][li]* pad leading part of string[/li][li]* @param {string} stringtoPad the source string[/li][li]* @param {number} targetLength what the final string length should be[/li][li]* @param {string} padWith optional what to pad with - default "0"[/li][li]* @return {string} the padded string[/li][li]*/[/li][li]function padLeading (stringtoPad , targetLength , padWith) [/li][li]{[/li][li] return (stringtoPad.length < targetLength ? Array(1+targetLength-stringtoPad.length).join(padWith | "0") : "" ) + stringtoPad ;[/li][li]}[/li][li]/**[/li][li]* get base64 encoded data as a string[/li][li]* @param {string} b64 as a string[/li][li]* @return {string} decoded as as string[/li][li]*/[/li][li]function b64ToString ( b64) [/li][li]{[/li][li] return Utilities.newBlob(Utilities.base64Decode(result.content)).getDataAsString();[/li][li]}[/li][li] [/li][li]function IsCloseToSunriseOrSunset(dateSunrise, dateSunset, nObservationDateTimeInHours)[/li][li]{[/li][li] // Convert sunrise and sunset into same format as observation time (hours)[/li][li] var nSunriseDateTime = dateSunrise.valueOf() / 1000 / 60 / 60;[/li][li] var nSunsetDateTime = dateSunset.valueOf() / 1000 / 60 / 60;[/li][li] [/li][li] // If before sunrise and not much more than 20 minutes before OR[/li][li] // If after sunrise and not much more than 20 minutes after OR[/li][li] // If before sunset and not much more than 20 minutes before OR[/li][li] // If after sunset and not much more than 20 minutes after[/li][li] if ([/li][li] (nSunriseDateTime - nObservationDateTimeInHours > 0 && nSunriseDateTime - nObservationDateTimeInHours < 0.4) ||[/li][li] (nObservationDateTimeInHours - nSunriseDateTime > 0 && nObservationDateTimeInHours - nSunriseDateTime < 0.4) ||[/li][li] (nSunsetDateTime - nObservationDateTimeInHours > 0 && nSunsetDateTime - nObservationDateTimeInHours < 0.4) ||[/li][li] (nObservationDateTimeInHours - nSunsetDateTime > 0 && nObservationDateTimeInHours - nSunsetDateTime < 0.4) [/li][li] )[/li][li] {[/li][li] return true;[/li][li] }[/li][li] else[/li][li] {[/li][li] return false;[/li][li] }[/li][li]}[/li][li] [/li][li]function ExtractTimeValueInSecondsFromDate(dateValue)[/li][li]{[/li][li] var dateNow = new Date();[/li][li] [/li][li] // Create time value (seconds since midnight)[/li][li] var dateTodayMidnight = new Date(dateNow.getFullYear(), dateNow.getMonth(), dateNow.getDate(), 0, 0, 0);[/li][li] var nTimeValue = (dateValue.valueOf() - dateTodayMidnight.valueOf()) / 1000;[/li][li] [/li][li] return nTimeValue;[/li][li]}[/li][li] [/li][li]function CallOWMPollutionAPI(sZIPABOX_REMOTING_PREFIX)[/li][li]{[/li][li] // Call openweathermap API for O3, SO2 and NO2[/li][li] var url = 'http://api.openweathermap.org/pollution/v1/o3/XXXXLATITUDEXXXXX,XXXXXLONGITUDEXXXXXXX/current.json?appid=XXXXXXXXXXXXAPIKEYXXXXXXXXXXXXX'[/li][li] var responseO3 = UrlFetchApp.fetch(url);[/li][li] [/li][li] // Apparently these are hardly ever updated[/li][li] [/li][li] //var url = 'http://api.openweathermap.org/pollution/v1/no2/40.38,-3.7/current.json?appid=6312d867d94d33fedff8512925b86d77'[/li][li] //var responseNO2 = UrlFetchApp.fetch(url);[/li][li] [/li][li] //var url = 'http://api.openweathermap.org/pollution/v1/so2/40.38,-3.7/current.json?appid=6312d867d94d33fedff8512925b86d77'[/li][li] //var responseSO2 = UrlFetchApp.fetch(url);[/li][li] [/li][li] if (responseO3.getResponseCode() == 200)[/li][li] {[/li][li] var contentText = response.getContentText();[/li][li] var conditions = JSON.parse(contentText);[/li][li] [/li][li] if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.main !== undefined && conditions.main !== null && conditions.cod != "404")[/li][li] {[/li][li] var dateObservation = new Date(conditions.time);[/li][li] var nDobsons = conditions.data;[/li][li] [/li][li] // Convert observation date-time into our Zipabox date-time format (hours)[/li][li] var nObservationDateTime = dateObservation.valueOf() / 1000 / 60 / 60;[/li][li] [/li][li] const sZIP_OWM_SUFFIX = "XXXXXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXXXXX";[/li][li] [/li][li] var sSuffixes = [/li][li] "&value10=" + parseFloat(nDobsons) + [/li][li] "&value11=" + parseFloat(nObservationDateTime);[/li][li] [/li][li] if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_OWM_SUFFIX+sSuffixes, options).getResponseCode() == 200)[/li][li] {[/li][li] return true;[/li][li] }[/li][li] }[/li][li] }[/li][li] [/li][li] return false;[/li][li]}[/li][li] [/li][li]function CallOWMWeatherAPI(bCallOWM, nDayInMilliseconds, nHourInMilliseconds, nMinuteInMilliseconds, options, sZIPABOX_REMOTING_PREFIX)[/li][li]{[/li][li] var bOWMDataSentOK = false;[/li][li] [/li][li] // Do OWM every second call [/li][li] if (bCallOWM && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0))[/li][li] {[/li][li] // Call openweathermap API[/li][li] var url = 'http://api.openweathermap.org/data/2.5/weather?id=XXXXXLOCATIONIDXXXX&appid=XXXXXXXXAPIKEYXXXXXXX&units=metric'[/li][li] var response = UrlFetchApp.fetch(url);[/li][li] // var nResponseCode = response.getResponseCode();[/li][li] [/li][li] if (response.getResponseCode() == 200)[/li][li] {[/li][li] var contentText = response.getContentText();[/li][li] var conditions = JSON.parse(contentText);[/li][li] [/li][li] if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.main !== undefined && conditions.main !== null && conditions.cod != "404")[/li][li] {[/li][li] var nCloudCover = conditions.clouds.all;[/li][li] var nTemperature = conditions.main.temp;[/li][li] var nMinTemperatureC = conditions.main.temp_min;[/li][li] var nMaxTemperatureC = conditions.main.temp_max;[/li][li] var nHumidity = conditions.main.humidity;[/li][li] var nWindSpeed = conditions.wind.speed*3600/1000;[/li][li] var nSunriseDateTime = conditions.sys.sunrise;[/li][li] var nSunsetDateTime = conditions.sys.sunset;[/li][li] var sIcon = conditions.weather[0].icon;[/li][li] [/li][li] // Get hour/minute of sunset[/li][li] var dateSunset = new Date(conditions.sys.sunset*1000);[/li][li] var nHourSunset = dateSunset.getHours();[/li][li] var nMinuteSunset = dateSunset.getMinutes();[/li][li] [/li][li] // Get hour/minute of sunrise[/li][li] var dateSunrise = new Date(conditions.sys.sunrise*1000);[/li][li] var nHourSunrise = dateSunrise.getHours();[/li][li] var nMinuteSunrise = dateSunrise.getMinutes();[/li][li] [/li][li] // Get sunrise/sunset times[/li][li] var nSunriseTime = ExtractTimeValueInSecondsFromDate(dateSunrise); [/li][li] var nSunsetTime = ExtractTimeValueInSecondsFromDate(dateSunset); [/li][li] [/li][li] // Refresh current date-time[/li][li] var dateNow = new Date();[/li][li] [/li][li] // Get date-time of observation[/li][li] var date = new Date(conditions.dt*1000);[/li][li] [/li][li] // Compare to current date[/li][li] var nHoursDifference = Math.floor(((dateNow - date) % nDayInMilliseconds) / nHourInMilliseconds);[/li][li] var nMinutesDifference = Math.floor(((dateNow - date) % nDayInMilliseconds) / nMinuteInMilliseconds);[/li][li] [/li][li] // Convert observation date-time into our Zipabox date-time format (hours)[/li][li] var nObservationDateTime = date.valueOf() / 1000 / 60 / 60;[/li][li] [/li][li] // Work out whether we are close to sunrise or sunset[/li][li] bIsCloseToSunriseOrSunset = IsCloseToSunriseOrSunset(dateSunrise, dateSunset, nObservationDateTime);[/li][li] [/li][li] // Convert string icon name into number[/li][li] var nNumericIconOWM = 0;[/li][li] [/li][li] switch (sIcon) [/li][li] {[/li][li] // Clear sky[/li][li] case '01d': [/li][li] case '01n': [/li][li] {[/li][li] nNumericIconOWM = 1; [/li][li] break;[/li][li] }[/li][li] // Few clouds[/li][li] case '02d': [/li][li] case '02n': [/li][li] {[/li][li] nNumericIconOWM = 2; [/li][li] break;[/li][li] }[/li][li] // Scattered clouds[/li][li] case '03d': [/li][li] case '03n': [/li][li] {[/li][li] nNumericIconOWM = 7; [/li][li] break;[/li][li] }[/li][li] // Broken clouds[/li][li] case '04d': [/li][li] case '04n': [/li][li] {[/li][li] nNumericIconOWM = 8; [/li][li] break;[/li][li] }[/li][li] // Shower rain[/li][li] case '09d': [/li][li] case '09n': [/li][li] {[/li][li] nNumericIconOWM = 10; [/li][li] break;[/li][li] }[/li][li] // Mist[/li][li] case '50d': [/li][li] case '50n': [/li][li] {[/li][li] nNumericIconOWM = 11; [/li][li] break;[/li][li] }[/li][li] // Rain[/li][li] case '10d': [/li][li] case '10n': [/li][li] {[/li][li] nNumericIconOWM = 12; [/li][li] break;[/li][li] }[/li][li] // Snow[/li][li] case '13d': [/li][li] case '13n': [/li][li] {[/li][li] nNumericIconOWM = 14; [/li][li] break;[/li][li] }[/li][li] // Thunderstorm[/li][li] case '11d': [/li][li] case '11n': [/li][li] {[/li][li] nNumericIconOWM = 21; [/li][li] break;[/li][li] }[/li][li] } [/li][li] [/li][li] const sZIP_OWM_SUFFIX = "XXXXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXX";[/li][li] [/li][li] var sSuffixes = [/li][li] "&value1=" + parseInt(nCloudCover) + [/li][li] "&value9=" + parseFloat(nTemperature) + [/li][li] "&value6=" + parseInt(nMinTemperatureC) + [/li][li] "&value7=" + parseInt(nMaxTemperatureC) + [/li][li] "&value5=" + parseFloat(nWindSpeed) + [/li][li] /*"&value4=" + parseInt(nHourSunrise) + [/li][li] "&value3=" + parseInt(nMinuteSunrise) + [/li][li] "&value2=" + parseInt(nHourSunset) + [/li][li] "&value16=" + parseInt(nMinuteSunset) + */[/li][li] "&value4=" + parseInt(nSunriseTime) + [/li][li] "&value2=" + parseInt(nSunsetTime) + [/li][li] "&value8=" + parseInt(nHoursDifference) + [/li][li] "&value15=" + parseInt(nNumericIconOWM) + [/li][li] "&value14=" + parseInt(nHumidity) + [/li][li] /*"&value13=" + parseFloat(nZipaboxDateTime) +*/[/li][li] "&value12=" + parseFloat(nObservationDateTime);[/li][li] [/li][li] if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_OWM_SUFFIX+sSuffixes, options).getResponseCode() == 200)[/li][li] {[/li][li] bOWMDataSentOK = true;[/li][li] [/li][li] // Also try pollution API[/li][li] // Don't bother now, it's hardly ever updated[/li][li] //var bCalledOK = CallOWMPollutionAPI(sZIPABOX_REMOTING_PREFIX);[/li][li] }[/li][li] else[/li][li] {[/li][li] nRecentFailuresZP++;[/li][li] }[/li][li] }[/li][li] }[/li][li] }[/li][li] else[/li][li] {[/li][li] bOWMDataSentOK = true; [/li][li] [/li][li] if (nRecentFailuresZP > nSkippedCallsZP)[/li][li] {[/li][li] nSkippedCallsZP = nSkippedCallsZP + 1;[/li][li] }[/li][li] }[/li][li] [/li][li] return bOWMDataSentOK;[/li][li]}[/li][li] [/li][li]function CallZipaboxWeatherAPI(bCallZipabox, sZIPABOX_REMOTING_PREFIX, sZIP_LASTSENSORUPDATE_SUFFIX, options)[/li][li]{[/li][li] var bZipaboxDataSentOK = false;[/li][li] [/li][li] if (bCallZipabox && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0))[/li][li] {[/li][li] // Call Zipato weather API[/li][li] [/li][li] // Need to get "nonce" and session ID first[/li][li] var response = UrlFetchApp.fetch("https://my.zipato.com:443/zipato-web/v2/user/init");[/li][li] [/li][li] if (response.getResponseCode() == 200)[/li][li] {[/li][li] var contentText = response.getContentText();[/li][li] var zipaboxdata = JSON.parse(contentText);[/li][li] [/li][li] if (typeof(zipaboxdata) !== "undefined" && zipaboxdata !== undefined && zipaboxdata !== null && zipaboxdata.nonce !== undefined && zipaboxdata.nonce !== null) [/li][li] {[/li][li] var sNonce = zipaboxdata.nonce;[/li][li] var sSessionID = zipaboxdata.jsessionid;[/li][li] [/li][li] // Make token[/li][li] var sToken = makeSha1Hex(sNonce + makeSha1Hex("XXXXXXZIPABOXPASSWORDXXXXXXX"));[/li][li] [/li][li] var headersZipabox = [/li][li] {[/li][li] 'Cookie':"JSESSIONID="+sSessionID[/li][li] }; [/li][li] [/li][li] var optionsZipabox = [/li][li] {[/li][li] 'headers' : headersZipabox[/li][li] }; [/li][li] [/li][li] // Now log in[/li][li] response = UrlFetchApp.fetch("https://my.zipato.com:443/zipato-web/v2/user/login?username=XXXXXXXXZIPABOXUSERNAMEXXXXXXXXXXXXXXXXX&token=" + sToken + "&serial=XXXXZIPABOXSERIALXXXXXXXXXX", optionsZipabox);[/li][li] [/li][li] if (response.getResponseCode() == 200)[/li][li] {[/li][li] var contentText = response.getContentText();[/li][li] [/li][li] // Now get weather data[/li][li] response = UrlFetchApp.fetch("https://my.zipato.com:443/zipato-web/v2/meteo/weather?location=XXXXWEATHERSTATIONCODEXXXXX", optionsZipabox);[/li][li] [/li][li] if (response.getResponseCode() == 200)[/li][li] {[/li][li] var contentText = response.getContentText();[/li][li] var conditions = JSON.parse(response.getContentText());[/li][li] [/li][li] if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.conditions !== undefined && conditions.conditions !== null) [/li][li] {[/li][li] var sObservationDate = conditions.conditions.observation_epoch;[/li][li] [/li][li] // Trim off Z from end [/li][li] sObservationDate = sObservationDate.slice(0, -1);[/li][li] [/li][li] // Add milliseconds and Z[/li][li] sObservationDate = sObservationDate + ".000Z";[/li][li] [/li][li] // Make into date[/li][li] var dateObservation = new Date(sObservationDate); [/li][li] [/li][li] // Convert observation date-time into our Zipabox date-time format (hours)[/li][li] var nObservationDateTime = dateObservation.valueOf() / 1000 / 60 / 60; [/li][li] [/li][li] var sSuffixes = [/li][li] "&value12=" + parseFloat(nObservationDateTime); [/li][li] [/li][li] if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_LASTSENSORUPDATE_SUFFIX+sSuffixes, options).getResponseCode() == 200)[/li][li] {[/li][li] bZipaboxDataSentOK = true;[/li][li] } [/li][li] else[/li][li] {[/li][li] nRecentFailuresZP = nRecentFailuresZP + 1;[/li][li] } [/li][li] }[/li][li] }[/li][li] }[/li][li] }[/li][li] }[/li][li] }[/li][li] else[/li][li] {[/li][li] bZipaboxDataSentOK = true;[/li][li] [/li][li] if (nRecentFailuresZP > nSkippedCallsZP)[/li][li] {[/li][li] nSkippedCallsZP = nSkippedCallsZP + 1;[/li][li] }[/li][li] }[/li][li] [/li][li] return bZipaboxDataSentOK;[/li][li]}[/li][li] [/li][li]function CallWundergroundWeatherAPI(bCallWunderground, sZIPABOX_REMOTING_PREFIX, sZIP_DATETIMEMETER_SUFFIX, options, nDayInMilliseconds, nHourInMilliseconds, nMinuteInMilliseconds)[/li][li]{[/li][li] var bWundergroundDataSentOK = false;[/li][li] [/li][li] if (bCallWunderground && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0))[/li][li] {[/li][li] // Call Wunderground API[/li][li] var url = 'http://api.wunderground.com/api/XXXXXXAPIKEYXXXXXXX/conditions/forecast/alert/q/XXXXXXXLATITUDEXXXXXXXXXX,XXXXXXLONGITUDEXXXXXXXX.json';[/li][li] var response = UrlFetchApp.fetch(url);[/li][li] //var nResponseCode = response.getResponseCode();[/li][li] [/li][li] if (response.getResponseCode() == 200)[/li][li] {[/li][li] var contentText = response.getContentText();[/li][li] var conditions = JSON.parse(contentText);[/li][li] [/li][li] if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.current_observation !== undefined && conditions.current_observation !== null &&[/li][li] conditions.forecast !== undefined && conditions.forecast !== null && conditions.forecast.simpleforecast !== undefined && conditions.forecast.simpleforecast !== null && [/li][li] conditions.forecast.simpleforecast.forecastday[0] !== undefined && conditions.forecast.simpleforecast.forecastday[0] !== null) [/li][li] {[/li][li] var todaysConditions = conditions;[/li][li] [/li][li] var sIcon = todaysConditions.current_observation.icon;[/li][li] var nIcon = 0;[/li][li] var bSkipRainMMNow = false;[/li][li] var nRainMMNow = 0;[/li][li] var sWindChillTemp = '-100';[/li][li] [/li][li] switch (sIcon) [/li][li] {[/li][li] case 'clear': nIcon = 1; break;[/li][li] case 'mostlysunny': nIcon = 2; break;[/li][li] case 'icy': nIcon = 3; break;[/li][li] case 'smoke': nIcon = 4; break;[/li][li] case 'dusty': nIcon = 5; break;[/li][li] case 'hazy': nIcon = 6; break;[/li][li] case 'partlycloudy': nIcon = 7; break;[/li][li] case 'mostlycloudy': nIcon = 8; break;[/li][li] case 'chancerain': nIcon = 9; break;[/li][li] case 'showers': nIcon = 10; break;[/li][li] case 'mist': nIcon = 11; break;[/li][li] case 'rain': nIcon = 12; break;[/li][li] case 'chancesnow': nIcon = 13; break;[/li][li] case 'snow': nIcon = 14; break;[/li][li] case 'cloudy': nIcon = 15; break;[/li][li] case 'rain_snow': nIcon = 16; break;[/li][li] case 'fog': nIcon = 17; break;[/li][li] case 'foggy': nIcon = 18; break;[/li][li] case 'chancestorm': nIcon = 19; break;[/li][li] case 'storm': nIcon = 20; break;[/li][li] case 'thunderstorm': nIcon = 21; break;[/li][li] }[/li][li] [/li][li] // Get current rain in mm now[/li][li] if (todaysConditions.current_observation.precip_today_metric == "--")[/li][li] {[/li][li] nRainMMNow = -1;[/li][li] }[/li][li] else[/li][li] {[/li][li] nRainMMNow = parseFloat(todaysConditions.current_observation.precip_today_metric);[/li][li] }[/li][li] [/li][li] // Get rain in mm and probability of precipitation forecast for today[/li][li] var nRainMMForecast = todaysConditions.forecast.simpleforecast.forecastday[0].qpf_day.mm;[/li][li] var nPoPForecast = todaysConditions.forecast.simpleforecast.forecastday[0].pop;[/li][li] [/li][li] if (nRainMMForecast == null)[/li][li] {[/li][li] nRainMMForecast = -1;[/li][li] }[/li][li] [/li][li] // Get current temperature (C)[/li][li] var nTemperatureC = todaysConditions.current_observation.temp_c;[/li][li] [/li][li] // Get min temperature (C)[/li][li] var nMinTemperatureC = todaysConditions.forecast.simpleforecast.forecastday[0].low.celsius;[/li][li] [/li][li] // Get max temperature (C)[/li][li] var nMaxTemperatureC = todaysConditions.forecast.simpleforecast.forecastday[0].high.celsius;[/li][li] [/li][li] // Get wind speed & humidity[/li][li] var nWindSpeed = todaysConditions.current_observation.wind_kph;[/li][li] var sHumidity = todaysConditions.current_observation.relative_humidity;[/li][li] sHumidity.replace('%', '');[/li][li] [/li][li] // Get wind chill temperature[/li][li] if (todaysConditions.current_observation.windchill_c != 'NA' && todaysConditions.current_observation.windchill_c != null)[/li][li] {[/li][li] sWindChillTemp = todaysConditions.current_observation.windchill_c;[/li][li] }[/li][li] else if (todaysConditions.current_observation.feelslike_c != 'NA' && todaysConditions.current_observation.feelslike_c != null)[/li][li] {[/li][li] sWindChillTemp = todaysConditions.current_observation.feelslike_c;[/li][li] }[/li][li] [/li][li] // Refresh current date-time[/li][li] var dateNow = new Date();[/li][li] [/li][li] // Get observation date/time[/li][li] var dateWundergroundObservation = new Date(todaysConditions.current_observation.observation_epoch*1000); [/li][li] [/li][li] // Convert observation date-time into our Zipabox date-time format (hours)[/li][li] var nObservationDateTime = dateWundergroundObservation.valueOf() / 1000 / 60 / 60;[/li][li] [/li][li] // Compare to current date[/li][li] var nHoursDifferenceW = Math.floor(((dateNow - dateWundergroundObservation) % nDayInMilliseconds) / nHourInMilliseconds);[/li][li] var nMinutesDifferenceW = Math.floor(((dateNow - dateWundergroundObservation) % nDayInMilliseconds) / nMinuteInMilliseconds); [/li][li] [/li][li] // Get UTC offset (daylight savings time = +2; otherwise +1)[/li][li] var sUTCOffset = todaysConditions.current_observation.local_tz_offset;[/li][li] [/li][li] var nUTCOffset = 0;[/li][li] [/li][li] if (sUTCOffset == "+0100")[/li][li] {[/li][li] nUTCOffset = 1;[/li][li] }[/li][li] else[/li][li] {[/li][li] nUTCOffset = 2;[/li][li] }[/li][li] [/li][li] var resultZipaboxDateTime = UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_DATETIMEMETER_SUFFIX+[/li][li] "&value8=" + parseInt(nUTCOffset), options);[/li][li] [/li][li] const sZIP_WUNDERGROUND_SUFFIX = "XXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXXX";[/li][li] [/li][li] var sSuffix = "&value1=" + parseInt(nIcon)[/li][li] +"&value9=" + parseFloat(nRainMMForecast)[/li][li] +"&value8=" + parseInt(nPoPForecast)[/li][li] +"&value7=" + nRainMMNow[/li][li] +"&value6=" + parseInt(nTemperatureC)[/li][li] +"&value3=" + parseInt(nMinTemperatureC)[/li][li] +"&value4=" + parseInt(nMaxTemperatureC)[/li][li] +"&value2=" + parseInt(nWindSpeed)[/li][li] /* Observation age in hours */[/li][li] +"&value5=" + parseInt(nHoursDifferenceW)[/li][li] +"&value16=" + parseInt(sHumidity)[/li][li] +"&value14=" + parseFloat(nObservationDateTime)[/li][li] +"&value13=" + parseInt(sWindChillTemp);[/li][li] [/li][li] /* Send icon to Zipabox virtual meter*/[/li][li] if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_WUNDERGROUND_SUFFIX + sSuffix, options).getResponseCode() == 200)[/li][li] {[/li][li] bWundergroundDataSentOK = true;[/li][li] }[/li][li] else[/li][li] {[/li][li] nRecentFailuresZP = nRecentFailuresZP + 1;[/li][li] } [/li][li] }[/li][li] }[/li][li] }[/li][li] else[/li][li] {[/li][li] bWundergroundDataSentOK = true;[/li][li] [/li][li] if (nRecentFailuresZP > nSkippedCallsZP)[/li][li] {[/li][li] nSkippedCallsZP = nSkippedCallsZP + 1;[/li][li] }[/li][li] }[/li][li] [/li][li] return bWundergroundDataSentOK;[/li][li]}[/li][li] [/li][li]function CallDarkSkyWeatherAPI(bCallDarkSky, sZIPABOX_REMOTING_PREFIX, options, nDayInMilliseconds, nMinuteInMilliseconds)[/li][li]{[/li][li] var bDarkSkyDataSentOK = false;[/li][li] [/li][li] // Do DarkSky every second call [/li][li] if (bCallDarkSky && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0))[/li][li] {[/li][li] // Call DarkSky API[/li][li] var url = 'https://api.darksky.net/forecast/XXXXXXXXXXXXXXXXAPIKEYXXXXXXXXXX/XXXXXXXXLATITUDEXXXXXXXXX,XXXXXXXXXXXLONGITUDEXXXXXXXXX'[/li][li] var response = UrlFetchApp.fetch(url);[/li][li] //var nResponseCode = response.getResponseCode();[/li][li] [/li][li] if (response.getResponseCode() == 200)[/li][li] {[/li][li] var contentText = response.getContentText();[/li][li] var conditions = JSON.parse(contentText);[/li][li] [/li][li] if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.currently !== undefined && conditions.currently !== null)[/li][li] {[/li][li] var nTemperatureC = (5/9)*(conditions.currently.temperature-32);[/li][li] var nHumidity = conditions.currently.humidity * 100;[/li][li] var nWindSpeed = conditions.currently.windSpeed * 1.60934;[/li][li] var nCloudCover = conditions.currently.cloudCover * 100;[/li][li] var nDSOzone = conditions.currently.ozone;[/li][li] var nDSPrecipPercent = conditions.currently.precipProbability * 100;[/li][li] var nRainMMNow = conditions.currently.precipIntensity * 25.4;[/li][li] var nDSMoonPhase = conditions.daily.data[0].moonPhase * 4;[/li][li] var nSunriseDateTime = conditions.daily.data[0].sunriseTime;[/li][li] var nSunsetDateTime = conditions.daily.data[0].sunsetTime;[/li][li] var sIcon = conditions.currently.icon;[/li][li] var sWindChillTemp = (5/9)*(conditions.currently.apparentTemperature-32);[/li][li] [/li][li] var nIcon = 0;[/li][li] [/li][li] switch (sIcon) [/li][li] {[/li][li] case 'clear-day': nIcon = 1; break;[/li][li] case 'clear-night': nIcon = 1; break;[/li][li] case 'partly-cloudy-day': nIcon = 7; break;[/li][li] case 'partly-cloudy-night': nIcon = 7; break;[/li][li] case 'cloudy': nIcon = 8; break;[/li][li] case 'rain': nIcon = 12; break;[/li][li] case 'snow': nIcon = 14; break;[/li][li] case 'cloudy': nIcon = 15; break;[/li][li] case 'sleet': nIcon = 16; break;[/li][li] case 'fog': nIcon = 17; break;[/li][li] case 'thunderstorm': nIcon = 21; break;[/li][li] } [/li][li] [/li][li] // Get hour/minute of sunset[/li][li] var dateSunset = new Date(nSunsetDateTime*1000);[/li][li] var nHourSunset = dateSunset.getHours();[/li][li] var nMinuteSunset = dateSunset.getMinutes();[/li][li] [/li][li] // Get hour/minute of sunrise[/li][li] var dateSunrise = new Date(nSunriseDateTime*1000);[/li][li] var nHourSunrise = dateSunrise.getHours();[/li][li] var nMinuteSunrise = dateSunrise.getMinutes();[/li][li] [/li][li] // Get sunrise/sunset times[/li][li] var nSunriseTime = ExtractTimeValueInSecondsFromDate(dateSunrise); [/li][li] var nSunsetTime = ExtractTimeValueInSecondsFromDate(dateSunset); [/li][li] [/li][li] // Refresh current date-time[/li][li] var dateNow = new Date();[/li][li] [/li][li] // Get observation date/time[/li][li] var dateObservation = new Date(conditions.currently.time*1000); [/li][li] [/li][li] // Convert observation date-time into our Zipabox date-time format (hours)[/li][li] var nObservationDateTime = dateObservation.valueOf() / 1000 / 60 / 60;[/li][li] [/li][li] // Work out whether we are close to sunrise or sunset[/li][li] bIsCloseToSunriseOrSunset = IsCloseToSunriseOrSunset(dateSunrise, dateSunset, nObservationDateTime); [/li][li] [/li][li] // Compare to current date[/li][li] var nMinutesDifference = Math.floor(((dateNow - dateObservation) % nDayInMilliseconds) / nMinuteInMilliseconds);[/li][li] [/li][li] var dataThisHourlyForecast;[/li][li] var nPoPForecast = 0;[/li][li] var nRainMMForecast = 0;[/li][li] var nThisForecastPrecipProbPercent = 0;[/li][li] var nThisForecastPrecipIntensityMM = 0;[/li][li] var nThisForecastTemperatureC = 0;[/li][li] var nMinTemperatureC = 0;[/li][li] var nMaxTemperatureC = 0;[/li][li] [/li][li] // Go through forecasts and check for rain[/li][li] for (var nIndex in conditions.hourly.data) [/li][li] {[/li][li] // Only check for next 24 hours[/li][li] if (nIndex > 24)[/li][li] {[/li][li] break;[/li][li] }[/li][li] [/li][li] dataThisHourlyForecast = conditions.hourly.data[nIndex];[/li][li] [/li][li] nThisForecastPrecipProbPercent = dataThisHourlyForecast.precipProbability * 100;[/li][li] nThisForecastPrecipIntensityMM = dataThisHourlyForecast.precipIntensity * 25.4;[/li][li] nThisForecastTemperatureC = (5/9)*(dataThisHourlyForecast.temperature-32);[/li][li] [/li][li] // Initialise the min/max temperatures to real temperatures[/li][li] if (nIndex == 0)[/li][li] {[/li][li] nMinTemperatureC = nThisForecastTemperatureC;[/li][li] nMaxTemperatureC = nThisForecastTemperatureC;[/li][li] }[/li][li] [/li][li] // Store rain if greater than current figure[/li][li] if (nThisForecastPrecipProbPercent > nPoPForecast)[/li][li] {[/li][li] nPoPForecast = nThisForecastPrecipProbPercent;[/li][li] }[/li][li] if (nThisForecastPrecipIntensityMM > nRainMMForecast)[/li][li] {[/li][li] nRainMMForecast = nThisForecastPrecipIntensityMM;[/li][li] }[/li][li] [/li][li] // And check for forecast min/max temperatures[/li][li] if (nThisForecastTemperatureC > nMaxTemperatureC)[/li][li] {[/li][li] nMaxTemperatureC = nThisForecastTemperatureC;[/li][li] }[/li][li] if (nThisForecastTemperatureC < nMinTemperatureC)[/li][li] {[/li][li] nMinTemperatureC = nThisForecastTemperatureC;[/li][li] }[/li][li] }[/li][li] [/li][li] const sZIP_DARKSKY_SUFFIX = "XXXXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXXX";[/li][li] const sZIP_DARKSKY2_SUFFIX = "XXXXXXXXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXXXXXXX";[/li][li] [/li][li] if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_DARKSKY_SUFFIX[/li][li] +"&value1=" + parseFloat(nTemperatureC)[/li][li] +"&value9=" + parseInt(nHumidity)[/li][li] +"&value8=" + parseFloat(nWindSpeed)[/li][li] +"&value7=" + parseFloat(nCloudCover)[/li][li] +"&value6=" + parseFloat(nDSOzone)[/li][li] +"&value5=" + parseInt(nPoPForecast)[/li][li] +"&value4=" + parseFloat(nRainMMNow)[/li][li] +"&value3=" + parseFloat(nDSMoonPhase)[/li][li] +"&value2=" + parseInt(nSunriseTime)[/li][li] +"&value15=" + parseInt(nSunsetTime)[/li][li]/* +"&value2=" + parseInt(nHourSunrise)[/li][li] +"&value16=" + parseInt(nMinuteSunrise)[/li][li] +"&value15=" + parseInt(nHourSunset)[/li][li] +"&value14=" + parseInt(nMinuteSunset)*/[/li][li] +"&value13=" + parseInt(nIcon)[/li][li] +"&value12=" + parseInt(nMinutesDifference)[/li][li] +"&value11=" + parseFloat(nRainMMForecast)[/li][li] +"&value10=" + parseFloat(nObservationDateTime) [/li][li] , options).getResponseCode() == 200 && [/li][li] UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_DARKSKY2_SUFFIX[/li][li] +"&value1=" + parseFloat(nMinTemperatureC) [/li][li] +"&value9=" + parseFloat(nMaxTemperatureC)[/li][li] +"&value8=" + parseFloat(sWindChillTemp)[/li][li] , options).getResponseCode() == 200) [/li][li] {[/li][li] bDarkSkyDataSentOK = true;[/li][li] }[/li][li] else[/li][li] {[/li][li] nRecentFailuresZP = nRecentFailuresZP + 1;[/li][li] } [/li][li] }[/li][li] } [/li][li] }[/li][li] else[/li][li] {[/li][li] bDarkSkyDataSentOK = true;[/li][li] [/li][li] if (nRecentFailuresZP > nSkippedCallsZP)[/li][li] {[/li][li] nSkippedCallsZP = nSkippedCallsZP + 1;[/li][li] }[/li][li] }[/li][li] [/li][li] return bDarkSkyDataSentOK;[/li][li]}[/li][li] [/li][li]function CallAirVisualWeatherAPI(bCallAirVisual, sZIPABOX_REMOTING_PREFIX, options, sheet, nDayInMilliseconds, nMinuteInMilliseconds)[/li][li]{ [/li][li] var bAirVisualDataSentOK = false;[/li][li] [/li][li] if (bCallAirVisual && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0))[/li][li] {[/li][li] // Call AirVisual pollution API[/li][li] [/li][li] // First check if we have had many failures recently[/li][li] var nRecentFailuresAV = sheet.getRange(2,2,2).getCell(1,1).getValue();[/li][li] var nSkippedCallsAV = sheet.getRange(2,2,2).getCell(2,1).getValue();[/li][li] [/li][li] // Reset skipped calls if over 5. We retry at least once per hour[/li][li] if (nSkippedCallsAV > 5)[/li][li] {[/li][li] nSkippedCallsAV = 0;[/li][li] }[/li][li] [/li][li] // The more failures, the more calls we skip[/li][li] if (nRecentFailuresAV == nSkippedCallsAV || nRecentFailuresAV == 0)[/li][li] { [/li][li] nSkippedCallsAV = 0;[/li][li] [/li][li] var url = 'http://api.airvisual.com/v1/nearest?lat=XXXXXXXXXLATITUDEXXXXXXXXXXXX&lon=XXXXXXXXLONGITUDEXXXXXXXX&key=XXXXXXXXXXXXXXXXXXXXXAPIKEYXXXXXXXXXXXXXXX'[/li][li] var response = UrlFetchApp.fetch(url);[/li][li] //var nResponseCode = response.getResponseCode();[/li][li] [/li][li] if (response.getResponseCode() == 200)[/li][li] {[/li][li] var contentText = response.getContentText();[/li][li] var conditions = JSON.parse(contentText);[/li][li] [/li][li] if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.data !== undefined && conditions.data !== null)[/li][li] {[/li][li] var nUSAQI = conditions.data.current.pollution.aqius;[/li][li] var nChinaAQI = conditions.data.current.pollution.aqicn;[/li][li] var sLastUpdated = conditions.data.current.pollution.ts;[/li][li] var nWindSpeed = conditions.data.current.weather.ws*3600/1000;[/li][li] [/li][li] // Refresh current date-time[/li][li] var dateNow = new Date();[/li][li] [/li][li] var dateObservation = new Date(sLastUpdated);[/li][li] [/li][li] // Convert observation date-time into our Zipabox date-time format (hours)[/li][li] var nObservationDateTime = dateObservation.valueOf() / 1000 / 60 / 60;[/li][li] [/li][li] // Compare to current date[/li][li] var nMinutesDifference = Math.floor(((dateNow - dateObservation) % nDayInMilliseconds) / nMinuteInMilliseconds);[/li][li] [/li][li] const sZIP_AV_SUFFIX = "XXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXX";[/li][li] [/li][li] // Send data to virtual meter[/li][li] if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_AV_SUFFIX[/li][li] +"&value1=" + parseInt(nUSAQI)[/li][li] +"&value9=" + parseInt(nChinaAQI)[/li][li] +"&value5=" + parseInt(nMinutesDifference)[/li][li] +"&value4=" + parseFloat(nWindSpeed)[/li][li] /* +"&value3=" + parseFloat(nZipaboxDateTime)*/[/li][li] +"&value2=" + parseFloat(nObservationDateTime) [/li][li] , options).getResponseCode() == 200)[/li][li] {[/li][li] bAirVisualDataSentOK = true;[/li][li] }[/li][li] else[/li][li] {[/li][li] nRecentFailuresZP = nRecentFailuresZP + 1;[/li][li] } [/li][li] }[/li][li] [/li][li] nRecentFailuresAV = 0;[/li][li] }[/li][li] else[/li][li] {[/li][li] nRecentFailuresAV = nRecentFailuresAV + 1;[/li][li] }[/li][li] }[/li][li] else[/li][li] {[/li][li] nSkippedCallsAV = nSkippedCallsAV + 1;[/li][li] }[/li][li] [/li][li] // Update the spreadsheet[/li][li] sheet.getRange(2,2,2).getCell(1,1).setValue(nRecentFailuresAV);[/li][li] sheet.getRange(2,2,2).getCell(2,1).setValue(nSkippedCallsAV);[/li][li] }[/li][li] else[/li][li] {[/li][li] bAirVisualDataSentOK = true;[/li][li] [/li][li] if (nRecentFailuresZP > nSkippedCallsZP)[/li][li] {[/li][li] nSkippedCallsZP = nSkippedCallsZP + 1;[/li][li] }[/li][li] }[/li][li] [/li][li] return bAirVisualDataSentOK;[/li][li]}[/li][li] [/li][li]function CallWeather2WeatherAPI(bCallWeather2, sZIPABOX_REMOTING_PREFIX, options, sheet, nZipaboxDateTime)[/li][li]{[/li][li] var bWeather2DataSentOK = false;[/li][li] [/li][li] // Do Weather2 every second call [/li][li] if (bCallWeather2 && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0))[/li][li] { [/li][li] // First check if we have had many failures recently[/li][li] var nRecentFailuresW2 = sheet.getRange(2,3,2).getCell(1,1).getValue();[/li][li] var nSkippedCallsW2 = sheet.getRange(2,3,2).getCell(2,1).getValue();[/li][li] [/li][li] // Reset skipped calls if over 5. We retry at least once per hour[/li][li] if (nSkippedCallsW2 > 5)[/li][li] {[/li][li] nSkippedCallsW2 = 0;[/li][li] }[/li][li] [/li][li] // The more failures, the more calls we skip[/li][li] if (nRecentFailuresW2 == nSkippedCallsW2 || nRecentFailuresW2 == 0)[/li][li] { [/li][li] nSkippedCallsW2 = 0;[/li][li] [/li][li] // Call Weather2 API[/li][li] var url = 'http://www.myweather2.com/developer/forecast.ashx?uac=XXXXXXXXAPIKEYXXXXXXXXX&query=XXXXXLATITUDEXXXXXXXX,XXXXXXXLONGITUDEXXXXXXXX&temp_unit=c&output=json'[/li][li] var response = UrlFetchApp.fetch(url);[/li][li] // var nResponseCode = response.getResponseCode();[/li][li] [/li][li] if (response.getResponseCode() == 200)[/li][li] {[/li][li] var contentText = response.getContentText();[/li][li] var conditions = JSON.parse(contentText);[/li][li] [/li][li] if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.weather !== undefined && conditions.weather !== null)[/li][li] {[/li][li] var nHumidity = conditions.weather.curren_weather[0].humidity;[/li][li] var nPressure = conditions.weather.curren_weather[0].pressure;[/li][li] var nWeatherCode = conditions.weather.curren_weather[0].weather_code;[/li][li] var nTemperatureC = conditions.weather.curren_weather[0].temp;[/li][li] var nWindSpeed = conditions.weather.curren_weather[0].wind[0].speed;[/li][li] var nMinTemperatureC = conditions.weather.forecast[0].night_min_temp;[/li][li] var nMaxTemperatureC = conditions.weather.forecast[0].day_max_temp;[/li][li] var nForecastDayCode = conditions.weather.forecast[0].day[0].weather_code;[/li][li] var nForecastNightCode = conditions.weather.forecast[0].night[0].weather_code;[/li][li] [/li][li] // Convert to standard icon values[/li][li] var nConvertedWeatherCode = convertWeather2WeatherCode(parseInt(nWeatherCode));[/li][li] var nConvertedForecastDayCode = convertWeather2WeatherCode(parseInt(nForecastDayCode));[/li][li] var nConvertedForecastNightCode = convertWeather2WeatherCode(parseInt(nForecastNightCode));[/li][li] [/li][li] const sZIP_WEATHER2_SUFFIX = "XXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXX";[/li][li] [/li][li] // Send data to virtual meter[/li][li] if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_WEATHER2_SUFFIX[/li][li] +"&value1=" + parseInt(nHumidity)[/li][li] +"&value9=" + parseInt(nPressure)[/li][li] +"&value8=" + parseInt(nTemperatureC)[/li][li] +"&value7=" + parseInt(nConvertedWeatherCode)[/li][li] +"&value6=" + parseInt(nWindSpeed)[/li][li] +"&value5=" + parseInt(nMinTemperatureC)[/li][li] +"&value4=" + parseInt(nMaxTemperatureC)[/li][li] +"&value3=" + parseInt(nConvertedForecastDayCode)[/li][li] +"&value2=" + parseInt(nConvertedForecastNightCode)[/li][li] +"&value16=" + parseFloat(nZipaboxDateTime)[/li][li] , options).getResponseCode() == 200)[/li][li] {[/li][li] bWeather2DataSentOK = true;[/li][li] }[/li][li] else[/li][li] {[/li][li] nRecentFailuresZP = nRecentFailuresZP + 1;[/li][li] } [/li][li] }[/li][li] [/li][li] nRecentFailuresW2 = 0;[/li][li] }[/li][li] else[/li][li] {[/li][li] nRecentFailuresW2 = nRecentFailuresW2 + 1;[/li][li] }[/li][li] }[/li][li] else[/li][li] {[/li][li] nSkippedCallsW2 = nSkippedCallsW2 + 1;[/li][li] }[/li][li] [/li][li] // Update the spreadsheet[/li][li] sheet.getRange(2,3,2).getCell(1,1).setValue(nRecentFailuresW2);[/li][li] sheet.getRange(2,3,2).getCell(2,1).setValue(nSkippedCallsW2); [/li][li] }[/li][li] else[/li][li] {[/li][li] bWeather2DataSentOK = true;[/li][li] [/li][li] if (nRecentFailuresZP > nSkippedCallsZP)[/li][li] {[/li][li] nSkippedCallsZP = nSkippedCallsZP + 1;[/li][li] }[/li][li] }[/li][li] [/li][li] return bWeather2DataSentOK;[/li][li]}[/li][li] [/li][li]function CallAerisWeatherAPI(bCallAeris, sZIPABOX_REMOTING_PREFIX, options, sheet)[/li][li]{[/li][li] var bAerisDataSentOK = false;[/li][li] [/li][li] // Do Aeris every second call [/li][li] if (bCallAeris && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0))[/li][li] { [/li][li] // Call Aeris API[/li][li] var url = 'http://api.aerisapi.com/observations/XXXXXXXXWEATHERSTATIONCODEXXXXXXXXXXXXXXXXX?client_id=XXXXXXXXCLIENTIDXXXXXXXXXXX&client_secret=XXXXXXXXXXXCLIENTSECRETXXXXXXXXXXXXXXXX'[/li][li] var response = UrlFetchApp.fetch(url);[/li][li] //var nResponseCode = response.getResponseCode();[/li][li] [/li][li] // Also call forecast API[/li][li] var url2 = 'http://api.aerisapi.com/forecasts/XXXXXXXXWEATHERSTATIONCODEXXXXXXXXXXXXXXXXX?client_id=XXXXXXXXCLIENTIDXXXXXXXXXXX&client_secret=XXXXXXXXXXXCLIENTSECRETXXXXXXXXXXXXXXXX'[/li][li] var response2 = UrlFetchApp.fetch(url2);[/li][li] //var nResponseCode2 = response2.getResponseCode();[/li][li] [/li][li] if (response.getResponseCode() == 200 && response2.getResponseCode() == 200)[/li][li] {[/li][li] var contentText = response.getContentText();[/li][li] var conditions = JSON.parse(contentText);[/li][li] [/li][li] if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && typeof(conditions.response) !== "undefined" && conditions.response !== undefined && [/li][li] conditions.response !== null && typeof(conditions.response.ob) !== "undefined" && conditions.response.ob !== undefined && conditions.response.ob !== null)[/li][li] {[/li][li] // Also forecast[/li][li] var contentText2 = response2.getContentText();[/li][li] var conditions2 = JSON.parse(contentText2);[/li][li] [/li][li] var nTemperatureC = conditions.response.ob.tempC;[/li][li] var nHumidity = conditions.response.ob.humidity;[/li][li] var nPressure = conditions.response.ob.pressureMB;[/li][li] var nWindSpeed = conditions.response.ob.windKPH;[/li][li] var nCloudCover = conditions.response.ob.sky;[/li][li] var nLightPercentage = conditions.response.ob.light;[/li][li] var sCodedWeather = conditions.response.ob.weatherCoded;[/li][li] [/li][li] if (typeof(conditions2) !== "undefined" && conditions2 !== undefined && conditions2 !== null && conditions2.response[0] !== undefined && conditions2.response[0] !== null)[/li][li] {[/li][li] var nMinTemperatureC = conditions2.response[0].periods[0].minTempC;[/li][li] var nMaxTemperatureC = conditions2.response[0].periods[0].maxTempC;[/li][li] var nRainMMForecast = conditions2.response[0].periods[0].precipMM;[/li][li] var nPoPForecast = conditions2.response[0].periods[0].pop;[/li][li] [/li][li] // Convert weather code to our normal icon codes[/li][li] var nConvertedWeatherCode = convertAerisWeatherCode(sCodedWeather);[/li][li] [/li][li] // Get observation date/time[/li][li] var dateObservation = new Date(conditions.response.ob.timestamp*1000); [/li][li] [/li][li] // Convert observation date-time into our Zipabox date-time format (hours)[/li][li] var nObservationDateTime = dateObservation.valueOf() / 1000 / 60 / 60;[/li][li] [/li][li] // Get wind chill temp[/li][li] var sWindChillTemp = conditions.response.ob.windchillC;[/li][li] [/li][li] // Get sunrise & sunset date/times[/li][li] var dateSunrise = new Date(conditions.response.ob.sunrise*1000); [/li][li] var dateSunset = new Date(conditions.response.ob.sunset*1000); [/li][li] [/li][li] // Create sunrise/sunset time values (seconds since midnight)[/li][li] // var nSunriseTime = (dateSunrise.valueOf() - dateTodayMidnight.valueOf()) / 1000; [/li][li] // var nSunsetTime = (dateSunset.valueOf() - dateTodayMidnight.valueOf()) / 1000; [/li][li] var nSunriseTime = ExtractTimeValueInSecondsFromDate(dateSunrise); [/li][li] var nSunsetTime = ExtractTimeValueInSecondsFromDate(dateSunset); [/li][li] [/li][li] const sZIPAERIS_SUFFIX = "XXXXXXXXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXXXX";[/li][li] [/li][li] // Send data to virtual meter[/li][li] if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIPAERIS_SUFFIX[/li][li] +"&value1=" + parseInt(nTemperatureC) [/li][li] +"&value9=" + parseInt(nHumidity)[/li][li] +"&value8=" + parseInt(nPressure)[/li][li] +"&value7=" + parseInt(nWindSpeed)[/li][li] +"&value6=" + parseInt(nCloudCover)[/li][li] +"&value5=" + parseInt(nLightPercentage)[/li][li] +"&value4=" + parseInt(nConvertedWeatherCode)[/li][li] +"&value3=" + parseInt(nSunriseTime)[/li][li] +"&value2=" + parseInt(nSunsetTime)[/li][li] +"&value16=" + parseFloat(nObservationDateTime)[/li][li] +"&value15=" + parseInt(nMinTemperatureC)[/li][li] +"&value14=" + parseInt(nMaxTemperatureC)[/li][li] +"&value13=" + parseFloat(nRainMMForecast)[/li][li] +"&value12=" + parseInt(nPoPForecast)[/li][li] +"&value10=" + parseInt(sWindChillTemp)[/li][li] , options).getResponseCode() == 200)[/li][li] {[/li][li] bAerisDataSentOK = true;[/li][li] }[/li][li] else[/li][li] {[/li][li] nRecentFailuresZP = nRecentFailuresZP + 1;[/li][li] } [/li][li] }[/li][li] } [/li][li] }[/li][li] }[/li][li] else[/li][li] {[/li][li] bAerisDataSentOK = true;[/li][li] [/li][li] if (nRecentFailuresZP > nSkippedCallsZP)[/li][li] {[/li][li] nSkippedCallsZP = nSkippedCallsZP + 1;[/li][li] }[/li][li] }[/li][li] [/li][li] return bAerisDataSentOK;[/li][li]}[/li][/co]

Replies (7)

photo
2

David,


Thanks a lot for sharing this amazing work with the community, as I already said, you rock. I will digest all this and adapt to my needs. Four thumbs up (hands and feet!!)

photo
2

Well, what to say. David, you are a key player...a nearly 1500 lines script just to have a reliable weather information together with god knows how many rules is definitely not a beginner solution :)

I have to say this is an epic effort.

photo
1

Many thanks folks. Of course it was built up piece by piece over time and grew into a monster!


Ask if there's anything that's not clear.

photo
1

I forgot something. The script uses a Google Sheet to record failures when calling some error-prone APIs, such as AirVisual and the Zipabox itself. I do this to prevent many calls being made to APIs that have become unresponsive for some reason, because the long timeouts end up chewing up your entire allowance under Google App Script for the day, which then knocks out your entire system. So I record the failures and, the more consecutive failures are recorded, the more calls are skipped. So when a particular API goes down for several hours, the script will only try a few times and then leave it alone for a while.

My script is set up to run every ten minutes.

Those consensus weather rules are also run every ten minutes.


Here's a screenshot of the Google Sheet. You can easily extend it to add more APIs if needed.

photo
2

wow, thanks david. And couldn't have come at a better time. I just found out that Zipato use Weather Underground, and this means that not all locations are supported and accurate as it relies on Underground community to update them. Our location has not been updated since start of February and the sunset/sunrise values are out by hours.

I have been told the best approach is to use your closet major city.

Brings us back to maybe Netamo Weatherstation support then or Google/Apple weather options. Or 1500 lines of code. :-)

photo
1

Truth Adrian: "Our location has not been updated since start of February and the sunset/sunrise values are out by hours". In my case it Zipato virtual weatherstation says 18h 16m is sunset today... (North of Spain). When the truth is 21h 14m.... Nothing but accurate

photo
1

By splitting up the script into small functions by API it should make it much easier to just use the API you need.

photo
1

I should add that, by using the Google Sheet method, I haven't had any warnings about " using too much computer time for one day" from Google App Script for months and months. So it really is an effective method.

photo
1

How to add / integrate the API in Zipato?


Como se añade / integra la API en Zipato?

Replies have been locked on this page!