From 0297341670318c0cfa6da71dcfa60a1455cbf702 Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Tue, 21 May 2019 15:29:15 -0400 Subject: [PATCH 1/7] Refactor watering scale adjustment method selection --- routes/weather.ts | 110 +++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/routes/weather.ts b/routes/weather.ts index aa1f7e1..2dbf7f6 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -18,6 +18,12 @@ const filters = { timezone: /^()()()()()()([+-])(\d{2})(\d{2})/ }; +// Enum of available watering scale adjustment methods. +const ADJUSTMENT_METHOD = { + ZIMMERMAN: 1, + RAIN_DELAY: 2 +}; + /** * Resolves a location description to geographic coordinates. * @param location A partial zip/city/country or a coordinate pair. @@ -110,56 +116,49 @@ function getTimeData( coordinates: GeoCoordinates ): TimeData { } /** - * Calculates how much watering should be scaled based on weather and adjustment options. - * @param adjustmentMethod The method to use to calculate the watering percentage. The only supported method is 1, which - * corresponds to the Zimmerman method. If an invalid adjustmentMethod is used, this method will return -1. + * Calculates how much watering should be scaled based on weather and adjustment options using the Zimmerman method. * @param adjustmentOptions Options to tweak the calculation, or undefined/null if no custom values are to be used. * @param wateringData The weather to use to calculate watering percentage. - * @return The percentage that watering should be scaled by, or -1 if an invalid adjustmentMethod was provided. + * @return The percentage that watering should be scaled by. */ -function calculateWeatherScale( adjustmentMethod: number, adjustmentOptions: AdjustmentOptions, wateringData: WateringData ): number { +function calculateZimmermanWateringScale( adjustmentOptions: AdjustmentOptions, wateringData: WateringData ): number { - // Zimmerman method - if ( adjustmentMethod === 1 ) { - let humidityBase = 30, tempBase = 70, precipBase = 0; + let humidityBase = 30, tempBase = 70, precipBase = 0; - // Check to make sure valid data exists for all factors - if ( !validateValues( [ "temp", "humidity", "precip" ], wateringData ) ) { - return 100; - } - - // Get baseline conditions for 100% water level, if provided - if ( adjustmentOptions ) { - humidityBase = adjustmentOptions.hasOwnProperty( "bh" ) ? adjustmentOptions.bh : humidityBase; - tempBase = adjustmentOptions.hasOwnProperty( "bt" ) ? adjustmentOptions.bt : tempBase; - precipBase = adjustmentOptions.hasOwnProperty( "br" ) ? adjustmentOptions.br : precipBase; - } - - let temp = wateringData.temp, - humidityFactor = ( humidityBase - wateringData.humidity ), - tempFactor = ( ( temp - tempBase ) * 4 ), - precipFactor = ( ( precipBase - wateringData.precip ) * 200 ); - - // Apply adjustment options, if provided, by multiplying the percentage against the factor - if ( adjustmentOptions ) { - if ( adjustmentOptions.hasOwnProperty( "h" ) ) { - humidityFactor = humidityFactor * ( adjustmentOptions.h / 100 ); - } - - if ( adjustmentOptions.hasOwnProperty( "t" ) ) { - tempFactor = tempFactor * ( adjustmentOptions.t / 100 ); - } - - if ( adjustmentOptions.hasOwnProperty( "r" ) ) { - precipFactor = precipFactor * ( adjustmentOptions.r / 100 ); - } - } - - // Apply all of the weather modifying factors and clamp the result between 0 and 200%. - return Math.floor( Math.min( Math.max( 0, 100 + humidityFactor + tempFactor + precipFactor ), 200 ) ); + // Check to make sure valid data exists for all factors + if ( !validateValues( [ "temp", "humidity", "precip" ], wateringData ) ) { + return 100; } - return -1; + // Get baseline conditions for 100% water level, if provided + if ( adjustmentOptions ) { + humidityBase = adjustmentOptions.hasOwnProperty( "bh" ) ? adjustmentOptions.bh : humidityBase; + tempBase = adjustmentOptions.hasOwnProperty( "bt" ) ? adjustmentOptions.bt : tempBase; + precipBase = adjustmentOptions.hasOwnProperty( "br" ) ? adjustmentOptions.br : precipBase; + } + + let temp = wateringData.temp, + humidityFactor = ( humidityBase - wateringData.humidity ), + tempFactor = ( ( temp - tempBase ) * 4 ), + precipFactor = ( ( precipBase - wateringData.precip ) * 200 ); + + // Apply adjustment options, if provided, by multiplying the percentage against the factor + if ( adjustmentOptions ) { + if ( adjustmentOptions.hasOwnProperty( "h" ) ) { + humidityFactor = humidityFactor * ( adjustmentOptions.h / 100 ); + } + + if ( adjustmentOptions.hasOwnProperty( "t" ) ) { + tempFactor = tempFactor * ( adjustmentOptions.t / 100 ); + } + + if ( adjustmentOptions.hasOwnProperty( "r" ) ) { + precipFactor = precipFactor * ( adjustmentOptions.r / 100 ); + } + } + + // Apply all of the weather modifying factors and clamp the result between 0 and 200%. + return Math.floor( Math.min( Math.max( 0, 100 + humidityFactor + tempFactor + precipFactor ), 200 ) ); } /** @@ -281,27 +280,30 @@ export const getWateringData = async function( req: express.Request, res: expres } } - let scale: number = calculateWeatherScale( adjustmentMethod, adjustmentOptions, wateringData ), - rainDelay: number = -1; + let scale = -1, rainDelay = -1; + + if ( adjustmentMethod === ADJUSTMENT_METHOD.ZIMMERMAN ) { + scale = calculateZimmermanWateringScale( adjustmentOptions, wateringData ); + } if (wateringData) { // Check for any user-set restrictions and change the scale to 0 if the criteria is met if (checkWeatherRestriction(req.params[0], wateringData)) { scale = 0; } - } - // If any weather adjustment is being used, check the rain status - if ( adjustmentMethod > 0 && wateringData && wateringData.raining ) { + // If any weather adjustment is being used, check the rain status + if ( adjustmentMethod > 0 && wateringData.raining ) { - // If it is raining and the user has weather-based rain delay as the adjustment method then apply the specified delay - if ( adjustmentMethod === 2 ) { + // If it is raining and the user has weather-based rain delay as the adjustment method then apply the specified delay + if ( adjustmentMethod === ADJUSTMENT_METHOD.RAIN_DELAY ) { - rainDelay = ( adjustmentOptions && adjustmentOptions.hasOwnProperty( "d" ) ) ? adjustmentOptions.d : 24; - } else { + rainDelay = ( adjustmentOptions && adjustmentOptions.hasOwnProperty( "d" ) ) ? adjustmentOptions.d : 24; + } else { - // For any other adjustment method, apply a scale of 0 (as the scale will revert when the rain stops) - scale = 0; + // For any other adjustment method, apply a scale of 0 (as the scale will revert when the rain stops) + scale = 0; + } } } From 0e23e568172ec392b3fa34b131a20af0c3cabf7a Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Tue, 21 May 2019 22:14:46 -0400 Subject: [PATCH 2/7] Add support for fallback WeatherProviders --- routes/weather.ts | 6 ++- .../CompositeWeatherProvider.ts | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 routes/weatherProviders/CompositeWeatherProvider.ts diff --git a/routes/weather.ts b/routes/weather.ts index 2dbf7f6..dc15a5b 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -7,7 +7,11 @@ import * as geoTZ from "geo-tz"; import * as local from "./local"; import { AdjustmentOptions, GeoCoordinates, TimeData, WateringData, WeatherData, WeatherProvider } from "../types"; -const weatherProvider: WeatherProvider = require("./weatherProviders/" + ( process.env.WEATHER_PROVIDER || "OWM" ) ).default; +import CompositeWeatherProvider from "./weatherProviders/CompositeWeatherProvider"; + +const weatherProvider: WeatherProvider = new CompositeWeatherProvider( + ( process.env.WEATHER_PROVIDER || "OWM" ).split( "," ).map( ( id ) => require( "./weatherProviders/" + id ).default ) +); // Define regex filters to match against location const filters = { diff --git a/routes/weatherProviders/CompositeWeatherProvider.ts b/routes/weatherProviders/CompositeWeatherProvider.ts new file mode 100644 index 0000000..c79e496 --- /dev/null +++ b/routes/weatherProviders/CompositeWeatherProvider.ts @@ -0,0 +1,45 @@ +import { GeoCoordinates, WateringData, WeatherData, WeatherProvider } from "../../types"; + +/** + * A WeatherProvider calls other WeatherProviders until one successfully returns a value. + * This is a special utility WeatherProvider, and should NOT be selected with the WEATHER_PROVIDER environment variable. + */ +export default class CompositeWeatherProvider implements WeatherProvider { + + private readonly weatherProviders: WeatherProvider[]; + + public constructor( weatherProviders: WeatherProvider[] ) { + this.weatherProviders = weatherProviders; + } + + public async getWateringData( coordinates : GeoCoordinates ): Promise< WateringData > { + return await this.callMethod( "getWateringData", coordinates ) as WateringData; + } + + public async getWeatherData( coordinates : GeoCoordinates ): Promise< WeatherData > { + return await this.callMethod( "getWeatherData", coordinates ) as WeatherData; + } + + /** + * Calls a specified function in each WeatherProvider until one returns a non-undefined value. If the function is + * not defined for a WeatherProvider, it will be skipped. + * @param func The name of the function to call. + * @param args The arguments to pass to the function. + * @return A promise that will be resolved with the first non-undefined value returned by a WeatherProvider, or + * resolved with undefined if none of the WeatherProviders returned a value. + */ + private async callMethod( func: "getWateringData" | "getWeatherData", ...args: any ): Promise< unknown > { + for ( const weatherProvider of this.weatherProviders ) { + if ( !weatherProvider[ func ] ) { + continue; + } + + // @ts-ignore + const result = await weatherProvider[ func ]( ...args ); + if ( result ) { + return result; + } + } + return undefined; + } +} From bcd006acf361bee13f24296a01e521c8bd9e8cd6 Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Tue, 21 May 2019 22:35:53 -0400 Subject: [PATCH 3/7] Add WeatherProvider ID to returned data --- routes/weatherProviders/DarkSky.ts | 2 ++ routes/weatherProviders/OWM.ts | 2 ++ types.ts | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/routes/weatherProviders/DarkSky.ts b/routes/weatherProviders/DarkSky.ts index c934d94..b5acddd 100644 --- a/routes/weatherProviders/DarkSky.ts +++ b/routes/weatherProviders/DarkSky.ts @@ -19,6 +19,7 @@ async function getDarkSkyWateringData( coordinates: GeoCoordinates ): Promise< W } return { + weatherProvider: "DarkSky", // Calculate average temperature for the day using hourly data. temp : historicData.hourly.data.reduce( ( sum, hourlyData ) => sum + hourlyData.temperature, 0 ) / historicData.hourly.data.length, humidity: historicData.daily.data[ 0 ].humidity * 100, @@ -40,6 +41,7 @@ async function getDarkSkyWeatherData( coordinates: GeoCoordinates ): Promise< We } const weather: WeatherData = { + weatherProvider: "DarkSky", temp: Math.floor( forecast.currently.temperature ), humidity: Math.floor( forecast.currently.humidity * 100 ), wind: Math.floor( forecast.currently.windSpeed ), diff --git a/routes/weatherProviders/OWM.ts b/routes/weatherProviders/OWM.ts index d225d1e..096fae2 100644 --- a/routes/weatherProviders/OWM.ts +++ b/routes/weatherProviders/OWM.ts @@ -31,6 +31,7 @@ async function getOWMWateringData( coordinates: GeoCoordinates ): Promise< Water } return { + weatherProvider: "OWM", temp: totalTemp / periods, humidity: totalHumidity / periods, precip: totalPrecip / 25.4, @@ -58,6 +59,7 @@ async function getOWMWeatherData( coordinates: GeoCoordinates ): Promise< Weathe } const weather: WeatherData = { + weatherProvider: "OWM", temp: parseInt( current.main.temp ), humidity: parseInt( current.main.humidity ), wind: parseInt( current.wind.speed ), diff --git a/types.ts b/types.ts index 5f94ab5..ada033a 100644 --- a/types.ts +++ b/types.ts @@ -13,6 +13,8 @@ export interface TimeData { } export interface WeatherData { + /** The WeatherProvider that generated this data. */ + weatherProvider: WeatherProviderId; /** The current temperature (in Fahrenheit). */ temp: number; /** The current humidity (as a percentage). */ @@ -54,6 +56,8 @@ export interface WeatherDataForecast { * available. */ export interface WateringData { + /** The WeatherProvider that generated this data. */ + weatherProvider: WeatherProviderId; /** The average temperature over the window (in Fahrenheit). */ temp: number; /** The average humidity over the window (as a percentage). */ @@ -98,3 +102,5 @@ export interface WeatherProvider { */ getWeatherData( coordinates : GeoCoordinates ): Promise< WeatherData >; } + +export type WeatherProviderId = "OWM" | "DarkSky"; From 4ffeed5999f783d8ed60860adb447c82abd34365 Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Tue, 21 May 2019 22:49:49 -0400 Subject: [PATCH 4/7] Convert local to a WeatherProvider --- routes/weather.ts | 22 ++++------------- routes/{ => weatherProviders}/local.ts | 33 +++++++++++++------------- server.ts | 2 +- types.ts | 6 ++--- 4 files changed, 24 insertions(+), 39 deletions(-) rename routes/{ => weatherProviders}/local.ts (72%) diff --git a/routes/weather.ts b/routes/weather.ts index dc15a5b..7fa9497 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -5,7 +5,6 @@ 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, WeatherProvider } from "../types"; import CompositeWeatherProvider from "./weatherProviders/CompositeWeatherProvider"; @@ -87,16 +86,6 @@ export async function httpJSONRequest(url: string ): Promise< any > { } } -/** - * Retrieves weather data necessary for watering level calculations from the a local record. - * @param coordinates The coordinates to retrieve the watering data for. - * @return A Promise that will be resolved with WateringData. - */ -async function getLocalWateringData( coordinates: GeoCoordinates ): Promise< WateringData > { - // TODO is this type assertion safe? - return local.getLocalWeather() as WateringData; -} - /** * Calculates timezone and sunrise/sunset for the specified coordinates. * @param coordinates The coordinates to use to calculate time data. @@ -262,12 +251,7 @@ export const getWateringData = async function( req: express.Request, res: expres // Continue with the weather request let timeData: TimeData = getTimeData( coordinates ); - let wateringData: WateringData; - if ( local.useLocalWeather() ) { - wateringData = await getLocalWateringData( coordinates ); - } else { - wateringData = await weatherProvider.getWateringData(coordinates); - } + let wateringData: WateringData = await weatherProvider.getWateringData(coordinates); // Process data to retrieve the resulting scale, sunrise/sunset, timezone, @@ -327,7 +311,9 @@ export const getWateringData = async function( req: express.Request, res: expres } }; - if ( local.useLocalWeather() ) { + /* Note: The local WeatherProvider will never return undefined, so there's no need to worry about this condition + failing to be met if the local WeatherProvider is used but wateringData is falsy (since it will never happen). */ + if ( wateringData && wateringData.weatherProvider === "local" ) { console.log( "OpenSprinkler Weather Response: %s", JSON.stringify( data ) ); } diff --git a/routes/local.ts b/routes/weatherProviders/local.ts similarity index 72% rename from routes/local.ts rename to routes/weatherProviders/local.ts index e9f0333..314df1c 100644 --- a/routes/local.ts +++ b/routes/weatherProviders/local.ts @@ -1,5 +1,6 @@ import * as express from "express"; import { CronJob } from "cron"; +import { GeoCoordinates, WateringData, WeatherProvider } from "../../types"; const count = { temp: 0, humidity: 0 }; @@ -42,25 +43,20 @@ export const captureWUStream = function( req: express.Request, res: express.Resp res.send( "success\n" ); }; -export const useLocalWeather = function(): boolean { - return process.env.PWS ? true : false; -}; - -export const getLocalWeather = function(): LocalWeather { - const result: LocalWeather = {}; - - // Use today's weather if we dont have information for yesterday yet (i.e. on startup) - Object.assign( result, today, yesterday); +export const getLocalWateringData = function(): WateringData { + const result: WateringData = { + ...yesterday as WateringData, + // Use today's weather if we dont have information for yesterday yet (i.e. on startup) + ...today, + // PWS report "buckets" so consider it still raining if last bucket was less than an hour ago + raining: last_bucket !== undefined ? ( ( Date.now() - +last_bucket ) / 1000 / 60 / 60 < 1 ) : undefined, + weatherProvider: "local" + }; if ( "precip" in yesterday && "precip" in today ) { result.precip = yesterday.precip + today.precip; } - // PWS report "buckets" so consider it still raining if last bucket was less than an hour ago - if ( last_bucket !== undefined ) { - result.raining = ( ( Date.now() - +last_bucket ) / 1000 / 60 / 60 < 1 ); - } - return result; }; @@ -79,6 +75,9 @@ interface PWSStatus { precip?: number; } -export interface LocalWeather extends PWSStatus { - raining?: boolean; -} +const LocalWeatherProvider: WeatherProvider = { + getWateringData: async function ( coordinates: GeoCoordinates ) { + return getLocalWateringData(); + } +}; +export default LocalWeatherProvider; diff --git a/server.ts b/server.ts index 15c8761..493dc70 100644 --- a/server.ts +++ b/server.ts @@ -5,7 +5,7 @@ import * as express from "express"; import * as cors from "cors"; import * as weather from "./routes/weather"; -import * as local from "./routes/local"; +import * as local from "./routes/weatherProviders/local"; let host = process.env.HOST || "127.0.0.1", port = parseInt( process.env.PORT ) || 3000; diff --git a/types.ts b/types.ts index ada033a..6727935 100644 --- a/types.ts +++ b/types.ts @@ -92,7 +92,7 @@ export interface WeatherProvider { * @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 >; + getWateringData?( coordinates : GeoCoordinates ): Promise< WateringData >; /** * Retrieves the current weather data for usage in the mobile app. @@ -100,7 +100,7 @@ export interface WeatherProvider { * @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 >; + getWeatherData?( coordinates : GeoCoordinates ): Promise< WeatherData >; } -export type WeatherProviderId = "OWM" | "DarkSky"; +export type WeatherProviderId = "OWM" | "DarkSky" | "local"; From dec9f31dec60907733f9339b2356bba8d2c3e10a Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Thu, 23 May 2019 14:36:34 -0400 Subject: [PATCH 5/7] Revert "Add support for fallback WeatherProviders" This reverts commit 0e23e568172ec392b3fa34b131a20af0c3cabf7a. --- routes/weather.ts | 6 +-- .../CompositeWeatherProvider.ts | 45 ------------------- 2 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 routes/weatherProviders/CompositeWeatherProvider.ts diff --git a/routes/weather.ts b/routes/weather.ts index 7fa9497..1d292c0 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -6,11 +6,7 @@ import * as moment from "moment-timezone"; import * as geoTZ from "geo-tz"; import { AdjustmentOptions, GeoCoordinates, TimeData, WateringData, WeatherData, WeatherProvider } from "../types"; -import CompositeWeatherProvider from "./weatherProviders/CompositeWeatherProvider"; - -const weatherProvider: WeatherProvider = new CompositeWeatherProvider( - ( process.env.WEATHER_PROVIDER || "OWM" ).split( "," ).map( ( id ) => require( "./weatherProviders/" + id ).default ) -); +const weatherProvider: WeatherProvider = require("./weatherProviders/" + ( process.env.WEATHER_PROVIDER || "OWM" ) ).default; // Define regex filters to match against location const filters = { diff --git a/routes/weatherProviders/CompositeWeatherProvider.ts b/routes/weatherProviders/CompositeWeatherProvider.ts deleted file mode 100644 index c79e496..0000000 --- a/routes/weatherProviders/CompositeWeatherProvider.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { GeoCoordinates, WateringData, WeatherData, WeatherProvider } from "../../types"; - -/** - * A WeatherProvider calls other WeatherProviders until one successfully returns a value. - * This is a special utility WeatherProvider, and should NOT be selected with the WEATHER_PROVIDER environment variable. - */ -export default class CompositeWeatherProvider implements WeatherProvider { - - private readonly weatherProviders: WeatherProvider[]; - - public constructor( weatherProviders: WeatherProvider[] ) { - this.weatherProviders = weatherProviders; - } - - public async getWateringData( coordinates : GeoCoordinates ): Promise< WateringData > { - return await this.callMethod( "getWateringData", coordinates ) as WateringData; - } - - public async getWeatherData( coordinates : GeoCoordinates ): Promise< WeatherData > { - return await this.callMethod( "getWeatherData", coordinates ) as WeatherData; - } - - /** - * Calls a specified function in each WeatherProvider until one returns a non-undefined value. If the function is - * not defined for a WeatherProvider, it will be skipped. - * @param func The name of the function to call. - * @param args The arguments to pass to the function. - * @return A promise that will be resolved with the first non-undefined value returned by a WeatherProvider, or - * resolved with undefined if none of the WeatherProviders returned a value. - */ - private async callMethod( func: "getWateringData" | "getWeatherData", ...args: any ): Promise< unknown > { - for ( const weatherProvider of this.weatherProviders ) { - if ( !weatherProvider[ func ] ) { - continue; - } - - // @ts-ignore - const result = await weatherProvider[ func ]( ...args ); - if ( result ) { - return result; - } - } - return undefined; - } -} From 4490df0aef678ec99af199e736458d502b088f1d Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Sat, 25 May 2019 15:00:42 -0400 Subject: [PATCH 6/7] Improve WeatherProvider error handling --- routes/weather.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/routes/weather.ts b/routes/weather.ts index 4669695..21b8cb9 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -182,6 +182,11 @@ function checkWeatherRestriction( adjustmentValue: number, weather: WateringData export const getWeatherData = async function( req: express.Request, res: express.Response ) { const location: string = getParameter(req.query.loc); + if ( !weatherProvider.getWeatherData ) { + res.send( "Error: selected WeatherProvider does not support getWeatherData" ); + return; + } + let coordinates: GeoCoordinates; try { coordinates = await resolveCoordinates( location ); @@ -250,6 +255,11 @@ export const getWateringData = async function( req: express.Request, res: expres let timeData: TimeData = getTimeData( coordinates ); let wateringData: WateringData; if ( adjustmentMethod !== ADJUSTMENT_METHOD.MANUAL ) { + if ( !weatherProvider.getWateringData ) { + res.send( "Error: selected WeatherProvider does not support getWateringData" ); + return; + } + wateringData = await weatherProvider.getWateringData( coordinates ); } From 7ab7a1cec2368a9e3d085dc00bf86aceabe79f04 Mon Sep 17 00:00:00 2001 From: Matthew Oslan Date: Sat, 25 May 2019 22:06:10 -0400 Subject: [PATCH 7/7] Miscellaneous code cleanup --- routes/weather.ts | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/routes/weather.ts b/routes/weather.ts index 21b8cb9..3071506 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -58,7 +58,7 @@ async function resolveCoordinates( location: string ): Promise< GeoCoordinates > if ( typeof data.RESULTS === "object" && data.RESULTS.length && data.RESULTS[ 0 ].tz !== "MISSING" ) { // If it is, reply with an array containing the GPS coordinates - return [ data.RESULTS[ 0 ].lat, data.RESULTS[ 0 ].lon ]; + return [ parseFloat( data.RESULTS[ 0 ].lat ), parseFloat( data.RESULTS[ 0 ].lon ) ]; } else { // Otherwise, indicate no data was found @@ -127,9 +127,8 @@ function calculateZimmermanWateringScale( adjustmentOptions: AdjustmentOptions, precipBase = adjustmentOptions.hasOwnProperty( "br" ) ? adjustmentOptions.br : precipBase; } - let temp = wateringData.temp, - humidityFactor = ( humidityBase - wateringData.humidity ), - tempFactor = ( ( temp - tempBase ) * 4 ), + let humidityFactor = ( humidityBase - wateringData.humidity ), + tempFactor = ( ( wateringData.temp - tempBase ) * 4 ), precipFactor = ( ( precipBase - wateringData.precip ) * 200 ); // Apply adjustment options, if provided, by multiplying the percentage against the factor @@ -249,7 +248,6 @@ export const getWateringData = async function( req: express.Request, res: expres res.send(`Error: Unable to resolve location (${err})`); return; } - location = coordinates; // Continue with the weather request let timeData: TimeData = getTimeData( coordinates ); @@ -263,21 +261,6 @@ export const getWateringData = async function( req: express.Request, res: expres wateringData = await weatherProvider.getWateringData( coordinates ); } - - // Process data to retrieve the resulting scale, sunrise/sunset, timezone, - // and also calculate if a restriction is met to prevent watering. - - // Use getTimeData as fallback if a PWS is used but time data is not provided. - // This will never occur, but it might become possible in the future when PWS support is re-added. - if ( !timeData ) { - if ( typeof location[ 0 ] === "number" && typeof location[ 1 ] === "number" ) { - timeData = getTimeData( location as GeoCoordinates ); - } else { - res.send( "Error: No weather data found." ); - return; - } - } - let scale = -1, rainDelay = -1; if ( adjustmentMethod === ADJUSTMENT_METHOD.ZIMMERMAN ) { @@ -324,12 +307,6 @@ export const getWateringData = async function( req: express.Request, res: expres }; } - /* Note: The local WeatherProvider will never return undefined, so there's no need to worry about this condition - failing to be met if the local WeatherProvider is used but wateringData is falsy (since it will never happen). */ - if ( wateringData && wateringData.weatherProvider === "local" ) { - console.log( "OpenSprinkler Weather Response: %s", JSON.stringify( data ) ); - } - // Return the response to the client in the requested format if ( outputFormat === "json" ) { res.json( data ); @@ -393,6 +370,11 @@ async function httpRequest( url: string ): Promise< string > { function validateValues( keys: string[], obj: object ): boolean { let key: string; + // Return false if the object is null/undefined. + if ( !obj ) { + return false; + } + for ( key in keys ) { if ( !keys.hasOwnProperty( key ) ) { continue;