Add comments to the code
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
( function() {
|
( function() {
|
||||||
|
|
||||||
// Define regex filters to match against location
|
|
||||||
var http = require( "http" ),
|
var http = require( "http" ),
|
||||||
parseXML = require( "xml2js" ).parseString,
|
parseXML = require( "xml2js" ).parseString,
|
||||||
|
|
||||||
|
// Define regex filters to match against location
|
||||||
filters = {
|
filters = {
|
||||||
gps: /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/,
|
gps: /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/,
|
||||||
pws: /^(?:pws|icao):/,
|
pws: /^(?:pws|icao):/,
|
||||||
@@ -71,12 +72,20 @@
|
|||||||
// If location does not match GPS or PWS/ICAO, then attempt to resolve
|
// If location does not match GPS or PWS/ICAO, then attempt to resolve
|
||||||
// location using Weather Underground autocomplete API
|
// location using Weather Underground autocomplete API
|
||||||
function resolveCoordinates( location, callback ) {
|
function resolveCoordinates( location, callback ) {
|
||||||
|
|
||||||
|
// Generate URL for autocomplete request
|
||||||
var url = "http://autocomplete.wunderground.com/aq?h=0&query=" +
|
var url = "http://autocomplete.wunderground.com/aq?h=0&query=" +
|
||||||
encodeURIComponent( location );
|
encodeURIComponent( location );
|
||||||
|
|
||||||
httpRequest( url, function( data ) {
|
httpRequest( url, function( data ) {
|
||||||
|
|
||||||
|
// Parse the reply for JSON data
|
||||||
data = JSON.parse( data );
|
data = JSON.parse( data );
|
||||||
|
|
||||||
|
// Check if the data is valid
|
||||||
if ( typeof data.RESULTS === "object" && data.RESULTS.length ) {
|
if ( typeof data.RESULTS === "object" && data.RESULTS.length ) {
|
||||||
|
|
||||||
|
// If it is, reply with an array containing the GPS coordinates
|
||||||
callback( [ data.RESULTS[0].lat, data.RESULTS[0].lon ] );
|
callback( [ data.RESULTS[0].lat, data.RESULTS[0].lon ] );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
@@ -85,12 +94,15 @@
|
|||||||
// Accepts a time string formatted in ISO-8601 and returns the timezone.
|
// Accepts a time string formatted in ISO-8601 and returns the timezone.
|
||||||
// The timezone output is formatted for OpenSprinkler Unified firmware.
|
// The timezone output is formatted for OpenSprinkler Unified firmware.
|
||||||
function getTimezone( time ) {
|
function getTimezone( time ) {
|
||||||
|
|
||||||
|
// Match the provided time string against a regex for parsing
|
||||||
time = time.match( filters.time );
|
time = time.match( filters.time );
|
||||||
|
|
||||||
var hour = parseInt( time[7] + time[8] ),
|
var hour = parseInt( time[7] + time[8] ),
|
||||||
minute = parseInt( time[9] );
|
minute = parseInt( time[9] );
|
||||||
|
|
||||||
minute = ( minute / 15 >> 0 ) / 4.0;
|
// Convert the timezone into the OpenSprinkler encoded format
|
||||||
|
minute = ( minute / 15 >> 0 ) / 4;
|
||||||
hour = hour + ( hour >=0 ? minute : -minute );
|
hour = hour + ( hour >=0 ? minute : -minute );
|
||||||
|
|
||||||
return ( ( hour + 12 ) * 4 ) >> 0;
|
return ( ( hour + 12 ) * 4 ) >> 0;
|
||||||
@@ -108,6 +120,8 @@
|
|||||||
|
|
||||||
// Perform the HTTP request to retrieve the weather data
|
// Perform the HTTP request to retrieve the weather data
|
||||||
httpRequest( url, function( data ) {
|
httpRequest( url, function( data ) {
|
||||||
|
|
||||||
|
// Return the data to the callback function
|
||||||
callback( JSON.parse( data ) );
|
callback( JSON.parse( data ) );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
@@ -117,7 +131,11 @@
|
|||||||
|
|
||||||
// Get the API key from the environment variables
|
// Get the API key from the environment variables
|
||||||
var WSI_HISTORY_KEY = process.env.WSI_HISTORY_KEY,
|
var WSI_HISTORY_KEY = process.env.WSI_HISTORY_KEY,
|
||||||
|
|
||||||
|
// Generate a Date object for the current time
|
||||||
today = new Date(),
|
today = new Date(),
|
||||||
|
|
||||||
|
// Generate a Date object for the previous day by subtracting a day (in milliseconds) from today
|
||||||
yesterday = new Date( today.getTime() - 1000 * 60 * 60 * 24 ),
|
yesterday = new Date( today.getTime() - 1000 * 60 * 60 * 24 ),
|
||||||
|
|
||||||
// Generate URL using WSI Cleaned History API in Imperial units showing daily average values
|
// Generate URL using WSI Cleaned History API in Imperial units showing daily average values
|
||||||
@@ -158,7 +176,7 @@
|
|||||||
tempFactor = ( ( temp - 70 ) * 4 ),
|
tempFactor = ( ( temp - 70 ) * 4 ),
|
||||||
precipFactor = ( precip * -2 );
|
precipFactor = ( precip * -2 );
|
||||||
|
|
||||||
// Apply adjustment options if available
|
// Apply adjustment options, if provided, by multiplying the percentage against the factor
|
||||||
if ( adjustmentOptions ) {
|
if ( adjustmentOptions ) {
|
||||||
if ( adjustmentOptions.hasOwnProperty( "h" ) ) {
|
if ( adjustmentOptions.hasOwnProperty( "h" ) ) {
|
||||||
humidityFactor = humidityFactor * ( adjustmentOptions.h / 100 );
|
humidityFactor = humidityFactor * ( adjustmentOptions.h / 100 );
|
||||||
@@ -173,6 +191,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply all of the weather modifying factors and clamp the result between 0 and 200%.
|
||||||
return parseInt( Math.min( Math.max( 0, 100 + humidityFactor + tempFactor + precipFactor ), 200 ) );
|
return parseInt( Math.min( Math.max( 0, 100 + humidityFactor + tempFactor + precipFactor ), 200 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,11 +201,13 @@
|
|||||||
// Function to return the sunrise and sunset times from the weather reply
|
// Function to return the sunrise and sunset times from the weather reply
|
||||||
function getSunData( weather ) {
|
function getSunData( weather ) {
|
||||||
|
|
||||||
// Sun times must be converted from strings into date objects and processed into minutes from midnight
|
// Sun times are parsed from string against a regex to identify the timezone
|
||||||
var sunrise = weather.observation.sunrise.match( filters.time ),
|
var sunrise = weather.observation.sunrise.match( filters.time ),
|
||||||
sunset = weather.observation.sunset.match( filters.time );
|
sunset = weather.observation.sunset.match( filters.time );
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
|
// Values are converted to minutes from midnight for the controller
|
||||||
parseInt( sunrise[4] ) * 60 + parseInt( sunrise[5] ),
|
parseInt( sunrise[4] ) * 60 + parseInt( sunrise[5] ),
|
||||||
parseInt( sunset[4] ) * 60 + parseInt( sunset[5] )
|
parseInt( sunset[4] ) * 60 + parseInt( sunset[5] )
|
||||||
];
|
];
|
||||||
@@ -194,9 +215,23 @@
|
|||||||
|
|
||||||
// Checks if the weather data meets any of the restrictions set by OpenSprinkler.
|
// Checks if the weather data meets any of the restrictions set by OpenSprinkler.
|
||||||
// Restrictions prevent any watering from occurring and are similar to 0% watering level.
|
// Restrictions prevent any watering from occurring and are similar to 0% watering level.
|
||||||
|
//
|
||||||
|
// All queries will return a restrict flag if the current weather indicates rain.
|
||||||
|
//
|
||||||
// California watering restriction prevents watering if precipitation over two days is greater
|
// California watering restriction prevents watering if precipitation over two days is greater
|
||||||
// than 0.01" over the past 48 hours.
|
// than 0.01" over the past 48 hours.
|
||||||
function checkWeatherRestriction( adjustmentValue, weather ) {
|
function checkWeatherRestriction( adjustmentValue, weather ) {
|
||||||
|
|
||||||
|
// Define all the weather codes that indicate rain
|
||||||
|
var adverseCodes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 35, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47];
|
||||||
|
|
||||||
|
if ( adverseCodes.indexOf( weather.observation.icon_code ) !== -1 ) {
|
||||||
|
|
||||||
|
// If the current weather indicates rain, add a restrict flag to the weather script indicating
|
||||||
|
// the controller should not water.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var californiaRestriction = ( adjustmentValue >> 7 ) & 1;
|
var californiaRestriction = ( adjustmentValue >> 7 ) & 1;
|
||||||
|
|
||||||
if ( californiaRestriction ) {
|
if ( californiaRestriction ) {
|
||||||
@@ -226,6 +261,10 @@
|
|||||||
outputFormat = req.query.format,
|
outputFormat = req.query.format,
|
||||||
firmwareVersion = req.query.fwv,
|
firmwareVersion = req.query.fwv,
|
||||||
remoteAddress = req.headers[ "x-forwarded-for" ] || req.connection.remoteAddress,
|
remoteAddress = req.headers[ "x-forwarded-for" ] || req.connection.remoteAddress,
|
||||||
|
|
||||||
|
// Function that will accept the weather after it is received from the API
|
||||||
|
// Data will be processed to retrieve the resulting scale, sunrise/sunset, timezone,
|
||||||
|
// and also calculate if a restriction is met to prevent watering.
|
||||||
finishRequest = function( weather ) {
|
finishRequest = function( weather ) {
|
||||||
if ( !weather || typeof weather.observation !== "object" || typeof weather.observation.imperial !== "object" ) {
|
if ( !weather || typeof weather.observation !== "object" || typeof weather.observation.imperial !== "object" ) {
|
||||||
res.send( "Error: No weather data found." );
|
res.send( "Error: No weather data found." );
|
||||||
@@ -261,6 +300,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// X-Forwarded-For header may contain more than one IP address and therefore
|
||||||
|
// the string is split against a comma and the first value is selected
|
||||||
remoteAddress = remoteAddress.split(",")[0];
|
remoteAddress = remoteAddress.split(",")[0];
|
||||||
|
|
||||||
// Parse weather adjustment options
|
// Parse weather adjustment options
|
||||||
@@ -269,32 +310,39 @@
|
|||||||
// Reconstruct JSON string from deformed controller output
|
// Reconstruct JSON string from deformed controller output
|
||||||
adjustmentOptions = JSON.parse( "{" + adjustmentOptions + "}" );
|
adjustmentOptions = JSON.parse( "{" + adjustmentOptions + "}" );
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
||||||
|
// If the JSON is not valid, do not incorporate weather adjustment options
|
||||||
adjustmentOptions = false;
|
adjustmentOptions = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse location string
|
// Parse location string
|
||||||
if ( filters.gps.test( location ) ) {
|
if ( filters.gps.test( location ) ) {
|
||||||
|
|
||||||
// Handle GPS coordinates
|
// Handle GPS coordinates by storing each coordinate in an array
|
||||||
location = location.split( "," );
|
location = location.split( "," );
|
||||||
location = [ parseFloat( location[0] ), parseFloat( location[1] ) ];
|
location = [ parseFloat( location[0] ), parseFloat( location[1] ) ];
|
||||||
|
|
||||||
|
// Continue with the weather request
|
||||||
getWeatherData( location, finishRequest );
|
getWeatherData( location, finishRequest );
|
||||||
|
|
||||||
} else if ( filters.pws.test( location ) ) {
|
} else if ( filters.pws.test( location ) ) {
|
||||||
|
|
||||||
|
// Handle locations using PWS or ICAO (Weather Underground)
|
||||||
if ( !weatherUndergroundKey ) {
|
if ( !weatherUndergroundKey ) {
|
||||||
|
|
||||||
|
// If no key is provided for Weather Underground then the PWS or ICAO cannot be resolved
|
||||||
res.send( "Error: Weather Underground key required when using PWS or ICAO location." );
|
res.send( "Error: Weather Underground key required when using PWS or ICAO location." );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Weather Underground specific location
|
|
||||||
getPWSCoordinates( location, weatherUndergroundKey, function( result ) {
|
getPWSCoordinates( location, weatherUndergroundKey, function( result ) {
|
||||||
location = result;
|
location = result;
|
||||||
getWeatherData( location, finishRequest );
|
getWeatherData( location, finishRequest );
|
||||||
} );
|
} );
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Attempt to resolve provided location to GPS coordinates
|
// Attempt to resolve provided location to GPS coordinates when it does not match
|
||||||
|
// a GPS coordinate or Weather Underground location using Weather Underground autocomplete
|
||||||
resolveCoordinates( location, function( result ) {
|
resolveCoordinates( location, function( result ) {
|
||||||
location = result;
|
location = result;
|
||||||
getWeatherData( location, finishRequest );
|
getWeatherData( location, finishRequest );
|
||||||
|
|||||||
Reference in New Issue
Block a user