var bIsCloseToSunriseOrSunset = false; var nRecentFailuresZP = 0; var nSkippedCallsZP = 0; function doGet(e) { Logger.log("doGet GetWeatherData"); return doPost(e); } function doPost(e) { Logger.log("doPost GetWeatherData"); return GetWeatherData(); }; // Send response with code/message function sendResponse(code, message) { return { status: { code: code, message: message }, payload: { } }; } function GetWeatherData() { //Logger.log("GetWeatherData"); // var lock = LockService.getPublicLock(); // lock.waitLock(30000); // wait 30 seconds before conceding defeat. const sZIPABOX_REMOTING_PREFIX = "https://my.zipato.com/zipato-web/remoting/attribute/set?serial=XXXXXZIPABOXSERIALXXXXXXX&apiKey="; const sZIP_LASTSENSORUPDATE_SUFFIX = "XXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXX"; const sZIP_DATETIMEMETER_SUFFIX = "XXXXXXXXXXXXXXXep=XXXXXXXXXXXXX"; var bOWMDataSentOK = false; var bWundergroundDataSentOK = false; var bDarkSkyDataSentOK = false; var bAirVisualDataSentOK = false; var bWeather2DataSentOK = false; var bAerisDataSentOK = false; var bZipaboxDataSentOK = false; // Prepare standard HTTP POST stuff var options = { "contentType" : "text/plain", "method" : "post" }; // Basic time info var nMinuteInMilliseconds = 60*1000; var nHourInMilliseconds = 60*60*1000; var nDayInMilliseconds = 60*60*24*1000; // Get current date/time var dateNow = new Date(); //var nYear = dateNow.getFullYear(); //var nMonth = dateNow.getMonth() + 1; //var nDay = dateNow.getDate(); var nHour = dateNow.getHours(); var nMinute = dateNow.getMinutes(); // Create date-time value for the Zipabox (number of hours since 1 January 1970) var nZipaboxDateTime = dateNow.valueOf() / 1000 / 60 / 60; var bOddMinutes = true; var bCallZipabox = false; var bCallAirVisual = false; var bCallWunderground = false; var bCallOWM = false; var bCallWeather2 = false; var bCallAeris = false; var bCallDarkSky = false; var bIsEarlyHours = false; /*var nRainMMNow = 0; var nPoPForecast = 0; var nRainMMForecast = 0; var sWindChillTemp = '-100';*/ // Global var (re)initialisations, just in case bIsCloseToSunriseOrSunset = false; nRecentFailuresZP = 0; nSkippedCallsZP = 0; // For debugging purposes only, declare local equivalents of the global variables var bIsCloseToSunriseOrSunsetLocal = false; var nRecentFailuresZPLocal = 0; var nSkippedCallsZPLocal = 0; // Take minutes and divide by ten, removing fractions var nMinuteTens = Math.floor(nMinute / 10); if (nMinuteTens % 2 == 0) { bOddMinutes = false; } // If it's the early hours of the morning, also restrict the call frequency if (nHour > 2 && nHour < 7) { bIsEarlyHours = true; } else { bCallWunderground = true; // Wunderground is called very 10 minutes except during the early hours, when it's called once every 20min } if (bOddMinutes) { // Call OWM and Weather2 once every 20min, except during the early hours, when we just call them once per 30min if (!bIsEarlyHours) { bCallOWM = true; bCallWeather2 = true; } bCallWunderground = true; // Wunderground is called every 10 minutes except during the early hours, when it's called once every 20min } else { bCallDarkSky = true; bCallAeris = true; } // Restrict Zipabox & AirVisual calls to 2 per hour if (nMinuteTens == 0 || nMinuteTens == 3) { bCallZipabox = true; bCallAirVisual = true; // Call OWM and Weather2 once every 20min, except during the early hours, when we just call them once per 30min if (bIsEarlyHours) { bCallOWM = true; bCallWeather2 = true; } } // 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 var doc = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/XXXXXXXXXSHEETIDXXXXXXXXXXXXX/edit'); var sheet = doc.getSheets()[0]; // First check if we have had many Zipabox failures recently nRecentFailuresZP = sheet.getRange(2,4,2).getCell(1,1).getValue(); nSkippedCallsZP = sheet.getRange(2,4,2).getCell(2,1).getValue(); // Copy to local vars so we can see them in the watch window nRecentFailuresZPLocal = nRecentFailuresZP; nSkippedCallsZPLocal = nSkippedCallsZP; // Reset skipped calls if over 5. We retry at least once per hour if (nSkippedCallsZP > 5) { nSkippedCallsZP = 0; } // Copy to local vars so we can see them in the watch window nSkippedCallsZPLocal = nSkippedCallsZP; // The more failures, the more calls we skip if (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0) { nSkippedCallsZP = 0; // Create time value (seconds since midnight) var nZipaboxTimeValue = ExtractTimeValueInSecondsFromDate(dateNow); //var dateTodayMidnight = new Date(dateNow.getFullYear(), dateNow.getMonth(), dateNow.getDate(), 0, 0, 0); //var nZipaboxTimeValue = (dateNow.valueOf() - dateTodayMidnight.valueOf()) / 1000; // Send values to Zipabox date-time meter var resultZipaboxDateTime = UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_DATETIMEMETER_SUFFIX+ "&value9=" + parseFloat(nZipaboxDateTime) + "&value1=" + parseInt(nZipaboxTimeValue), options); if (resultZipaboxDateTime.getResponseCode() == 200) { nRecentFailuresZP = 0; } else { nRecentFailuresZP = nRecentFailuresZP + 1; } } else { nSkippedCallsZP = nSkippedCallsZP + 1; } // Copy to local vars so we can see them in the watch window nRecentFailuresZPLocal = nRecentFailuresZP; nSkippedCallsZPLocal = nSkippedCallsZP; // OpenWeatherMap bOWMDataSentOK = CallOWMWeatherAPI(bCallOWM, nDayInMilliseconds, nHourInMilliseconds, nMinuteInMilliseconds, options,sZIPABOX_REMOTING_PREFIX); // Zipabox (just to get last synch time) bZipaboxDataSentOK = CallZipaboxWeatherAPI(bCallZipabox, sZIPABOX_REMOTING_PREFIX, sZIP_LASTSENSORUPDATE_SUFFIX, options); // Wunderground bWundergroundDataSentOK = CallWundergroundWeatherAPI(bCallWunderground, sZIPABOX_REMOTING_PREFIX, sZIP_DATETIMEMETER_SUFFIX, options, nDayInMilliseconds, nHourInMilliseconds, nMinuteInMilliseconds); // DarkSky bDarkSkyDataSentOK = CallDarkSkyWeatherAPI(bCallDarkSky, sZIPABOX_REMOTING_PREFIX, options, nDayInMilliseconds, nMinuteInMilliseconds); // AirVisual bAirVisualDataSentOK = CallAirVisualWeatherAPI(bCallAirVisual, sZIPABOX_REMOTING_PREFIX, options, sheet, nDayInMilliseconds, nMinuteInMilliseconds); // Weather2 bWeather2DataSentOK = CallWeather2WeatherAPI(bCallWeather2, sZIPABOX_REMOTING_PREFIX, options, sheet, nZipaboxDateTime); // Copy to local vars so we can see them in the watch window nRecentFailuresZPLocal = nRecentFailuresZP; nSkippedCallsZPLocal = nSkippedCallsZP; bIsCloseToSunriseOrSunsetLocal = bIsCloseToSunriseOrSunset; // If we are close to sunrise or sunset, call Aeris unless we have recent failures, so we get the light% value if (bIsCloseToSunriseOrSunset) { bCallAeris = true; } // Aeris bAerisDataSentOK = CallAerisWeatherAPI(bCallAeris, sZIPABOX_REMOTING_PREFIX, options, sheet); // Update the spreadsheet with Zipabox failures sheet.getRange(2,4,2).getCell(1,1).setValue(nRecentFailuresZP); sheet.getRange(2,4,2).getCell(2,1).setValue(nSkippedCallsZP); // lock.releaseLock(); var response; if (bOWMDataSentOK && bWundergroundDataSentOK && bDarkSkyDataSentOK && bAirVisualDataSentOK && bWeather2DataSentOK && bAerisDataSentOK) { //Logger.log("GetWeatherData completed successfully."); response = sendResponse("200", "Function succeeded.") } else { //Logger.log("GetWeatherData completed but with errors."); response = sendResponse("200", "Function did not completely succeed.") } return ContentService.createTextOutput(JSON.stringify(response)) .setMimeType(ContentService.MimeType.JSON); } function convertAerisWeatherCode(sCodedWeather) { var nIcon = 0; var arrCodeParts = sCodedWeather.split(":"); if (arrCodeParts.length == 3) { var sCoverage = arrCodeParts[0]; var sIntensity = arrCodeParts[1]; var sWeather = arrCodeParts[2]; // Weather: /* Cloud codes: CL Clear Cloud coverage is 0-7% of the sky. FW Fair/Mostly sunny Cloud coverage is 7-32% of the sky. SC Partly cloudy Cloud coverage is 32-70% of the sky. BK Mostly Cloudy Cloud coverage is 70-95% of the sky. OV Cloudy/Overcast Cloud coverage is 95-100% of the sky. Weather codes: A Hail BD Blowing dust BN Blowing sand BR Mist BS Blowing snow BY Blowing spray F Fog FR Frost H Haze IC Ice crystals IF Ice fog IP Ice pellets / Sleet K Smoke L Drizzle R Rain RW Rain showers RS Rain/snow mix SI Snow/sleet mix WM Wintry mix (snow, sleet, rain) S Snow SW Snow showers T Thunderstorms UP Unknown precipitation May occur in an automated observation station, which cannot determine the precipitation type falling. VA Volcanic ash WP Waterspouts ZF Freezing fog ZL Freezing drizzle ZR Freezing rain ZY Freezing spray Intensity codes: VL Very light L Light H Heavy VH Very heavy Coverage codes: AR Areas of BR Brief C Chance of D Definite FQ Frequent IN Intermittent IS Isolated L Likely NM Numerous O Occasional PA Patchy PD Periods of S Slight chance SC Scattered VC In the vicinity/Nearby WD Widespread */ switch (sWeather) { case 'CL': nIcon = 1; break; case 'FW': nIcon = 2; break; case 'FR': case 'IC': case 'IF': { nIcon = 3; break; // Icy } case 'K': nIcon = 4; break; case 'BD': nIcon = 5; break; // Dusty case 'H': nIcon = 6; break; case 'SC': nIcon = 7; break; case 'BK': nIcon = 8; break; case 'L': case 'RW': case 'ZL': { nIcon = 10; break; // Showers } case 'BR': nIcon = 11; break; case 'R': case 'ZR': { nIcon = 12; break; // Rain } case 'BS': case 'S': case 'SW': { nIcon = 14; break; // Snow } case 'OV': nIcon = 15; break; case 'IP': case 'RS': case 'SI': case 'WM': { nIcon = 16; break; // Rain/snow/sleet } case 'F': case 'ZF': { nIcon = 17; break; // Fog } case 'A': nIcon = 20; break; // Storm case 'T': nIcon = 21; break; } } return parseInt(nIcon); } function convertWeather2WeatherCode(nWeather2Code) { var nIcon = -1; switch (nWeather2Code) { case 0: nIcon = 1; break; // Sunny case 1: nIcon = 7; break; // Partly cloudy case 2: nIcon = 8; break; // Cloudy case 3: nIcon = 15; break; // Overcast case 10: nIcon = 11; break; // Mist case 21: nIcon = 9; break; // Patchy rain possible case 22: nIcon = 13; break; // Patchy snow possible case 23: nIcon = 16; break; // Patchy sleet possible case 24: nIcon = 16; break; // Freezing drizzle possible case 29: nIcon = 21; break; // Thundery outbreaks possible case 38: nIcon = 14; break; // Blowing snow case 39: nIcon = 14; break; // Blizzard case 45: nIcon = 17; break; // Fog case 49: nIcon = 17; break; // Freezing fog case 50: nIcon = 10; break; // Patchy light drizzle case 51: nIcon = 10; break; // Light drizzle case 56: nIcon = 16; break; // Freezing drizzle case 57: nIcon = 16; break; // Heavy freezing drizzle case 60: nIcon = 10; break; // Patchy light rain case 61: nIcon = 10; break; // Light rain case 62: nIcon = 12; break; // Moderate rain at times case 63: nIcon = 12; break; // Moderate rain case 64: nIcon = 12; break; // Heavy rain at times case 65: nIcon = 12; break; // Heavy rain case 66: nIcon = 10; break; // Light freezing rain case 67: nIcon = 12; break; // Moderate or heavy freezing rain case 68: nIcon = 16; break; // Light sleet case 69: nIcon = 16; break; // Moderate or heavy sleet case 70: case 71: case 72: case 73: case 74: case 75: { nIcon = 14; break; // Various degrees of snow } case 79: nIcon = 3; break; // Ice pellets case 80: nIcon = 10; break; // Light rain shower case 81: nIcon = 12; break; // Moderate or heavy rain shower case 82: nIcon = 20; break; // Torrential rain shower case 83: nIcon = 16; break; // Light sleet showers case 84: nIcon = 16; break; // Moderate or heavy sleet showers case 85: nIcon = 14; break; // Light snow showers case 86: nIcon = 14; break; // Moderate or heavy snow showers case 87: nIcon = 3; break; // Light showers of ice pellets case 88: nIcon = 3; break; // Moderate or Heavy showers of ice pellets case 91: case 92: case 93: case 94: { nIcon = 21; break; // Various degrees of thunderstorm with rain or snow } } return parseInt(nIcon); }; // From https://code.google.com/p/google-apps-script-issues/issues/detail?id=106 /** * make a hex sha1 string * @param {string} content some content * @return {string} the hex result */ function makeSha1Hex (content) { return byteToHexString(Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_1, content)); } /** * convert an array of bytes to a hex string * @param {Array.byte} bytes the byte array to convert * @return {string} the hex encoded string */ function byteToHexString (bytes) { return bytes.reduce(function (p,c) { return p += padLeading ((c < 0 ? c+256 : c).toString(16), 2 ); },''); } /** * pad leading part of string * @param {string} stringtoPad the source string * @param {number} targetLength what the final string length should be * @param {string} padWith optional what to pad with - default "0" * @return {string} the padded string */ function padLeading (stringtoPad , targetLength , padWith) { return (stringtoPad.length < targetLength ? Array(1+targetLength-stringtoPad.length).join(padWith | "0") : "" ) + stringtoPad ; } /** * get base64 encoded data as a string * @param {string} b64 as a string * @return {string} decoded as as string */ function b64ToString ( b64) { return Utilities.newBlob(Utilities.base64Decode(result.content)).getDataAsString(); } function IsCloseToSunriseOrSunset(dateSunrise, dateSunset, nObservationDateTimeInHours) { // Convert sunrise and sunset into same format as observation time (hours) var nSunriseDateTime = dateSunrise.valueOf() / 1000 / 60 / 60; var nSunsetDateTime = dateSunset.valueOf() / 1000 / 60 / 60; // If before sunrise and not much more than 20 minutes before OR // If after sunrise and not much more than 20 minutes after OR // If before sunset and not much more than 20 minutes before OR // If after sunset and not much more than 20 minutes after if ( (nSunriseDateTime - nObservationDateTimeInHours > 0 && nSunriseDateTime - nObservationDateTimeInHours < 0.4) || (nObservationDateTimeInHours - nSunriseDateTime > 0 && nObservationDateTimeInHours - nSunriseDateTime < 0.4) || (nSunsetDateTime - nObservationDateTimeInHours > 0 && nSunsetDateTime - nObservationDateTimeInHours < 0.4) || (nObservationDateTimeInHours - nSunsetDateTime > 0 && nObservationDateTimeInHours - nSunsetDateTime < 0.4) ) { return true; } else { return false; } } function ExtractTimeValueInSecondsFromDate(dateValue) { var dateNow = new Date(); // Create time value (seconds since midnight) var dateTodayMidnight = new Date(dateNow.getFullYear(), dateNow.getMonth(), dateNow.getDate(), 0, 0, 0); var nTimeValue = (dateValue.valueOf() - dateTodayMidnight.valueOf()) / 1000; return nTimeValue; } function CallOWMPollutionAPI(sZIPABOX_REMOTING_PREFIX) { // Call openweathermap API for O3, SO2 and NO2 var url = 'http://api.openweathermap.org/pollution/v1/o3/XXXXLATITUDEXXXXX,XXXXXLONGITUDEXXXXXXX/current.json?appid=XXXXXXXXXXXXAPIKEYXXXXXXXXXXXXX' var responseO3 = UrlFetchApp.fetch(url); // Apparently these are hardly ever updated //var url = 'http://api.openweathermap.org/pollution/v1/no2/40.38,-3.7/current.json?appid=6312d867d94d33fedff8512925b86d77' //var responseNO2 = UrlFetchApp.fetch(url); //var url = 'http://api.openweathermap.org/pollution/v1/so2/40.38,-3.7/current.json?appid=6312d867d94d33fedff8512925b86d77' //var responseSO2 = UrlFetchApp.fetch(url); if (responseO3.getResponseCode() == 200) { var contentText = response.getContentText(); var conditions = JSON.parse(contentText); if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.main !== undefined && conditions.main !== null && conditions.cod != "404") { var dateObservation = new Date(conditions.time); var nDobsons = conditions.data; // Convert observation date-time into our Zipabox date-time format (hours) var nObservationDateTime = dateObservation.valueOf() / 1000 / 60 / 60; const sZIP_OWM_SUFFIX = "XXXXXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXXXXX"; var sSuffixes = "&value10=" + parseFloat(nDobsons) + "&value11=" + parseFloat(nObservationDateTime); if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_OWM_SUFFIX+sSuffixes, options).getResponseCode() == 200) { return true; } } } return false; } function CallOWMWeatherAPI(bCallOWM, nDayInMilliseconds, nHourInMilliseconds, nMinuteInMilliseconds, options, sZIPABOX_REMOTING_PREFIX) { var bOWMDataSentOK = false; // Do OWM every second call if (bCallOWM && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0)) { // Call openweathermap API var url = 'http://api.openweathermap.org/data/2.5/weather?id=XXXXXLOCATIONIDXXXX&appid=XXXXXXXXAPIKEYXXXXXXX&units=metric' var response = UrlFetchApp.fetch(url); // var nResponseCode = response.getResponseCode(); if (response.getResponseCode() == 200) { var contentText = response.getContentText(); var conditions = JSON.parse(contentText); if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.main !== undefined && conditions.main !== null && conditions.cod != "404") { var nCloudCover = conditions.clouds.all; var nTemperature = conditions.main.temp; var nMinTemperatureC = conditions.main.temp_min; var nMaxTemperatureC = conditions.main.temp_max; var nHumidity = conditions.main.humidity; var nWindSpeed = conditions.wind.speed*3600/1000; var nSunriseDateTime = conditions.sys.sunrise; var nSunsetDateTime = conditions.sys.sunset; var sIcon = conditions.weather[0].icon; // Get hour/minute of sunset var dateSunset = new Date(conditions.sys.sunset*1000); var nHourSunset = dateSunset.getHours(); var nMinuteSunset = dateSunset.getMinutes(); // Get hour/minute of sunrise var dateSunrise = new Date(conditions.sys.sunrise*1000); var nHourSunrise = dateSunrise.getHours(); var nMinuteSunrise = dateSunrise.getMinutes(); // Get sunrise/sunset times var nSunriseTime = ExtractTimeValueInSecondsFromDate(dateSunrise); var nSunsetTime = ExtractTimeValueInSecondsFromDate(dateSunset); // Refresh current date-time var dateNow = new Date(); // Get date-time of observation var date = new Date(conditions.dt*1000); // Compare to current date var nHoursDifference = Math.floor(((dateNow - date) % nDayInMilliseconds) / nHourInMilliseconds); var nMinutesDifference = Math.floor(((dateNow - date) % nDayInMilliseconds) / nMinuteInMilliseconds); // Convert observation date-time into our Zipabox date-time format (hours) var nObservationDateTime = date.valueOf() / 1000 / 60 / 60; // Work out whether we are close to sunrise or sunset bIsCloseToSunriseOrSunset = IsCloseToSunriseOrSunset(dateSunrise, dateSunset, nObservationDateTime); // Convert string icon name into number var nNumericIconOWM = 0; switch (sIcon) { // Clear sky case '01d': case '01n': { nNumericIconOWM = 1; break; } // Few clouds case '02d': case '02n': { nNumericIconOWM = 2; break; } // Scattered clouds case '03d': case '03n': { nNumericIconOWM = 7; break; } // Broken clouds case '04d': case '04n': { nNumericIconOWM = 8; break; } // Shower rain case '09d': case '09n': { nNumericIconOWM = 10; break; } // Mist case '50d': case '50n': { nNumericIconOWM = 11; break; } // Rain case '10d': case '10n': { nNumericIconOWM = 12; break; } // Snow case '13d': case '13n': { nNumericIconOWM = 14; break; } // Thunderstorm case '11d': case '11n': { nNumericIconOWM = 21; break; } } const sZIP_OWM_SUFFIX = "XXXXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXX"; var sSuffixes = "&value1=" + parseInt(nCloudCover) + "&value9=" + parseFloat(nTemperature) + "&value6=" + parseInt(nMinTemperatureC) + "&value7=" + parseInt(nMaxTemperatureC) + "&value5=" + parseFloat(nWindSpeed) + /*"&value4=" + parseInt(nHourSunrise) + "&value3=" + parseInt(nMinuteSunrise) + "&value2=" + parseInt(nHourSunset) + "&value16=" + parseInt(nMinuteSunset) + */ "&value4=" + parseInt(nSunriseTime) + "&value2=" + parseInt(nSunsetTime) + "&value8=" + parseInt(nHoursDifference) + "&value15=" + parseInt(nNumericIconOWM) + "&value14=" + parseInt(nHumidity) + /*"&value13=" + parseFloat(nZipaboxDateTime) +*/ "&value12=" + parseFloat(nObservationDateTime); if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_OWM_SUFFIX+sSuffixes, options).getResponseCode() == 200) { bOWMDataSentOK = true; // Also try pollution API // Don't bother now, it's hardly ever updated //var bCalledOK = CallOWMPollutionAPI(sZIPABOX_REMOTING_PREFIX); } else { nRecentFailuresZP++; } } } } else { bOWMDataSentOK = true; if (nRecentFailuresZP > nSkippedCallsZP) { nSkippedCallsZP = nSkippedCallsZP + 1; } } return bOWMDataSentOK; } function CallZipaboxWeatherAPI(bCallZipabox, sZIPABOX_REMOTING_PREFIX, sZIP_LASTSENSORUPDATE_SUFFIX, options) { var bZipaboxDataSentOK = false; if (bCallZipabox && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0)) { // Call Zipato weather API // Need to get "nonce" and session ID first var response = UrlFetchApp.fetch("https://my.zipato.com:443/zipato-web/v2/user/init"); if (response.getResponseCode() == 200) { var contentText = response.getContentText(); var zipaboxdata = JSON.parse(contentText); if (typeof(zipaboxdata) !== "undefined" && zipaboxdata !== undefined && zipaboxdata !== null && zipaboxdata.nonce !== undefined && zipaboxdata.nonce !== null) { var sNonce = zipaboxdata.nonce; var sSessionID = zipaboxdata.jsessionid; // Make token var sToken = makeSha1Hex(sNonce + makeSha1Hex("XXXXXXZIPABOXPASSWORDXXXXXXX")); var headersZipabox = { 'Cookie':"JSESSIONID="+sSessionID }; var optionsZipabox = { 'headers' : headersZipabox }; // Now log in response = UrlFetchApp.fetch("https://my.zipato.com:443/zipato-web/v2/user/login?username=XXXXXXXXZIPABOXUSERNAMEXXXXXXXXXXXXXXXXX&token=" + sToken + "&serial=XXXXZIPABOXSERIALXXXXXXXXXX", optionsZipabox); if (response.getResponseCode() == 200) { var contentText = response.getContentText(); // Now get weather data response = UrlFetchApp.fetch("https://my.zipato.com:443/zipato-web/v2/meteo/weather?location=XXXXWEATHERSTATIONCODEXXXXX", optionsZipabox); if (response.getResponseCode() == 200) { var contentText = response.getContentText(); var conditions = JSON.parse(response.getContentText()); if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.conditions !== undefined && conditions.conditions !== null) { var sObservationDate = conditions.conditions.observation_epoch; // Trim off Z from end sObservationDate = sObservationDate.slice(0, -1); // Add milliseconds and Z sObservationDate = sObservationDate + ".000Z"; // Make into date var dateObservation = new Date(sObservationDate); // Convert observation date-time into our Zipabox date-time format (hours) var nObservationDateTime = dateObservation.valueOf() / 1000 / 60 / 60; var sSuffixes = "&value12=" + parseFloat(nObservationDateTime); if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_LASTSENSORUPDATE_SUFFIX+sSuffixes, options).getResponseCode() == 200) { bZipaboxDataSentOK = true; } else { nRecentFailuresZP = nRecentFailuresZP + 1; } } } } } } } else { bZipaboxDataSentOK = true; if (nRecentFailuresZP > nSkippedCallsZP) { nSkippedCallsZP = nSkippedCallsZP + 1; } } return bZipaboxDataSentOK; } function CallWundergroundWeatherAPI(bCallWunderground, sZIPABOX_REMOTING_PREFIX, sZIP_DATETIMEMETER_SUFFIX, options, nDayInMilliseconds, nHourInMilliseconds, nMinuteInMilliseconds) { var bWundergroundDataSentOK = false; if (bCallWunderground && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0)) { // Call Wunderground API var url = 'http://api.wunderground.com/api/XXXXXXAPIKEYXXXXXXX/conditions/forecast/alert/q/XXXXXXXLATITUDEXXXXXXXXXX,XXXXXXLONGITUDEXXXXXXXX.json'; var response = UrlFetchApp.fetch(url); //var nResponseCode = response.getResponseCode(); if (response.getResponseCode() == 200) { var contentText = response.getContentText(); var conditions = JSON.parse(contentText); if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.current_observation !== undefined && conditions.current_observation !== null && conditions.forecast !== undefined && conditions.forecast !== null && conditions.forecast.simpleforecast !== undefined && conditions.forecast.simpleforecast !== null && conditions.forecast.simpleforecast.forecastday[0] !== undefined && conditions.forecast.simpleforecast.forecastday[0] !== null) { var todaysConditions = conditions; var sIcon = todaysConditions.current_observation.icon; var nIcon = 0; var bSkipRainMMNow = false; var nRainMMNow = 0; var sWindChillTemp = '-100'; switch (sIcon) { case 'clear': nIcon = 1; break; case 'mostlysunny': nIcon = 2; break; case 'icy': nIcon = 3; break; case 'smoke': nIcon = 4; break; case 'dusty': nIcon = 5; break; case 'hazy': nIcon = 6; break; case 'partlycloudy': nIcon = 7; break; case 'mostlycloudy': nIcon = 8; break; case 'chancerain': nIcon = 9; break; case 'showers': nIcon = 10; break; case 'mist': nIcon = 11; break; case 'rain': nIcon = 12; break; case 'chancesnow': nIcon = 13; break; case 'snow': nIcon = 14; break; case 'cloudy': nIcon = 15; break; case 'rain_snow': nIcon = 16; break; case 'fog': nIcon = 17; break; case 'foggy': nIcon = 18; break; case 'chancestorm': nIcon = 19; break; case 'storm': nIcon = 20; break; case 'thunderstorm': nIcon = 21; break; } // Get current rain in mm now if (todaysConditions.current_observation.precip_today_metric == "--") { nRainMMNow = -1; } else { nRainMMNow = parseFloat(todaysConditions.current_observation.precip_today_metric); } // Get rain in mm and probability of precipitation forecast for today var nRainMMForecast = todaysConditions.forecast.simpleforecast.forecastday[0].qpf_day.mm; var nPoPForecast = todaysConditions.forecast.simpleforecast.forecastday[0].pop; if (nRainMMForecast == null) { nRainMMForecast = -1; } // Get current temperature (C) var nTemperatureC = todaysConditions.current_observation.temp_c; // Get min temperature (C) var nMinTemperatureC = todaysConditions.forecast.simpleforecast.forecastday[0].low.celsius; // Get max temperature (C) var nMaxTemperatureC = todaysConditions.forecast.simpleforecast.forecastday[0].high.celsius; // Get wind speed & humidity var nWindSpeed = todaysConditions.current_observation.wind_kph; var sHumidity = todaysConditions.current_observation.relative_humidity; sHumidity.replace('%', ''); // Get wind chill temperature if (todaysConditions.current_observation.windchill_c != 'NA' && todaysConditions.current_observation.windchill_c != null) { sWindChillTemp = todaysConditions.current_observation.windchill_c; } else if (todaysConditions.current_observation.feelslike_c != 'NA' && todaysConditions.current_observation.feelslike_c != null) { sWindChillTemp = todaysConditions.current_observation.feelslike_c; } // Refresh current date-time var dateNow = new Date(); // Get observation date/time var dateWundergroundObservation = new Date(todaysConditions.current_observation.observation_epoch*1000); // Convert observation date-time into our Zipabox date-time format (hours) var nObservationDateTime = dateWundergroundObservation.valueOf() / 1000 / 60 / 60; // Compare to current date var nHoursDifferenceW = Math.floor(((dateNow - dateWundergroundObservation) % nDayInMilliseconds) / nHourInMilliseconds); var nMinutesDifferenceW = Math.floor(((dateNow - dateWundergroundObservation) % nDayInMilliseconds) / nMinuteInMilliseconds); // Get UTC offset (daylight savings time = +2; otherwise +1) var sUTCOffset = todaysConditions.current_observation.local_tz_offset; var nUTCOffset = 0; if (sUTCOffset == "+0100") { nUTCOffset = 1; } else { nUTCOffset = 2; } var resultZipaboxDateTime = UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_DATETIMEMETER_SUFFIX+ "&value8=" + parseInt(nUTCOffset), options); const sZIP_WUNDERGROUND_SUFFIX = "XXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXXX"; var sSuffix = "&value1=" + parseInt(nIcon) +"&value9=" + parseFloat(nRainMMForecast) +"&value8=" + parseInt(nPoPForecast) +"&value7=" + nRainMMNow +"&value6=" + parseInt(nTemperatureC) +"&value3=" + parseInt(nMinTemperatureC) +"&value4=" + parseInt(nMaxTemperatureC) +"&value2=" + parseInt(nWindSpeed) /* Observation age in hours */ +"&value5=" + parseInt(nHoursDifferenceW) +"&value16=" + parseInt(sHumidity) +"&value14=" + parseFloat(nObservationDateTime) +"&value13=" + parseInt(sWindChillTemp); /* Send icon to Zipabox virtual meter*/ if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_WUNDERGROUND_SUFFIX + sSuffix, options).getResponseCode() == 200) { bWundergroundDataSentOK = true; } else { nRecentFailuresZP = nRecentFailuresZP + 1; } } } } else { bWundergroundDataSentOK = true; if (nRecentFailuresZP > nSkippedCallsZP) { nSkippedCallsZP = nSkippedCallsZP + 1; } } return bWundergroundDataSentOK; } function CallDarkSkyWeatherAPI(bCallDarkSky, sZIPABOX_REMOTING_PREFIX, options, nDayInMilliseconds, nMinuteInMilliseconds) { var bDarkSkyDataSentOK = false; // Do DarkSky every second call if (bCallDarkSky && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0)) { // Call DarkSky API var url = 'https://api.darksky.net/forecast/XXXXXXXXXXXXXXXXAPIKEYXXXXXXXXXX/XXXXXXXXLATITUDEXXXXXXXXX,XXXXXXXXXXXLONGITUDEXXXXXXXXX' var response = UrlFetchApp.fetch(url); //var nResponseCode = response.getResponseCode(); if (response.getResponseCode() == 200) { var contentText = response.getContentText(); var conditions = JSON.parse(contentText); if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.currently !== undefined && conditions.currently !== null) { var nTemperatureC = (5/9)*(conditions.currently.temperature-32); var nHumidity = conditions.currently.humidity * 100; var nWindSpeed = conditions.currently.windSpeed * 1.60934; var nCloudCover = conditions.currently.cloudCover * 100; var nDSOzone = conditions.currently.ozone; var nDSPrecipPercent = conditions.currently.precipProbability * 100; var nRainMMNow = conditions.currently.precipIntensity * 25.4; var nDSMoonPhase = conditions.daily.data[0].moonPhase * 4; var nSunriseDateTime = conditions.daily.data[0].sunriseTime; var nSunsetDateTime = conditions.daily.data[0].sunsetTime; var sIcon = conditions.currently.icon; var sWindChillTemp = (5/9)*(conditions.currently.apparentTemperature-32); var nIcon = 0; switch (sIcon) { case 'clear-day': nIcon = 1; break; case 'clear-night': nIcon = 1; break; case 'partly-cloudy-day': nIcon = 7; break; case 'partly-cloudy-night': nIcon = 7; break; case 'cloudy': nIcon = 8; break; case 'rain': nIcon = 12; break; case 'snow': nIcon = 14; break; case 'cloudy': nIcon = 15; break; case 'sleet': nIcon = 16; break; case 'fog': nIcon = 17; break; case 'thunderstorm': nIcon = 21; break; } // Get hour/minute of sunset var dateSunset = new Date(nSunsetDateTime*1000); var nHourSunset = dateSunset.getHours(); var nMinuteSunset = dateSunset.getMinutes(); // Get hour/minute of sunrise var dateSunrise = new Date(nSunriseDateTime*1000); var nHourSunrise = dateSunrise.getHours(); var nMinuteSunrise = dateSunrise.getMinutes(); // Get sunrise/sunset times var nSunriseTime = ExtractTimeValueInSecondsFromDate(dateSunrise); var nSunsetTime = ExtractTimeValueInSecondsFromDate(dateSunset); // Refresh current date-time var dateNow = new Date(); // Get observation date/time var dateObservation = new Date(conditions.currently.time*1000); // Convert observation date-time into our Zipabox date-time format (hours) var nObservationDateTime = dateObservation.valueOf() / 1000 / 60 / 60; // Work out whether we are close to sunrise or sunset bIsCloseToSunriseOrSunset = IsCloseToSunriseOrSunset(dateSunrise, dateSunset, nObservationDateTime); // Compare to current date var nMinutesDifference = Math.floor(((dateNow - dateObservation) % nDayInMilliseconds) / nMinuteInMilliseconds); var dataThisHourlyForecast; var nPoPForecast = 0; var nRainMMForecast = 0; var nThisForecastPrecipProbPercent = 0; var nThisForecastPrecipIntensityMM = 0; var nThisForecastTemperatureC = 0; var nMinTemperatureC = 0; var nMaxTemperatureC = 0; // Go through forecasts and check for rain for (var nIndex in conditions.hourly.data) { // Only check for next 24 hours if (nIndex > 24) { break; } dataThisHourlyForecast = conditions.hourly.data[nIndex]; nThisForecastPrecipProbPercent = dataThisHourlyForecast.precipProbability * 100; nThisForecastPrecipIntensityMM = dataThisHourlyForecast.precipIntensity * 25.4; nThisForecastTemperatureC = (5/9)*(dataThisHourlyForecast.temperature-32); // Initialise the min/max temperatures to real temperatures if (nIndex == 0) { nMinTemperatureC = nThisForecastTemperatureC; nMaxTemperatureC = nThisForecastTemperatureC; } // Store rain if greater than current figure if (nThisForecastPrecipProbPercent > nPoPForecast) { nPoPForecast = nThisForecastPrecipProbPercent; } if (nThisForecastPrecipIntensityMM > nRainMMForecast) { nRainMMForecast = nThisForecastPrecipIntensityMM; } // And check for forecast min/max temperatures if (nThisForecastTemperatureC > nMaxTemperatureC) { nMaxTemperatureC = nThisForecastTemperatureC; } if (nThisForecastTemperatureC < nMinTemperatureC) { nMinTemperatureC = nThisForecastTemperatureC; } } const sZIP_DARKSKY_SUFFIX = "XXXXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXXX"; const sZIP_DARKSKY2_SUFFIX = "XXXXXXXXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXXXXXXX"; if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_DARKSKY_SUFFIX +"&value1=" + parseFloat(nTemperatureC) +"&value9=" + parseInt(nHumidity) +"&value8=" + parseFloat(nWindSpeed) +"&value7=" + parseFloat(nCloudCover) +"&value6=" + parseFloat(nDSOzone) +"&value5=" + parseInt(nPoPForecast) +"&value4=" + parseFloat(nRainMMNow) +"&value3=" + parseFloat(nDSMoonPhase) +"&value2=" + parseInt(nSunriseTime) +"&value15=" + parseInt(nSunsetTime) /* +"&value2=" + parseInt(nHourSunrise) +"&value16=" + parseInt(nMinuteSunrise) +"&value15=" + parseInt(nHourSunset) +"&value14=" + parseInt(nMinuteSunset)*/ +"&value13=" + parseInt(nIcon) +"&value12=" + parseInt(nMinutesDifference) +"&value11=" + parseFloat(nRainMMForecast) +"&value10=" + parseFloat(nObservationDateTime) , options).getResponseCode() == 200 && UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_DARKSKY2_SUFFIX +"&value1=" + parseFloat(nMinTemperatureC) +"&value9=" + parseFloat(nMaxTemperatureC) +"&value8=" + parseFloat(sWindChillTemp) , options).getResponseCode() == 200) { bDarkSkyDataSentOK = true; } else { nRecentFailuresZP = nRecentFailuresZP + 1; } } } } else { bDarkSkyDataSentOK = true; if (nRecentFailuresZP > nSkippedCallsZP) { nSkippedCallsZP = nSkippedCallsZP + 1; } } return bDarkSkyDataSentOK; } function CallAirVisualWeatherAPI(bCallAirVisual, sZIPABOX_REMOTING_PREFIX, options, sheet, nDayInMilliseconds, nMinuteInMilliseconds) { var bAirVisualDataSentOK = false; if (bCallAirVisual && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0)) { // Call AirVisual pollution API // First check if we have had many failures recently var nRecentFailuresAV = sheet.getRange(2,2,2).getCell(1,1).getValue(); var nSkippedCallsAV = sheet.getRange(2,2,2).getCell(2,1).getValue(); // Reset skipped calls if over 5. We retry at least once per hour if (nSkippedCallsAV > 5) { nSkippedCallsAV = 0; } // The more failures, the more calls we skip if (nRecentFailuresAV == nSkippedCallsAV || nRecentFailuresAV == 0) { nSkippedCallsAV = 0; var url = 'http://api.airvisual.com/v1/nearest?lat=XXXXXXXXXLATITUDEXXXXXXXXXXXX&lon=XXXXXXXXLONGITUDEXXXXXXXX&key=XXXXXXXXXXXXXXXXXXXXXAPIKEYXXXXXXXXXXXXXXX' var response = UrlFetchApp.fetch(url); //var nResponseCode = response.getResponseCode(); if (response.getResponseCode() == 200) { var contentText = response.getContentText(); var conditions = JSON.parse(contentText); if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.data !== undefined && conditions.data !== null) { var nUSAQI = conditions.data.current.pollution.aqius; var nChinaAQI = conditions.data.current.pollution.aqicn; var sLastUpdated = conditions.data.current.pollution.ts; var nWindSpeed = conditions.data.current.weather.ws*3600/1000; // Refresh current date-time var dateNow = new Date(); var dateObservation = new Date(sLastUpdated); // Convert observation date-time into our Zipabox date-time format (hours) var nObservationDateTime = dateObservation.valueOf() / 1000 / 60 / 60; // Compare to current date var nMinutesDifference = Math.floor(((dateNow - dateObservation) % nDayInMilliseconds) / nMinuteInMilliseconds); const sZIP_AV_SUFFIX = "XXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXX"; // Send data to virtual meter if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_AV_SUFFIX +"&value1=" + parseInt(nUSAQI) +"&value9=" + parseInt(nChinaAQI) +"&value5=" + parseInt(nMinutesDifference) +"&value4=" + parseFloat(nWindSpeed) /* +"&value3=" + parseFloat(nZipaboxDateTime)*/ +"&value2=" + parseFloat(nObservationDateTime) , options).getResponseCode() == 200) { bAirVisualDataSentOK = true; } else { nRecentFailuresZP = nRecentFailuresZP + 1; } } nRecentFailuresAV = 0; } else { nRecentFailuresAV = nRecentFailuresAV + 1; } } else { nSkippedCallsAV = nSkippedCallsAV + 1; } // Update the spreadsheet sheet.getRange(2,2,2).getCell(1,1).setValue(nRecentFailuresAV); sheet.getRange(2,2,2).getCell(2,1).setValue(nSkippedCallsAV); } else { bAirVisualDataSentOK = true; if (nRecentFailuresZP > nSkippedCallsZP) { nSkippedCallsZP = nSkippedCallsZP + 1; } } return bAirVisualDataSentOK; } function CallWeather2WeatherAPI(bCallWeather2, sZIPABOX_REMOTING_PREFIX, options, sheet, nZipaboxDateTime) { var bWeather2DataSentOK = false; // Do Weather2 every second call if (bCallWeather2 && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0)) { // First check if we have had many failures recently var nRecentFailuresW2 = sheet.getRange(2,3,2).getCell(1,1).getValue(); var nSkippedCallsW2 = sheet.getRange(2,3,2).getCell(2,1).getValue(); // Reset skipped calls if over 5. We retry at least once per hour if (nSkippedCallsW2 > 5) { nSkippedCallsW2 = 0; } // The more failures, the more calls we skip if (nRecentFailuresW2 == nSkippedCallsW2 || nRecentFailuresW2 == 0) { nSkippedCallsW2 = 0; // Call Weather2 API var url = 'http://www.myweather2.com/developer/forecast.ashx?uac=XXXXXXXXAPIKEYXXXXXXXXX&query=XXXXXLATITUDEXXXXXXXX,XXXXXXXLONGITUDEXXXXXXXX&temp_unit=c&output=json' var response = UrlFetchApp.fetch(url); // var nResponseCode = response.getResponseCode(); if (response.getResponseCode() == 200) { var contentText = response.getContentText(); var conditions = JSON.parse(contentText); if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && conditions.weather !== undefined && conditions.weather !== null) { var nHumidity = conditions.weather.curren_weather[0].humidity; var nPressure = conditions.weather.curren_weather[0].pressure; var nWeatherCode = conditions.weather.curren_weather[0].weather_code; var nTemperatureC = conditions.weather.curren_weather[0].temp; var nWindSpeed = conditions.weather.curren_weather[0].wind[0].speed; var nMinTemperatureC = conditions.weather.forecast[0].night_min_temp; var nMaxTemperatureC = conditions.weather.forecast[0].day_max_temp; var nForecastDayCode = conditions.weather.forecast[0].day[0].weather_code; var nForecastNightCode = conditions.weather.forecast[0].night[0].weather_code; // Convert to standard icon values var nConvertedWeatherCode = convertWeather2WeatherCode(parseInt(nWeatherCode)); var nConvertedForecastDayCode = convertWeather2WeatherCode(parseInt(nForecastDayCode)); var nConvertedForecastNightCode = convertWeather2WeatherCode(parseInt(nForecastNightCode)); const sZIP_WEATHER2_SUFFIX = "XXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXX"; // Send data to virtual meter if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIP_WEATHER2_SUFFIX +"&value1=" + parseInt(nHumidity) +"&value9=" + parseInt(nPressure) +"&value8=" + parseInt(nTemperatureC) +"&value7=" + parseInt(nConvertedWeatherCode) +"&value6=" + parseInt(nWindSpeed) +"&value5=" + parseInt(nMinTemperatureC) +"&value4=" + parseInt(nMaxTemperatureC) +"&value3=" + parseInt(nConvertedForecastDayCode) +"&value2=" + parseInt(nConvertedForecastNightCode) +"&value16=" + parseFloat(nZipaboxDateTime) , options).getResponseCode() == 200) { bWeather2DataSentOK = true; } else { nRecentFailuresZP = nRecentFailuresZP + 1; } } nRecentFailuresW2 = 0; } else { nRecentFailuresW2 = nRecentFailuresW2 + 1; } } else { nSkippedCallsW2 = nSkippedCallsW2 + 1; } // Update the spreadsheet sheet.getRange(2,3,2).getCell(1,1).setValue(nRecentFailuresW2); sheet.getRange(2,3,2).getCell(2,1).setValue(nSkippedCallsW2); } else { bWeather2DataSentOK = true; if (nRecentFailuresZP > nSkippedCallsZP) { nSkippedCallsZP = nSkippedCallsZP + 1; } } return bWeather2DataSentOK; } function CallAerisWeatherAPI(bCallAeris, sZIPABOX_REMOTING_PREFIX, options, sheet) { var bAerisDataSentOK = false; // Do Aeris every second call if (bCallAeris && (nRecentFailuresZP <= nSkippedCallsZP || nRecentFailuresZP == 0)) { // Call Aeris API var url = 'http://api.aerisapi.com/observations/XXXXXXXXWEATHERSTATIONCODEXXXXXXXXXXXXXXXXX?client_id=XXXXXXXXCLIENTIDXXXXXXXXXXX&client_secret=XXXXXXXXXXXCLIENTSECRETXXXXXXXXXXXXXXXX' var response = UrlFetchApp.fetch(url); //var nResponseCode = response.getResponseCode(); // Also call forecast API var url2 = 'http://api.aerisapi.com/forecasts/XXXXXXXXWEATHERSTATIONCODEXXXXXXXXXXXXXXXXX?client_id=XXXXXXXXCLIENTIDXXXXXXXXXXX&client_secret=XXXXXXXXXXXCLIENTSECRETXXXXXXXXXXXXXXXX' var response2 = UrlFetchApp.fetch(url2); //var nResponseCode2 = response2.getResponseCode(); if (response.getResponseCode() == 200 && response2.getResponseCode() == 200) { var contentText = response.getContentText(); var conditions = JSON.parse(contentText); if (typeof(conditions) !== "undefined" && conditions !== undefined && conditions !== null && typeof(conditions.response) !== "undefined" && conditions.response !== undefined && conditions.response !== null && typeof(conditions.response.ob) !== "undefined" && conditions.response.ob !== undefined && conditions.response.ob !== null) { // Also forecast var contentText2 = response2.getContentText(); var conditions2 = JSON.parse(contentText2); var nTemperatureC = conditions.response.ob.tempC; var nHumidity = conditions.response.ob.humidity; var nPressure = conditions.response.ob.pressureMB; var nWindSpeed = conditions.response.ob.windKPH; var nCloudCover = conditions.response.ob.sky; var nLightPercentage = conditions.response.ob.light; var sCodedWeather = conditions.response.ob.weatherCoded; if (typeof(conditions2) !== "undefined" && conditions2 !== undefined && conditions2 !== null && conditions2.response[0] !== undefined && conditions2.response[0] !== null) { var nMinTemperatureC = conditions2.response[0].periods[0].minTempC; var nMaxTemperatureC = conditions2.response[0].periods[0].maxTempC; var nRainMMForecast = conditions2.response[0].periods[0].precipMM; var nPoPForecast = conditions2.response[0].periods[0].pop; // Convert weather code to our normal icon codes var nConvertedWeatherCode = convertAerisWeatherCode(sCodedWeather); // Get observation date/time var dateObservation = new Date(conditions.response.ob.timestamp*1000); // Convert observation date-time into our Zipabox date-time format (hours) var nObservationDateTime = dateObservation.valueOf() / 1000 / 60 / 60; // Get wind chill temp var sWindChillTemp = conditions.response.ob.windchillC; // Get sunrise & sunset date/times var dateSunrise = new Date(conditions.response.ob.sunrise*1000); var dateSunset = new Date(conditions.response.ob.sunset*1000); // Create sunrise/sunset time values (seconds since midnight) // var nSunriseTime = (dateSunrise.valueOf() - dateTodayMidnight.valueOf()) / 1000; // var nSunsetTime = (dateSunset.valueOf() - dateTodayMidnight.valueOf()) / 1000; var nSunriseTime = ExtractTimeValueInSecondsFromDate(dateSunrise); var nSunsetTime = ExtractTimeValueInSecondsFromDate(dateSunset); const sZIPAERIS_SUFFIX = "XXXXXXXXXXXXXXXXXXXXXXXXX&ep=XXXXXXXXXXXXXXXXXXXXXX"; // Send data to virtual meter if (UrlFetchApp.fetch(sZIPABOX_REMOTING_PREFIX+sZIPAERIS_SUFFIX +"&value1=" + parseInt(nTemperatureC) +"&value9=" + parseInt(nHumidity) +"&value8=" + parseInt(nPressure) +"&value7=" + parseInt(nWindSpeed) +"&value6=" + parseInt(nCloudCover) +"&value5=" + parseInt(nLightPercentage) +"&value4=" + parseInt(nConvertedWeatherCode) +"&value3=" + parseInt(nSunriseTime) +"&value2=" + parseInt(nSunsetTime) +"&value16=" + parseFloat(nObservationDateTime) +"&value15=" + parseInt(nMinTemperatureC) +"&value14=" + parseInt(nMaxTemperatureC) +"&value13=" + parseFloat(nRainMMForecast) +"&value12=" + parseInt(nPoPForecast) +"&value10=" + parseInt(sWindChillTemp) , options).getResponseCode() == 200) { bAerisDataSentOK = true; } else { nRecentFailuresZP = nRecentFailuresZP + 1; } } } } } else { bAerisDataSentOK = true; if (nRecentFailuresZP > nSkippedCallsZP) { nSkippedCallsZP = nSkippedCallsZP + 1; } } return bAerisDataSentOK; }