Add the ability to retrieve weather data from Weather Underground
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
"grunt": "^0.4.5",
|
"grunt": "^0.4.5",
|
||||||
"grunt-contrib-jshint": "^0.11.2",
|
"grunt-contrib-jshint": "^0.11.2",
|
||||||
"mongoose": "^4.0.6",
|
"mongoose": "^4.0.6",
|
||||||
|
"suncalc": "^1.6.0",
|
||||||
"xml2js": "^0.4.9"
|
"xml2js": "^0.4.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
var http = require( "http" ),
|
var http = require( "http" ),
|
||||||
|
SunCalc = require( "suncalc" ),
|
||||||
// parseXML = require( "xml2js" ).parseString,
|
// parseXML = require( "xml2js" ).parseString,
|
||||||
Cache = require( "../models/Cache" ),
|
Cache = require( "../models/Cache" ),
|
||||||
|
|
||||||
@@ -7,7 +8,8 @@ var http = require( "http" ),
|
|||||||
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):/,
|
||||||
url: /^https?:\/\/([\w\.-]+)(:\d+)?(\/.*)?$/,
|
url: /^https?:\/\/([\w\.-]+)(:\d+)?(\/.*)?$/,
|
||||||
time: /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-])(\d{2})(\d{2})/
|
time: /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-])(\d{2})(\d{2})/,
|
||||||
|
timezone: /^()()()()()()([+-])(\d{2})(\d{2})/
|
||||||
};
|
};
|
||||||
|
|
||||||
// Takes a PWS or ICAO location and resolves the GPS coordinates
|
// Takes a PWS or ICAO location and resolves the GPS coordinates
|
||||||
@@ -53,7 +55,47 @@ function resolveCoordinates( location, callback ) {
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve weather data to complete the weather request
|
// Retrieve weather data to complete the weather request using Weather Underground
|
||||||
|
function getWeatherUndergroundData( location, weatherUndergroundKey, callback ) {
|
||||||
|
|
||||||
|
// Generate URL using The Weather Company API v1 in Imperial units
|
||||||
|
var url = "http://api.wunderground.com/api/" + weatherUndergroundKey +
|
||||||
|
"/yesterday/conditions/q/" + location + ".json";
|
||||||
|
|
||||||
|
// Perform the HTTP request to retrieve the weather data
|
||||||
|
httpRequest( url, function( data ) {
|
||||||
|
try {
|
||||||
|
var data = JSON.parse( data );
|
||||||
|
|
||||||
|
// Calculate sunrise and sunset since Weather Underground does not provide it
|
||||||
|
var sunData = SunCalc.getTimes( new Date(), data.current_observation.observation_location.latitude, data.current_observation.observation_location.longitude ),
|
||||||
|
weather = {
|
||||||
|
icon: data.current_observation.icon,
|
||||||
|
timezone: data.current_observation.local_tz_offset,
|
||||||
|
sunrise: ( sunData.sunrise.getHours() * 60 + sunData.sunrise.getMinutes() ),
|
||||||
|
sunset: ( sunData.sunset.getHours() * 60 + sunData.sunset.getMinutes() ),
|
||||||
|
maxTemp: parseInt( data.history.dailysummary[0].maxtempi ),
|
||||||
|
minTemp: parseInt( data.history.dailysummary[0].mintempi ),
|
||||||
|
temp: data.current_observation.temp_f,
|
||||||
|
humidity: ( parseInt( data.history.dailysummary[0].maxhumidity ) + parseInt( data.history.dailysummary[0].minhumidity ) ) / 2,
|
||||||
|
precip: parseInt( data.current_observation.precip_today_in ) + parseInt( data.history.dailysummary[0].precipi ),
|
||||||
|
solar: parseInt( data.current_observation.UV ),
|
||||||
|
wind: parseInt( data.history.dailysummary[0].meanwindspdi ),
|
||||||
|
elevation: data.current_observation.observation_location.elevation
|
||||||
|
};
|
||||||
|
|
||||||
|
callback( weather );
|
||||||
|
|
||||||
|
} catch ( err ) {
|
||||||
|
|
||||||
|
// Otherwise indicate the request failed
|
||||||
|
callback( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve weather data to complete the weather request using The Weather Channel
|
||||||
function getWeatherData( location, callback ) {
|
function getWeatherData( location, callback ) {
|
||||||
|
|
||||||
// Get the API key from the environment variables
|
// Get the API key from the environment variables
|
||||||
@@ -67,7 +109,20 @@ function getWeatherData( location, callback ) {
|
|||||||
httpRequest( url, function( data ) {
|
httpRequest( url, function( data ) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var weather = JSON.parse( data );
|
var data = JSON.parse( data ),
|
||||||
|
weather = {
|
||||||
|
iconCode: data.observation.icon_code,
|
||||||
|
timezone: data.observation.obs_time_local,
|
||||||
|
sunrise: parseDayTime( data.observation.sunrise ),
|
||||||
|
sunset: parseDayTime( data.observation.sunset ),
|
||||||
|
maxTemp: data.observation.imperial.temp_max_24hour,
|
||||||
|
minTemp: data.observation.imperial.temp_min_24hour,
|
||||||
|
temp: data.observation.imperial.temp,
|
||||||
|
humidity: data.observation.imperial.rh || 0,
|
||||||
|
precip: data.observation.imperial.precip_2day || data.observation.imperial.precip_24hour,
|
||||||
|
solar: data.observation.imperial.uv_index,
|
||||||
|
wind: data.observation.imperial.wspd
|
||||||
|
};
|
||||||
|
|
||||||
location = location.join( "," );
|
location = location.join( "," );
|
||||||
|
|
||||||
@@ -122,7 +177,7 @@ function updateCache( location, weather ) {
|
|||||||
// If a record is found update the data and save it
|
// If a record is found update the data and save it
|
||||||
if ( record ) {
|
if ( record ) {
|
||||||
|
|
||||||
record.currentHumidityTotal += weather.observation.imperial.rh;
|
record.currentHumidityTotal += weather.humidity;
|
||||||
record.currentHumidityCount++;
|
record.currentHumidityCount++;
|
||||||
record.save();
|
record.save();
|
||||||
|
|
||||||
@@ -131,7 +186,7 @@ function updateCache( location, weather ) {
|
|||||||
// If no cache record is found, generate a new one and save it
|
// If no cache record is found, generate a new one and save it
|
||||||
new Cache( {
|
new Cache( {
|
||||||
location: location,
|
location: location,
|
||||||
currentHumidityTotal: weather.observation.imperial.rh,
|
currentHumidityTotal: weather.humidity,
|
||||||
currentHumidityCount: 1
|
currentHumidityCount: 1
|
||||||
} ).save();
|
} ).save();
|
||||||
|
|
||||||
@@ -142,28 +197,14 @@ function updateCache( location, weather ) {
|
|||||||
// Calculates the resulting water scale using the provided weather data, adjustment method and options
|
// Calculates the resulting water scale using the provided weather data, adjustment method and options
|
||||||
function calculateWeatherScale( adjustmentMethod, adjustmentOptions, weather ) {
|
function calculateWeatherScale( adjustmentMethod, adjustmentOptions, weather ) {
|
||||||
|
|
||||||
// Calculate the average temperature
|
|
||||||
var temp = ( weather.observation.imperial.temp_max_24hour + weather.observation.imperial.temp_min_24hour ) / 2,
|
|
||||||
|
|
||||||
// Relative humidity and if unavailable default to 0
|
|
||||||
rh = weather.yesterdayHumidity || weather.observation.imperial.rh || 0,
|
|
||||||
|
|
||||||
// The absolute precipitation in the past 48 hours
|
|
||||||
precip = weather.observation.imperial.precip_2day || weather.observation.imperial.precip_24hour;
|
|
||||||
|
|
||||||
if ( typeof temp !== "number" ) {
|
|
||||||
|
|
||||||
// If the maximum and minimum temperatures are not available then use the current temperature
|
|
||||||
temp = weather.observation.imperial.temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zimmerman method
|
// Zimmerman method
|
||||||
if ( adjustmentMethod === 1 ) {
|
if ( adjustmentMethod === 1 ) {
|
||||||
|
|
||||||
var humidityFactor = ( 30 - rh ),
|
var temp = ( ( weather.maxTemp + weather.minTemp ) / 2 ) || weather.temp,
|
||||||
|
humidityFactor = ( 30 - weather.humidity ),
|
||||||
tempFactor = ( ( temp - 70 ) * 4 ),
|
tempFactor = ( ( temp - 70 ) * 4 ),
|
||||||
precipFactor = ( precip * -2 );
|
precipFactor = ( weather.precip * -200 );
|
||||||
|
console.log(temp, humidityFactor, tempFactor, precipFactor);
|
||||||
// Apply adjustment options, if provided, by multiplying the percentage against the factor
|
// Apply adjustment options, if provided, by multiplying the percentage against the factor
|
||||||
if ( adjustmentOptions ) {
|
if ( adjustmentOptions ) {
|
||||||
if ( adjustmentOptions.hasOwnProperty( "h" ) ) {
|
if ( adjustmentOptions.hasOwnProperty( "h" ) ) {
|
||||||
@@ -196,9 +237,10 @@ function calculateWeatherScale( adjustmentMethod, adjustmentOptions, weather ) {
|
|||||||
function checkWeatherRestriction( adjustmentValue, weather ) {
|
function checkWeatherRestriction( adjustmentValue, weather ) {
|
||||||
|
|
||||||
// Define all the weather codes that indicate rain
|
// 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 ];
|
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 ],
|
||||||
|
adverseWords = [ "flurries", "sleet", "rain", "sleet", "snow", "tstorms" ];
|
||||||
|
|
||||||
if ( adverseCodes.indexOf( weather.observation.icon_code ) !== -1 ) {
|
if ( ( weather.iconCode && adverseCodes.indexOf( weather.iconCode ) !== -1 ) || ( weather.icon && adverseWords.indexOf( weather.icon ) !== -1 ) ) {
|
||||||
|
|
||||||
// If the current weather indicates rain, add a restrict flag to the weather script indicating
|
// If the current weather indicates rain, add a restrict flag to the weather script indicating
|
||||||
// the controller should not water.
|
// the controller should not water.
|
||||||
@@ -211,7 +253,7 @@ function checkWeatherRestriction( adjustmentValue, weather ) {
|
|||||||
|
|
||||||
// If the California watering restriction is in use then prevent watering
|
// If the California watering restriction is in use then prevent watering
|
||||||
// if more then 0.01" of rain has accumulated in the past 48 hours
|
// if more then 0.01" of rain has accumulated in the past 48 hours
|
||||||
if ( weather.observation.imperial.precip_2day > 0.01 || weather.observation.imperial.precip_24hour > 0.01 ) {
|
if ( weather.precip > 0.01 ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +281,7 @@ exports.getWeather = function( req, res ) {
|
|||||||
// Data will be processed to retrieve the resulting scale, sunrise/sunset, timezone,
|
// Data will be processed to retrieve the resulting scale, sunrise/sunset, timezone,
|
||||||
// and also calculate if a restriction is met to prevent watering.
|
// 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 ) {
|
||||||
res.send( "Error: No weather data found." );
|
res.send( "Error: No weather data found." );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -247,9 +289,9 @@ exports.getWeather = function( req, res ) {
|
|||||||
var data = {
|
var data = {
|
||||||
scale: calculateWeatherScale( adjustmentMethod, adjustmentOptions, weather ),
|
scale: calculateWeatherScale( adjustmentMethod, adjustmentOptions, weather ),
|
||||||
restrict: checkWeatherRestriction( req.params[0], weather ) ? 1 : 0,
|
restrict: checkWeatherRestriction( req.params[0], weather ) ? 1 : 0,
|
||||||
tz: getTimezone( weather.observation.obs_time_local ),
|
tz: getTimezone( weather.timezone ),
|
||||||
sunrise: getSunData( weather )[0],
|
sunrise: weather.sunrise,
|
||||||
sunset: getSunData( weather )[1],
|
sunset: weather.sunset,
|
||||||
eip: ipToInt( remoteAddress )
|
eip: ipToInt( remoteAddress )
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -289,16 +331,7 @@ exports.getWeather = function( req, res ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse location string
|
// Parse location string
|
||||||
if ( filters.gps.test( location ) ) {
|
if ( filters.pws.test( location ) ) {
|
||||||
|
|
||||||
// Handle GPS coordinates by storing each coordinate in an array
|
|
||||||
location = location.split( "," );
|
|
||||||
location = [ parseFloat( location[0] ), parseFloat( location[1] ) ];
|
|
||||||
|
|
||||||
// Continue with the weather request
|
|
||||||
getWeatherData( location, finishRequest );
|
|
||||||
|
|
||||||
} else if ( filters.pws.test( location ) ) {
|
|
||||||
|
|
||||||
// Handle locations using PWS or ICAO (Weather Underground)
|
// Handle locations using PWS or ICAO (Weather Underground)
|
||||||
if ( !weatherUndergroundKey ) {
|
if ( !weatherUndergroundKey ) {
|
||||||
@@ -317,6 +350,22 @@ exports.getWeather = function( req, res ) {
|
|||||||
location = result;
|
location = result;
|
||||||
getWeatherData( location, finishRequest );
|
getWeatherData( location, finishRequest );
|
||||||
} );
|
} );
|
||||||
|
} else if ( weatherUndergroundKey ) {
|
||||||
|
|
||||||
|
// The current weather script uses Weather Underground and during the transition period
|
||||||
|
// both will be supported and users who provide a Weather Underground API key will continue
|
||||||
|
// using Weather Underground until The Weather Service becomes the default API
|
||||||
|
|
||||||
|
getWeatherUndergroundData( location, weatherUndergroundKey, finishRequest );
|
||||||
|
} else if ( filters.gps.test( location ) ) {
|
||||||
|
|
||||||
|
// Handle GPS coordinates by storing each coordinate in an array
|
||||||
|
location = location.split( "," );
|
||||||
|
location = [ parseFloat( location[0] ), parseFloat( location[1] ) ];
|
||||||
|
|
||||||
|
// Continue with the weather request
|
||||||
|
getWeatherData( location, finishRequest );
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Attempt to resolve provided location to GPS coordinates when it does not match
|
// Attempt to resolve provided location to GPS coordinates when it does not match
|
||||||
@@ -363,12 +412,13 @@ function httpRequest( url, callback ) {
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accepts a time string formatted in ISO-8601 and returns the timezone.
|
// Accepts a time string formatted in ISO-8601 or just the timezone
|
||||||
|
// offset 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
|
// Match the provided time string against a regex for parsing
|
||||||
time = time.match( filters.time );
|
time = time.match( filters.time ) || time.match( filters.timezone );
|
||||||
|
|
||||||
var hour = parseInt( time[7] + time[8] ),
|
var hour = parseInt( time[7] + time[8] ),
|
||||||
minute = parseInt( time[9] );
|
minute = parseInt( time[9] );
|
||||||
@@ -381,18 +431,13 @@ function getTimezone( time ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 parseDayTime( time ) {
|
||||||
|
|
||||||
// Sun times are parsed from string against a regex to identify the timezone
|
// Time is parsed from string against a regex
|
||||||
var sunrise = weather.observation.sunrise.match( filters.time ),
|
time = time.match( filters.time );
|
||||||
sunset = weather.observation.sunset.match( filters.time );
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
// Values are converted to minutes from midnight for the controller
|
// Values are converted to minutes from midnight for the controller
|
||||||
parseInt( sunrise[4] ) * 60 + parseInt( sunrise[5] ),
|
return parseInt( time[4] ) * 60 + parseInt( time[5] );
|
||||||
parseInt( sunset[4] ) * 60 + parseInt( sunset[5] )
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts IP string to integer
|
// Converts IP string to integer
|
||||||
|
|||||||
Reference in New Issue
Block a user