Update remaining functions to modern TypeScript
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
|
|
||||||
var http = require( "http" ),
|
const http = require( "http" ),
|
||||||
SunCalc = require( "suncalc" ),
|
SunCalc = require( "suncalc" ),
|
||||||
moment = require( "moment-timezone" ),
|
moment = require( "moment-timezone" ),
|
||||||
geoTZ = require( "geo-tz" ),
|
geoTZ = require( "geo-tz" ),
|
||||||
@@ -187,18 +187,27 @@ function getTimeData( coordinates: GeoCoordinates ): TimeData {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculates the resulting water scale using the provided weather data, adjustment method and options
|
/**
|
||||||
function calculateWeatherScale( adjustmentMethod, adjustmentOptions, weather ) {
|
* 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.
|
||||||
|
* @param adjustmentOptions Options to tweak the calculation, or undefined/null if no custom values are to be used.
|
||||||
|
* @param data 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.
|
||||||
|
*/
|
||||||
|
function calculateWeatherScale( adjustmentMethod: number, adjustmentOptions: AdjustmentOptions, data: OWMWateringData | TimeData ): number {
|
||||||
|
|
||||||
// Zimmerman method
|
// Zimmerman method
|
||||||
if ( adjustmentMethod === 1 ) {
|
if ( adjustmentMethod === 1 ) {
|
||||||
var humidityBase = 30, tempBase = 70, precipBase = 0;
|
let humidityBase = 30, tempBase = 70, precipBase = 0;
|
||||||
|
|
||||||
// Check to make sure valid data exists for all factors
|
// Check to make sure valid data exists for all factors
|
||||||
if ( !validateValues( [ "temp", "humidity", "precip" ], weather ) ) {
|
if ( !validateValues( [ "temp", "humidity", "precip" ], data ) ) {
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wateringData: OWMWateringData = data as OWMWateringData;
|
||||||
|
|
||||||
// Get baseline conditions for 100% water level, if provided
|
// Get baseline conditions for 100% water level, if provided
|
||||||
if ( adjustmentOptions ) {
|
if ( adjustmentOptions ) {
|
||||||
humidityBase = adjustmentOptions.hasOwnProperty( "bh" ) ? adjustmentOptions.bh : humidityBase;
|
humidityBase = adjustmentOptions.hasOwnProperty( "bh" ) ? adjustmentOptions.bh : humidityBase;
|
||||||
@@ -206,10 +215,10 @@ function calculateWeatherScale( adjustmentMethod, adjustmentOptions, weather ) {
|
|||||||
precipBase = adjustmentOptions.hasOwnProperty( "br" ) ? adjustmentOptions.br : precipBase;
|
precipBase = adjustmentOptions.hasOwnProperty( "br" ) ? adjustmentOptions.br : precipBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
var temp = ( ( weather.maxTemp + weather.minTemp ) / 2 ) || weather.temp,
|
let temp = wateringData.temp,
|
||||||
humidityFactor = ( humidityBase - weather.humidity ),
|
humidityFactor = ( humidityBase - wateringData.humidity ),
|
||||||
tempFactor = ( ( temp - tempBase ) * 4 ),
|
tempFactor = ( ( temp - tempBase ) * 4 ),
|
||||||
precipFactor = ( ( precipBase - weather.precip ) * 200 );
|
precipFactor = ( ( precipBase - wateringData.precip ) * 200 );
|
||||||
|
|
||||||
// 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 ) {
|
||||||
@@ -233,17 +242,23 @@ function calculateWeatherScale( adjustmentMethod, adjustmentOptions, weather ) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
* 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. Known restrictions are:
|
||||||
// California watering restriction prevents watering if precipitation over two days is greater
|
*
|
||||||
// than 0.01" over the past 48 hours.
|
* - California watering restriction prevents watering if precipitation over two days is greater than 0.1" over the past
|
||||||
function checkWeatherRestriction( adjustmentValue, weather ) {
|
* 48 hours.
|
||||||
|
* @param adjustmentValue The adjustment value, which indicates which restrictions should be checked.
|
||||||
|
* @param weather Watering data to use to determine if any restrictions apply.
|
||||||
|
* @return A boolean indicating if the watering level should be set to 0% due to a restriction.
|
||||||
|
*/
|
||||||
|
function checkWeatherRestriction( adjustmentValue: number, weather: OWMWateringData ): boolean {
|
||||||
|
|
||||||
var californiaRestriction = ( adjustmentValue >> 7 ) & 1;
|
const californiaRestriction = ( adjustmentValue >> 7 ) & 1;
|
||||||
|
|
||||||
if ( californiaRestriction ) {
|
if ( californiaRestriction ) {
|
||||||
|
|
||||||
|
// TODO this is currently checking if the forecasted precipitation over the next 30 hours is >0.1 inches
|
||||||
// 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.1" of rain has accumulated in the past 48 hours
|
// if more then 0.1" of rain has accumulated in the past 48 hours
|
||||||
if ( weather.precip > 0.1 ) {
|
if ( weather.precip > 0.1 ) {
|
||||||
@@ -287,24 +302,25 @@ exports.getWeatherData = async function( req: express.Request, res: express.Resp
|
|||||||
// API Handler when using the weatherX.py where X represents the
|
// API Handler when using the weatherX.py where X represents the
|
||||||
// adjustment method which is encoded to also carry the watering
|
// adjustment method which is encoded to also carry the watering
|
||||||
// restriction and therefore must be decoded
|
// restriction and therefore must be decoded
|
||||||
exports.getWateringData = async function( req, res ) {
|
exports.getWateringData = async function( req: express.Request, res: express.Response ) {
|
||||||
|
|
||||||
// The adjustment method is encoded by the OpenSprinkler firmware and must be
|
// The adjustment method is encoded by the OpenSprinkler firmware and must be
|
||||||
// parsed. This allows the adjustment method and the restriction type to both
|
// parsed. This allows the adjustment method and the restriction type to both
|
||||||
// be saved in the same byte.
|
// be saved in the same byte.
|
||||||
var adjustmentMethod = req.params[ 0 ] & ~( 1 << 7 ),
|
let adjustmentMethod: number = req.params[ 0 ] & ~( 1 << 7 ),
|
||||||
adjustmentOptions = req.query.wto,
|
adjustmentOptionsString: string = getParameter(req.query.wto),
|
||||||
location = req.query.loc,
|
location: string | GeoCoordinates = getParameter(req.query.loc),
|
||||||
outputFormat = req.query.format,
|
outputFormat: string = getParameter(req.query.format),
|
||||||
remoteAddress = req.headers[ "x-forwarded-for" ] || req.connection.remoteAddress,
|
remoteAddress: string = getParameter(req.headers[ "x-forwarded-for" ]) || req.connection.remoteAddress,
|
||||||
|
adjustmentOptions: AdjustmentOptions,
|
||||||
|
|
||||||
// Function that will accept the weather after it is received from the API
|
// 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,
|
// 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: OWMWateringData | TimeData ) {
|
||||||
if ( !weather ) {
|
if ( !weather ) {
|
||||||
if ( typeof location[ 0 ] === "number" && typeof location[ 1 ] === "number" ) {
|
if ( typeof location[ 0 ] === "number" && typeof location[ 1 ] === "number" ) {
|
||||||
const timeData: TimeData = getTimeData( location );
|
const timeData: TimeData = getTimeData( location as GeoCoordinates );
|
||||||
finishRequest( timeData );
|
finishRequest( timeData );
|
||||||
} else {
|
} else {
|
||||||
res.send( "Error: No weather data found." );
|
res.send( "Error: No weather data found." );
|
||||||
@@ -313,16 +329,22 @@ exports.getWateringData = async function( req, res ) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var scale = calculateWeatherScale( adjustmentMethod, adjustmentOptions, weather ),
|
// The OWMWateringData if it exists, or undefined if only TimeData is available
|
||||||
rainDelay = -1;
|
const wateringData: OWMWateringData = validateValues( [ "temp", "humidity", "precip" ], weather ) ? weather as OWMWateringData: undefined;
|
||||||
|
|
||||||
|
|
||||||
|
let scale: number = calculateWeatherScale( adjustmentMethod, adjustmentOptions, weather ),
|
||||||
|
rainDelay: number = -1;
|
||||||
|
|
||||||
|
if (wateringData) {
|
||||||
// Check for any user-set restrictions and change the scale to 0 if the criteria is met
|
// Check for any user-set restrictions and change the scale to 0 if the criteria is met
|
||||||
if ( checkWeatherRestriction( req.params[ 0 ], weather ) ) {
|
if (checkWeatherRestriction(req.params[0], wateringData)) {
|
||||||
scale = 0;
|
scale = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If any weather adjustment is being used, check the rain status
|
// If any weather adjustment is being used, check the rain status
|
||||||
if ( adjustmentMethod > 0 && weather.hasOwnProperty( "raining" ) && weather.raining ) {
|
if ( adjustmentMethod > 0 && wateringData && wateringData.raining ) {
|
||||||
|
|
||||||
// If it is raining and the user has weather-based rain delay as the adjustment method then apply the specified delay
|
// 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 ( adjustmentMethod === 2 ) {
|
||||||
@@ -335,18 +357,19 @@ exports.getWateringData = async function( req, res ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = {
|
const data = {
|
||||||
scale: scale,
|
scale: scale,
|
||||||
rd: rainDelay,
|
rd: rainDelay,
|
||||||
tz: getTimezone( weather.timezone, undefined ),
|
tz: getTimezone( weather.timezone, undefined ),
|
||||||
sunrise: weather.sunrise,
|
sunrise: weather.sunrise,
|
||||||
sunset: weather.sunset,
|
sunset: weather.sunset,
|
||||||
eip: ipToInt( remoteAddress ),
|
eip: ipToInt( remoteAddress ),
|
||||||
|
// TODO this may need to be changed (https://github.com/OpenSprinkler/OpenSprinkler-Weather/pull/11#issuecomment-491037948)
|
||||||
rawData: {
|
rawData: {
|
||||||
h: weather.humidity,
|
h: wateringData ? wateringData.humidity : null,
|
||||||
p: Math.round( weather.precip * 100 ) / 100,
|
p: wateringData ? Math.round( wateringData.precip * 100 ) / 100 : null,
|
||||||
t: Math.round( weather.temp * 10 ) / 10,
|
t: wateringData ? Math.round( wateringData.temp * 10 ) / 10 : null,
|
||||||
raining: weather.raining ? 1 : 0
|
raining: wateringData ? ( wateringData.raining ? 1 : 0 ) : null
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -379,14 +402,14 @@ exports.getWateringData = async function( req, res ) {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
// Parse data that may be encoded
|
// Parse data that may be encoded
|
||||||
adjustmentOptions = decodeURIComponent( adjustmentOptions.replace( /\\x/g, "%" ) );
|
adjustmentOptionsString = decodeURIComponent( adjustmentOptionsString.replace( /\\x/g, "%" ) );
|
||||||
|
|
||||||
// Reconstruct JSON string from deformed controller output
|
// Reconstruct JSON string from deformed controller output
|
||||||
adjustmentOptions = JSON.parse( "{" + adjustmentOptions + "}" );
|
adjustmentOptions = JSON.parse( "{" + adjustmentOptionsString + "}" );
|
||||||
} catch ( err ) {
|
} catch ( err ) {
|
||||||
|
|
||||||
// If the JSON is not valid, do not incorporate weather adjustment options
|
// If the JSON is not valid, do not incorporate weather adjustment options
|
||||||
adjustmentOptions = false;
|
adjustmentOptions = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let coordinates: GeoCoordinates;
|
let coordinates: GeoCoordinates;
|
||||||
@@ -399,8 +422,8 @@ exports.getWateringData = async function( req, res ) {
|
|||||||
} else if ( filters.gps.test( location ) ) {
|
} else if ( filters.gps.test( location ) ) {
|
||||||
|
|
||||||
// Handle GPS coordinates by storing each coordinate in an array
|
// Handle GPS coordinates by storing each coordinate in an array
|
||||||
location = location.split( "," );
|
const splitLocation: string[] = location.split( "," );
|
||||||
coordinates = [ parseFloat( location[ 0 ] ), parseFloat( location[ 1 ] ) ];
|
coordinates = [ parseFloat( splitLocation[ 0 ] ), parseFloat( splitLocation[ 1 ] ) ];
|
||||||
location = coordinates;
|
location = coordinates;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -459,9 +482,14 @@ async function httpRequest( url: string ): Promise< string > {
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks to make sure an array contains the keys provided and returns true or false
|
/**
|
||||||
function validateValues( keys, array ) {
|
* Checks if the specified object contains numeric values for each of the specified keys.
|
||||||
var key;
|
* @param keys A list of keys to validate exist on the specified object.
|
||||||
|
* @param obj The object to check.
|
||||||
|
* @return A boolean indicating if the object has numeric values for all of the specified keys.
|
||||||
|
*/
|
||||||
|
function validateValues( keys: string[], obj: object ): boolean {
|
||||||
|
let key: string;
|
||||||
|
|
||||||
for ( key in keys ) {
|
for ( key in keys ) {
|
||||||
if ( !keys.hasOwnProperty( key ) ) {
|
if ( !keys.hasOwnProperty( key ) ) {
|
||||||
@@ -470,7 +498,7 @@ function validateValues( keys, array ) {
|
|||||||
|
|
||||||
key = keys[ key ];
|
key = keys[ key ];
|
||||||
|
|
||||||
if ( !array.hasOwnProperty( key ) || typeof array[ key ] !== "number" || isNaN( array[ key ] ) || array[ key ] === null || array[ key ] === -999 ) {
|
if ( !obj.hasOwnProperty( key ) || typeof obj[ key ] !== "number" || isNaN( obj[ key ] ) || obj[ key ] === null || obj[ key ] === -999 ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -513,10 +541,14 @@ function getTimezone( time: number | string, useMinutes: boolean = false ): numb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts IP string to integer
|
/**
|
||||||
function ipToInt( ip ) {
|
* Converts an IP address string to an integer.
|
||||||
ip = ip.split( "." );
|
* @param ip The string representation of the IP address.
|
||||||
return ( ( ( ( ( ( +ip[ 0 ] ) * 256 ) + ( +ip[ 1 ] ) ) * 256 ) + ( +ip[ 2 ] ) ) * 256 ) + ( +ip[ 3 ] );
|
* @return The integer representation of the IP address.
|
||||||
|
*/
|
||||||
|
function ipToInt( ip: string ): number {
|
||||||
|
const split = ip.split( "." );
|
||||||
|
return ( ( ( ( ( ( +split[ 0 ] ) * 256 ) + ( +split[ 1 ] ) ) * 256 ) + ( +split[ 2 ] ) ) * 256 ) + ( +split[ 3 ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -584,3 +616,20 @@ interface OWMWateringData extends TimeData {
|
|||||||
/** A boolean indicating if it is currently raining. */
|
/** A boolean indicating if it is currently raining. */
|
||||||
raining: boolean;
|
raining: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AdjustmentOptions {
|
||||||
|
/** Base humidity (as a percentage). */
|
||||||
|
bh?: number;
|
||||||
|
/** Base temperature (in Fahrenheit). */
|
||||||
|
bt?: number;
|
||||||
|
/** Base precipitation (in inches). */
|
||||||
|
br?: number;
|
||||||
|
/** The percentage to weight the humidity factor by. */
|
||||||
|
h?: number;
|
||||||
|
/** The percentage to weight the temperature factor by. */
|
||||||
|
t?: number;
|
||||||
|
/** The percentage to weight the precipitation factor by. */
|
||||||
|
r?: number;
|
||||||
|
/** The rain delay to use (in hours). */
|
||||||
|
d?: number;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user