From f0c32a32baaca2897cb5daa58331b331d799879d Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Sun, 12 May 2019 00:21:19 -0400 Subject: [PATCH 1/7] Add HTTPS support to httpRequest --- routes/weather.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/routes/weather.ts b/routes/weather.ts index 13afc35..535d41b 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -2,6 +2,7 @@ import * as express from "express"; import { AdjustmentOptions, GeoCoordinates, TimeData, WateringData, WeatherData } from "../types"; const http = require( "http" ), + https = require ( "https" ), local = require( "./local"), SunCalc = require( "suncalc" ), moment = require( "moment-timezone" ), @@ -458,7 +459,7 @@ exports.getWateringData = async function( req: express.Request, res: express.Res }; /** - * Makes an HTTP GET request to the specified URL and returns the response body. + * Makes an HTTP/HTTPS GET request to the specified URL and returns the response body. * @param url The URL to fetch. * @return A Promise that will be resolved the with response body if the request succeeds, or will be rejected with an * Error if the request fails. @@ -467,14 +468,15 @@ async function httpRequest( url: string ): Promise< string > { return new Promise< any >( ( resolve, reject ) => { const splitUrl: string[] = url.match( filters.url ); + const isHttps = url.startsWith("https"); const options = { host: splitUrl[ 1 ], - port: splitUrl[ 2 ] || 80, + port: splitUrl[ 2 ] || ( isHttps ? 443 : 80 ), path: splitUrl[ 3 ] }; - http.get( options, ( response ) => { + ( isHttps ? https : http ).get( options, ( response ) => { let data = ""; // Reassemble the data as it comes in From 31af9d3320cf0977be1d4c5a6e0f7bb2e0f05f9b Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Sun, 12 May 2019 01:38:27 -0400 Subject: [PATCH 2/7] Use ES6 modules --- routes/local.ts | 13 +++++++------ routes/weather.ts | 39 ++++++++++++++++++++------------------- server.ts | 27 ++++++++++++++------------- tsconfig.json | 2 +- 4 files changed, 42 insertions(+), 39 deletions(-) diff --git a/routes/local.ts b/routes/local.ts index 833c473..10aa1fa 100644 --- a/routes/local.ts +++ b/routes/local.ts @@ -1,8 +1,9 @@ import * as express from "express"; +import { CronJob } from "cron"; -const CronJob = require( "cron" ).CronJob, - server = require( "../server.js" ), - count = { temp: 0, humidity: 0 }; +import * as server from "../server"; + +const count = { temp: 0, humidity: 0 }; let today: PWSStatus = {}, yesterday: PWSStatus = {}, @@ -15,7 +16,7 @@ function sameDay(d1: Date, d2: Date): boolean { d1.getDate() === d2.getDate(); } -exports.captureWUStream = function( req: express.Request, res: express.Response ) { +export const captureWUStream = function( req: express.Request, res: express.Response ) { let prev: number, curr: number; if ( !( "dateutc" in req.query ) || !sameDay( current_date, new Date( req.query.dateutc + "Z") )) { @@ -43,11 +44,11 @@ exports.captureWUStream = function( req: express.Request, res: express.Response res.send( "success\n" ); }; -exports.useLocalWeather = function(): boolean { +export const useLocalWeather = function(): boolean { return server.pws !== "none" ? true : false; }; -exports.getLocalWeather = function(): LocalWeather { +export const getLocalWeather = function(): LocalWeather { const result: LocalWeather = {}; // Use today's weather if we dont have information for yesterday yet (i.e. on startup) diff --git a/routes/weather.ts b/routes/weather.ts index 535d41b..d0fcef9 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -1,21 +1,21 @@ -import * as express from "express"; +import * as express from "express"; +import * as http from "http"; +import * as https from "https"; +import * as SunCalc from "suncalc"; +import * as moment from "moment-timezone"; +import * as geoTZ from "geo-tz"; + +import * as local from "./local"; import { AdjustmentOptions, GeoCoordinates, TimeData, WateringData, WeatherData } from "../types"; -const http = require( "http" ), - https = require ( "https" ), - local = require( "./local"), - SunCalc = require( "suncalc" ), - moment = require( "moment-timezone" ), - geoTZ = require( "geo-tz" ), - - // Define regex filters to match against location - filters = { - gps: /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/, - pws: /^(?:pws|icao|zmw):/, - url: /^https?:\/\/([\w\.-]+)(:\d+)?(\/.*)?$/, - time: /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-])(\d{2})(\d{2})/, - timezone: /^()()()()()()([+-])(\d{2})(\d{2})/ - }; +// Define regex filters to match against location +const filters = { + gps: /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/, + pws: /^(?:pws|icao|zmw):/, + url: /^https?:\/\/([\w\.-]+)(:\d+)?(\/.*)?$/, + time: /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-])(\d{2})(\d{2})/, + timezone: /^()()()()()()([+-])(\d{2})(\d{2})/ +}; /** * Resolves a location description to geographic coordinates. @@ -174,7 +174,8 @@ async function getOWMWeatherData( coordinates: GeoCoordinates ): Promise< Weathe * @return A Promise that will be resolved with WateringData. */ async function getLocalWateringData( coordinates: GeoCoordinates ): Promise< WateringData > { - return local.getLocalWeather(); + // TODO is this type assertion safe? + return local.getLocalWeather() as WateringData; } /** @@ -279,7 +280,7 @@ function checkWeatherRestriction( adjustmentValue: number, weather: WateringData return false; } -exports.getWeatherData = async function( req: express.Request, res: express.Response ) { +export const getWeatherData = async function( req: express.Request, res: express.Response ) { const location: string = getParameter(req.query.loc); if ( !location ) { @@ -309,7 +310,7 @@ exports.getWeatherData = async function( req: express.Request, res: express.Resp // API Handler when using the weatherX.py where X represents the // adjustment method which is encoded to also carry the watering // restriction and therefore must be decoded -exports.getWateringData = async function( req: express.Request, res: express.Response ) { +export const getWateringData = async function( req: express.Request, res: express.Response ) { // The adjustment method is encoded by the OpenSprinkler firmware and must be // parsed. This allows the adjustment method and the restriction type to both diff --git a/server.ts b/server.ts index 2716c85..d9b4d9a 100644 --- a/server.ts +++ b/server.ts @@ -1,16 +1,20 @@ -const packageJson = require( "../package.json" ), - express = require( "express" ), - weather = require( "./routes/weather.js" ), - local = require( "./routes/local.js" ), - cors = require( "cors" ); +import * as express from "express"; +import * as cors from "cors"; +import * as dotenv from "dotenv"; + +import * as weather from "./routes/weather"; +import * as local from "./routes/local"; + +const packageJson = require( "../package.json" ); let host = process.env.HOST || "127.0.0.1", - port = process.env.PORT || 3000, - pws = process.env.PWS || "none", - app = express(); + port = process.env.PORT || 3000; + +export let pws = process.env.PWS || "none"; +export const app = express(); if ( !process.env.HOST || !process.env.PORT || !process.env.LOCAL_PWS ) { - require( "dotenv" ).load(); + dotenv.load(); host = process.env.HOST || host; port = process.env.PORT || port; pws = process.env.PWS || pws; @@ -42,13 +46,10 @@ app.use( function( req, res ) { } ); // Start listening on the service port -app.listen( port, host, function() { +app.listen( +port, host, function() { console.log( "%s now listening on %s:%s", packageJson.description, host, port ); if (pws !== "none" ) { console.log( "%s now listening for local weather stream", packageJson.description ); } } ); - -exports.app = app; -exports.pws = pws; diff --git a/tsconfig.json b/tsconfig.json index 98a056c..aaf0e43 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es6", + "target": "es5", "noImplicitReturns": true, "noEmitOnError": true, "outDir": "js/", From ee31e736ba5540716cac3f1adf356aee8c8e0b4e Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Sun, 12 May 2019 01:55:00 -0400 Subject: [PATCH 3/7] Allow dynamic selection of weather provider --- routes/weather.ts | 105 ++------------------------------- routes/weatherProviders/OWM.ts | 92 +++++++++++++++++++++++++++++ types.ts | 18 ++++++ 3 files changed, 115 insertions(+), 100 deletions(-) create mode 100644 routes/weatherProviders/OWM.ts diff --git a/routes/weather.ts b/routes/weather.ts index d0fcef9..ac9a91f 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -6,7 +6,8 @@ import * as moment from "moment-timezone"; import * as geoTZ from "geo-tz"; import * as local from "./local"; -import { AdjustmentOptions, GeoCoordinates, TimeData, WateringData, WeatherData } from "../types"; +import { AdjustmentOptions, GeoCoordinates, TimeData, WateringData, WeatherData, WeatherProvider } from "../types"; +const weatherProvider: WeatherProvider = require("./weatherProviders/" + ( process.env.WEATHER_PROVIDER || "OWM" ) ).default; // Define regex filters to match against location const filters = { @@ -62,7 +63,7 @@ async function resolveCoordinates( location: string ): Promise< GeoCoordinates > * @return A Promise that will be resolved the with parsed response body if the request succeeds, or will be rejected * with an Error if the request or JSON parsing fails. */ -async function httpJSONRequest(url: string ): Promise< any > { +export async function httpJSONRequest(url: string ): Promise< any > { try { const data: string = await httpRequest(url); return JSON.parse(data); @@ -72,102 +73,6 @@ async function httpJSONRequest(url: string ): Promise< any > { } } -/** - * Retrieves weather data necessary for watering level calculations from the OWM API. - * @param coordinates The coordinates to retrieve the watering data for. - * @return A Promise that will be resolved with WateringData if the API calls succeed, or resolved with undefined - * if an error occurs while retrieving the weather data. - */ -async function getOWMWateringData( coordinates: GeoCoordinates ): Promise< WateringData > { - const OWM_API_KEY = process.env.OWM_API_KEY, - forecastUrl = "http://api.openweathermap.org/data/2.5/forecast?appid=" + OWM_API_KEY + "&units=imperial&lat=" + coordinates[ 0 ] + "&lon=" + coordinates[ 1 ]; - - // Perform the HTTP request to retrieve the weather data - let forecast; - try { - forecast = await httpJSONRequest( forecastUrl ); - } catch (err) { - // Indicate watering data could not be retrieved if an API error occurs. - return undefined; - } - - // Indicate watering data could not be retrieved if the forecast data is incomplete. - if ( !forecast || !forecast.list ) { - return undefined; - } - - let totalTemp = 0, - totalHumidity = 0, - totalPrecip = 0; - - const periods = Math.min(forecast.list.length, 10); - for ( let index = 0; index < periods; index++ ) { - totalTemp += parseFloat( forecast.list[ index ].main.temp ); - totalHumidity += parseInt( forecast.list[ index ].main.humidity ); - totalPrecip += ( forecast.list[ index ].rain ? parseFloat( forecast.list[ index ].rain[ "3h" ] || 0 ) : 0 ); - } - - return { - temp: totalTemp / periods, - humidity: totalHumidity / periods, - precip: totalPrecip / 25.4, - raining: ( forecast.list[ 0 ].rain ? ( parseFloat( forecast.list[ 0 ].rain[ "3h" ] || 0 ) > 0 ) : false ) - }; -} - -/** - * Retrieves the current weather data from OWM for usage in the mobile app. - * @param coordinates The coordinates to retrieve the weather for - * @return A Promise that will be resolved with the WeatherData if the API calls succeed, or resolved with undefined - * if an error occurs while retrieving the weather data. - */ -async function getOWMWeatherData( coordinates: GeoCoordinates ): Promise< WeatherData > { - const OWM_API_KEY = process.env.OWM_API_KEY, - currentUrl = "http://api.openweathermap.org/data/2.5/weather?appid=" + OWM_API_KEY + "&units=imperial&lat=" + coordinates[ 0 ] + "&lon=" + coordinates[ 1 ], - forecastDailyUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?appid=" + OWM_API_KEY + "&units=imperial&lat=" + coordinates[ 0 ] + "&lon=" + coordinates[ 1 ]; - - let current, forecast; - try { - current = await httpJSONRequest( currentUrl ); - forecast = await httpJSONRequest( forecastDailyUrl ); - } catch (err) { - // Indicate watering data could not be retrieved if an API error occurs. - return undefined; - } - - // Indicate watering data could not be retrieved if the forecast data is incomplete. - if ( !current || !current.main || !current.wind || !current.weather || !forecast || !forecast.list ) { - return undefined; - } - - const weather: WeatherData = { - temp: parseInt( current.main.temp ), - humidity: parseInt( current.main.humidity ), - wind: parseInt( current.wind.speed ), - description: current.weather[0].description, - icon: current.weather[0].icon, - - region: forecast.city.country, - city: forecast.city.name, - minTemp: parseInt( forecast.list[ 0 ].temp.min ), - maxTemp: parseInt( forecast.list[ 0 ].temp.max ), - precip: ( forecast.list[ 0 ].rain ? parseFloat( forecast.list[ 0 ].rain || 0 ) : 0 ) / 25.4, - forecast: [] - }; - - for ( let index = 0; index < forecast.list.length; index++ ) { - weather.forecast.push( { - temp_min: parseInt( forecast.list[ index ].temp.min ), - temp_max: parseInt( forecast.list[ index ].temp.max ), - date: parseInt( forecast.list[ index ].dt ), - icon: forecast.list[ index ].weather[ 0 ].icon, - description: forecast.list[ index ].weather[ 0 ].description - } ); - } - - return weather; -} - /** * Retrieves weather data necessary for watering level calculations from the a local record. * @param coordinates The coordinates to retrieve the watering data for. @@ -298,7 +203,7 @@ export const getWeatherData = async function( req: express.Request, res: express // Continue with the weather request const timeData: TimeData = getTimeData( coordinates ); - const weatherData: WeatherData = await getOWMWeatherData( coordinates ); + const weatherData: WeatherData = await weatherProvider.getWeatherData( coordinates ); res.json( { ...timeData, @@ -381,7 +286,7 @@ export const getWateringData = async function( req: express.Request, res: expres if ( local.useLocalWeather() ) { wateringData = await getLocalWateringData( coordinates ); } else { - wateringData = await getOWMWateringData(coordinates); + wateringData = await weatherProvider.getWateringData(coordinates); } diff --git a/routes/weatherProviders/OWM.ts b/routes/weatherProviders/OWM.ts new file mode 100644 index 0000000..ce0c4cb --- /dev/null +++ b/routes/weatherProviders/OWM.ts @@ -0,0 +1,92 @@ +import { GeoCoordinates, WateringData, WeatherData, WeatherProvider } from "../../types"; +import { httpJSONRequest } from "../weather"; + +async function getOWMWateringData( coordinates: GeoCoordinates ): Promise< WateringData > { + const OWM_API_KEY = process.env.OWM_API_KEY, + forecastUrl = "http://api.openweathermap.org/data/2.5/forecast?appid=" + OWM_API_KEY + "&units=imperial&lat=" + coordinates[ 0 ] + "&lon=" + coordinates[ 1 ]; + + // Perform the HTTP request to retrieve the weather data + let forecast; + try { + forecast = await httpJSONRequest( forecastUrl ); + } catch (err) { + // Indicate watering data could not be retrieved if an API error occurs. + return undefined; + } + + // Indicate watering data could not be retrieved if the forecast data is incomplete. + if ( !forecast || !forecast.list ) { + return undefined; + } + + let totalTemp = 0, + totalHumidity = 0, + totalPrecip = 0; + + const periods = Math.min(forecast.list.length, 10); + for ( let index = 0; index < periods; index++ ) { + totalTemp += parseFloat( forecast.list[ index ].main.temp ); + totalHumidity += parseInt( forecast.list[ index ].main.humidity ); + totalPrecip += ( forecast.list[ index ].rain ? parseFloat( forecast.list[ index ].rain[ "3h" ] || 0 ) : 0 ); + } + + return { + temp: totalTemp / periods, + humidity: totalHumidity / periods, + precip: totalPrecip / 25.4, + raining: ( forecast.list[ 0 ].rain ? ( parseFloat( forecast.list[ 0 ].rain[ "3h" ] || 0 ) > 0 ) : false ) + }; +} + +async function getOWMWeatherData( coordinates: GeoCoordinates ): Promise< WeatherData > { + const OWM_API_KEY = process.env.OWM_API_KEY, + currentUrl = "http://api.openweathermap.org/data/2.5/weather?appid=" + OWM_API_KEY + "&units=imperial&lat=" + coordinates[ 0 ] + "&lon=" + coordinates[ 1 ], + forecastDailyUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?appid=" + OWM_API_KEY + "&units=imperial&lat=" + coordinates[ 0 ] + "&lon=" + coordinates[ 1 ]; + + let current, forecast; + try { + current = await httpJSONRequest( currentUrl ); + forecast = await httpJSONRequest( forecastDailyUrl ); + } catch (err) { + // Indicate watering data could not be retrieved if an API error occurs. + return undefined; + } + + // Indicate watering data could not be retrieved if the forecast data is incomplete. + if ( !current || !current.main || !current.wind || !current.weather || !forecast || !forecast.list ) { + return undefined; + } + + const weather: WeatherData = { + temp: parseInt( current.main.temp ), + humidity: parseInt( current.main.humidity ), + wind: parseInt( current.wind.speed ), + description: current.weather[0].description, + icon: current.weather[0].icon, + + region: forecast.city.country, + city: forecast.city.name, + minTemp: parseInt( forecast.list[ 0 ].temp.min ), + maxTemp: parseInt( forecast.list[ 0 ].temp.max ), + precip: ( forecast.list[ 0 ].rain ? parseFloat( forecast.list[ 0 ].rain || 0 ) : 0 ) / 25.4, + forecast: [] + }; + + for ( let index = 0; index < forecast.list.length; index++ ) { + weather.forecast.push( { + temp_min: parseInt( forecast.list[ index ].temp.min ), + temp_max: parseInt( forecast.list[ index ].temp.max ), + date: parseInt( forecast.list[ index ].dt ), + icon: forecast.list[ index ].weather[ 0 ].icon, + description: forecast.list[ index ].weather[ 0 ].description + } ); + } + + return weather; +} + +const OWMWeatherProvider: WeatherProvider = { + getWateringData: getOWMWateringData, + getWeatherData: getOWMWeatherData +}; +export default OWMWeatherProvider; diff --git a/types.ts b/types.ts index c5ae0d4..b9493f9 100644 --- a/types.ts +++ b/types.ts @@ -63,3 +63,21 @@ export interface AdjustmentOptions { /** The rain delay to use (in hours). */ d?: number; } + +export interface WeatherProvider { + /** + * Retrieves weather data necessary for watering level calculations. + * @param coordinates The coordinates to retrieve the watering data for. + * @return A Promise that will be resolved with the WateringData if it is successfully retrieved, + * or resolved with undefined if an error occurs while retrieving the WateringData. + */ + getWateringData( coordinates : GeoCoordinates ): Promise< WateringData >; + + /** + * Retrieves the current weather data for usage in the mobile app. + * @param coordinates The coordinates to retrieve the weather for + * @return A Promise that will be resolved with the WeatherData if it is successfully retrieved, + * or resolved with undefined if an error occurs while retrieving the WeatherData. + */ + getWeatherData( coordinates : GeoCoordinates ): Promise< WeatherData >; +} From 16e913caf368faad5f5159e47fb2ccaf6b222d1a Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Mon, 13 May 2019 15:14:06 -0400 Subject: [PATCH 4/7] Add support for DarkSky API --- routes/weather.ts | 2 +- routes/weatherProviders/DarkSky.ts | 83 ++++++++++++++++++++++++++++++ types.ts | 12 +++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 routes/weatherProviders/DarkSky.ts diff --git a/routes/weather.ts b/routes/weather.ts index ac9a91f..ab966c8 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -337,7 +337,7 @@ export const getWateringData = async function( req: express.Request, res: expres eip: ipToInt( remoteAddress ), // TODO this may need to be changed (https://github.com/OpenSprinkler/OpenSprinkler-Weather/pull/11#issuecomment-491037948) rawData: { - h: wateringData ? wateringData.humidity : null, + h: wateringData ? Math.round( wateringData.humidity * 100) / 100 : null, p: wateringData ? Math.round( wateringData.precip * 100 ) / 100 : null, t: wateringData ? Math.round( wateringData.temp * 10 ) / 10 : null, raining: wateringData ? ( wateringData.raining ? 1 : 0 ) : null diff --git a/routes/weatherProviders/DarkSky.ts b/routes/weatherProviders/DarkSky.ts new file mode 100644 index 0000000..4f1b19a --- /dev/null +++ b/routes/weatherProviders/DarkSky.ts @@ -0,0 +1,83 @@ +import { GeoCoordinates, WateringData, WeatherData, WeatherProvider } from "../../types"; +import { httpJSONRequest } from "../weather"; + +async function getDarkSkyWateringData( coordinates: GeoCoordinates ): Promise< WateringData > { + const DARKSKY_API_KEY = process.env.DARKSKY_API_KEY, + forecastUrl = `https://api.darksky.net/forecast/${DARKSKY_API_KEY}/${coordinates[0]},${coordinates[1]}`; + + let forecast; + try { + forecast = await httpJSONRequest( forecastUrl ); + } catch (err) { + // Indicate watering data could not be retrieved if an API error occurs. + return undefined; + } + + + let totalTemp = 0, + totalHumidity = 0, + totalPrecip = 0; + + const periods = Math.min( forecast.hourly.data.length, 30 ); + for ( let index = 0; index < periods; index++ ) { + totalTemp += forecast.hourly.data[ index ].temperature; + totalHumidity += forecast.hourly.data[ index ].humidity * 100; + totalPrecip += forecast.hourly.data[ index ].precipIntensity; + } + + return { + temp: totalTemp / periods, + humidity: totalHumidity / periods, + precip: totalPrecip, + raining: forecast.currently.precipType === "rain" + }; +} + +async function getDarkSkyWeatherData( coordinates: GeoCoordinates ): Promise< WeatherData > { + const DARKSKY_API_KEY = process.env.DARKSKY_API_KEY, + forecastUrl = `https://api.darksky.net/forecast/${DARKSKY_API_KEY}/${coordinates[0]},${coordinates[1]}`; + + let forecast; + try { + forecast = await httpJSONRequest( forecastUrl ); + } catch (err) { + // Indicate watering data could not be retrieved if an API error occurs. + return undefined; + } + + const weather: WeatherData = { + temp: Math.floor( forecast.currently.temperature ), + humidity: Math.floor( forecast.currently.humidity * 100 ), + wind: Math.floor( forecast.currently.windSpeed ), + description: forecast.currently.summary, + // TODO set this + icon: "", + + region: "", + city: "", + minTemp: Math.floor( forecast.daily.data[ 0 ].temperatureLow ), + maxTemp: Math.floor( forecast.daily.data[ 0 ].temperatureHigh ), + precip: forecast.daily.data[ 0 ].precipIntensity * 24, + forecast: [] + }; + + for ( let index = 0; index < forecast.daily.data.length; index++ ) { + weather.forecast.push( { + temp_min: Math.floor( forecast.daily.data[ index ].temperatureLow ), + temp_max: Math.floor( forecast.daily.data[ index ].temperatureHigh ), + date: forecast.daily.data[ index ].time, + // TODO set this + icon: "", + description: forecast.daily.data[ index ].summary + } ); + } + + return weather; +} + + +const DarkSkyWeatherProvider: WeatherProvider = { + getWateringData: getDarkSkyWateringData, + getWeatherData: getDarkSkyWeatherData +}; +export default DarkSkyWeatherProvider; diff --git a/types.ts b/types.ts index b9493f9..ba856ea 100644 --- a/types.ts +++ b/types.ts @@ -17,22 +17,34 @@ export interface WeatherData { temp: number; /** The current humidity (as a percentage). */ humidity: number; + /** The current wind speed (in miles per hour). */ wind: number; + /** A human-readable description of the weather. */ description: string; + /** An icon ID that represents the current weather. This will be used in http://openweathermap.org/img/w/.png */ icon: string; region: string; city: string; + /** The forecasted minimum temperature for the current day (in Fahrenheit). */ minTemp: number; + /** The forecasted minimum temperature for the current day (in Fahrenheit). */ maxTemp: number; + /** The forecasted total precipitation for the current day (in inches). */ precip: number; forecast: WeatherDataForecast[] } +/** The forecasted weather for a specific day in the future. */ export interface WeatherDataForecast { + /** The forecasted minimum temperature for this day (in Fahrenheit). */ temp_min: number; + /** The forecasted maximum temperature for this day (in Fahrenheit). */ temp_max: number; + /** The timestamp of the day this forecast is for (in Unix epoch seconds). */ date: number; + /** An icon ID that represents the weather at this forecast window. This will be used in http://openweathermap.org/img/w/.png */ icon: string; + /** A human-readable description of the weather. */ description: string; } From 0d77a86ea95a323f24c80ac3e7669ee23b2c7d30 Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Mon, 13 May 2019 15:44:45 -0400 Subject: [PATCH 5/7] Update README --- README.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 29e42f5..8398e7f 100644 --- a/README.md +++ b/README.md @@ -52,21 +52,37 @@ pi@OSPi:~ $ git clone https://github.com/OpenSprinkler/OpenSprinkler-Weather.git pi@OSPi:~ $ cd weather pi@OSPi:~/weather $ npm install ``` -**Step 4:** Go to `https://openweathermap.org/appid` to register with OpenWeatherMaps and obtain an API key that is needed to request weather information. +**Step 4:** Configure the weather server to use either the OpenWeatherMap API or the Dark Sky API + +* **Step 4a:** If you want to use the Open Weather Map API, go to `https://openweathermap.org/appid` to register with OpenWeatherMaps and obtain an API key that is needed to request weather information. + +* **Step 4b:** If you want to use the Dark Sky API, go to `https://darksky.net/dev` to register with Dark Sky and obtain an API key that is needed to request weather information. **Step 5:** The file .env is used by the weather server to specify the interface and port to listen on for OpenSprinkler Firmware weather requests. We need to create a new file, .env, and enter some configuration details. ``` pi@OSPi:~/weather $ nano .env ``` -Add the following three lines to the .env file so that the weather server is configured to listen for weather requests and generate OWM calls. Using 0.0.0.0 as the host interfaces allows you to access the service from another machine to test. Alternatively, set HOST to “localhost” if you want to limit weather service access to only applications running on the local machine. Make sure to use the OWM API key that was provided during registration. +Add the following two lines to the .env file so that the weather server is configured to listen for weather requests. Using 0.0.0.0 as the host interfaces allows you to access the service from another machine to test. Alternatively, set HOST to “localhost” if you want to limit weather service access to only applications running on the local machine. ``` HOST=0.0.0.0 PORT=3000 -OWM_API_KEY= ``` +If you want to use the OWM API, also add the following two lines to the .env file: +``` +WEATHER_PROVIDER=OWM +OWM_API_KEY= +``` + +If you want to use the Dark Sky API instead, add these two lines to the .env file: +``` +WEATHER_PROVIDER=DarkSky +DARKSKY_API_KEY= +``` + + **Step 6:** Setup the Weather Server to start whenever the Raspberry Pi boots up using the built-in service manager: ``` From d4ac228efdd42779addd65a41782b0f863803ddf Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Mon, 13 May 2019 15:51:31 -0400 Subject: [PATCH 6/7] Use more readable casting for port number --- server.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server.ts b/server.ts index d9b4d9a..fb4ccd2 100644 --- a/server.ts +++ b/server.ts @@ -8,7 +8,7 @@ import * as local from "./routes/local"; const packageJson = require( "../package.json" ); let host = process.env.HOST || "127.0.0.1", - port = process.env.PORT || 3000; + port = parseInt( process.env.PORT ) || 3000; export let pws = process.env.PWS || "none"; export const app = express(); @@ -16,7 +16,7 @@ export const app = express(); if ( !process.env.HOST || !process.env.PORT || !process.env.LOCAL_PWS ) { dotenv.load(); host = process.env.HOST || host; - port = process.env.PORT || port; + port = parseInt( process.env.PORT ) || port; pws = process.env.PWS || pws; } @@ -46,8 +46,8 @@ app.use( function( req, res ) { } ); // Start listening on the service port -app.listen( +port, host, function() { - console.log( "%s now listening on %s:%s", packageJson.description, host, port ); +app.listen( port, host, function() { + console.log( "%s now listening on %s:%d", packageJson.description, host, port ); if (pws !== "none" ) { console.log( "%s now listening for local weather stream", packageJson.description ); From 9eb322ec873049d08e89742433fa8397ab26d6a1 Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Mon, 13 May 2019 16:33:30 -0400 Subject: [PATCH 7/7] Fix merge conflict --- server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.ts b/server.ts index fb4ccd2..deaa1c0 100644 --- a/server.ts +++ b/server.ts @@ -14,7 +14,7 @@ export let pws = process.env.PWS || "none"; export const app = express(); if ( !process.env.HOST || !process.env.PORT || !process.env.LOCAL_PWS ) { - dotenv.load(); + dotenv.config(); host = process.env.HOST || host; port = parseInt( process.env.PORT ) || port; pws = process.env.PWS || pws;