Refactor adjustment method selection
This commit is contained in:
58
routes/adjustmentMethods/AdjustmentMethod.ts
Normal file
58
routes/adjustmentMethods/AdjustmentMethod.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { GeoCoordinates, WateringData } from "../../types";
|
||||
import { WeatherProvider } from "../weatherProviders/WeatherProvider";
|
||||
|
||||
|
||||
export interface AdjustmentMethod {
|
||||
/**
|
||||
* Calculates the percentage that should be used to scale watering time.
|
||||
* @param adjustmentOptions The user-specified options for the calculation, or undefined/null if no custom values
|
||||
* are to be used. No checks will be made to ensure the AdjustmentOptions are the correct type that the function
|
||||
* is expecting or to ensure that any of its fields are valid.
|
||||
* @param wateringData The basic weather information of the watering site. This may be undefined if an error occurred
|
||||
* while retrieving the data.
|
||||
* @param coordinates The coordinates of the watering site.
|
||||
* @param weatherProvider The WeatherProvider that should be used if the adjustment method needs to obtain any more
|
||||
* weather data.
|
||||
* @return A Promise that will be resolved with the result of the calculation, or rejected with an error message if
|
||||
* the watering scale cannot be calculated.
|
||||
* @throws An error message can be thrown if an error occurs while calculating the watering scale.
|
||||
*/
|
||||
calculateWateringScale(
|
||||
adjustmentOptions: AdjustmentOptions,
|
||||
wateringData: WateringData | undefined,
|
||||
coordinates: GeoCoordinates,
|
||||
weatherProvider: WeatherProvider
|
||||
): Promise< AdjustmentMethodResponse >;
|
||||
}
|
||||
|
||||
export interface AdjustmentMethodResponse {
|
||||
/**
|
||||
* The percentage that should be used to scale the watering level. This should be an integer between 0-200 (inclusive),
|
||||
* or undefined if the watering level should not be changed.
|
||||
*/
|
||||
scale: number | undefined;
|
||||
/**
|
||||
* The raw data that was used to calculate the watering scale. This will be sent directly to the OS controller, so
|
||||
* each field should be formatted in a way that the controller understands and numbers should be rounded
|
||||
* appropriately to remove excessive figures. If no data was used (e.g. an error occurred), this should be undefined.
|
||||
*/
|
||||
rawData?: object;
|
||||
/**
|
||||
* How long watering should be delayed for (in hours) due to rain, or undefined if watering should not be delayed
|
||||
* for a specific amount of time (either it should be delayed indefinitely or it should not be delayed at all). This
|
||||
* property will not stop watering on its own, and the `scale` property should be set to 0 to actually prevent
|
||||
* watering.
|
||||
*/
|
||||
rainDelay?: number;
|
||||
// TODO consider removing this field and breaking backwards compatibility to handle all errors consistently.
|
||||
/**
|
||||
* An message to send to the OS firmware to indicate that an error occurred while calculating the watering
|
||||
* scale and the returned scale either defaulted to some reasonable value or was calculated with incomplete data.
|
||||
* Older firmware versions will ignore this field (they will silently swallow the error and use the returned scale),
|
||||
* but newer firmware versions may be able to alert the user that an error occurred and/or default to a
|
||||
* user-configured watering scale instead of using the one returned by the AdjustmentMethod.
|
||||
*/
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface AdjustmentOptions {}
|
||||
17
routes/adjustmentMethods/ManualAdjustmentMethod.ts
Normal file
17
routes/adjustmentMethods/ManualAdjustmentMethod.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { AdjustmentMethod, AdjustmentMethodResponse } from "./AdjustmentMethod";
|
||||
|
||||
|
||||
/**
|
||||
* Does not change the watering scale (only time data will be returned).
|
||||
*/
|
||||
async function calculateManualWateringScale( ): Promise< AdjustmentMethodResponse > {
|
||||
return {
|
||||
scale: undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const ManualAdjustmentMethod: AdjustmentMethod = {
|
||||
calculateWateringScale: calculateManualWateringScale
|
||||
};
|
||||
export default ManualAdjustmentMethod;
|
||||
27
routes/adjustmentMethods/RainDelayAdjustmentMethod.ts
Normal file
27
routes/adjustmentMethods/RainDelayAdjustmentMethod.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { AdjustmentMethod, AdjustmentMethodResponse, AdjustmentOptions } from "./AdjustmentMethod";
|
||||
import { WateringData } from "../../types";
|
||||
|
||||
|
||||
/**
|
||||
* Only delays watering if it is currently raining and does not adjust the watering scale.
|
||||
*/
|
||||
async function calculateRainDelayWateringScale( adjustmentOptions: RainDelayAdjustmentOptions, wateringData: WateringData | undefined ): Promise< AdjustmentMethodResponse > {
|
||||
const raining = wateringData && wateringData.raining;
|
||||
const d = adjustmentOptions && adjustmentOptions.hasOwnProperty( "d" ) ? adjustmentOptions.d : 24;
|
||||
return {
|
||||
scale: undefined,
|
||||
rawData: { raining: raining ? 1 : 0 },
|
||||
rainDelay: raining ? d : undefined
|
||||
}
|
||||
}
|
||||
|
||||
export interface RainDelayAdjustmentOptions extends AdjustmentOptions {
|
||||
/** The rain delay to use (in hours). */
|
||||
d?: number;
|
||||
}
|
||||
|
||||
|
||||
const RainDelayAdjustmentMethod: AdjustmentMethod = {
|
||||
calculateWateringScale: calculateRainDelayWateringScale
|
||||
};
|
||||
export default RainDelayAdjustmentMethod;
|
||||
94
routes/adjustmentMethods/ZimmermanAdjustmentMethod.ts
Normal file
94
routes/adjustmentMethods/ZimmermanAdjustmentMethod.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { AdjustmentMethod, AdjustmentMethodResponse, AdjustmentOptions } from "./AdjustmentMethod";
|
||||
import { WateringData } from "../../types";
|
||||
import { validateValues } from "../weather";
|
||||
|
||||
|
||||
/**
|
||||
* Calculates how much watering should be scaled based on weather and adjustment options using the Zimmerman method.
|
||||
* (https://github.com/rszimm/sprinklers_pi/wiki/Weather-adjustments#formula-for-setting-the-scale)
|
||||
*/
|
||||
async function calculateZimmermanWateringScale( adjustmentOptions: ZimmermanAdjustmentOptions, wateringData: WateringData | undefined ): Promise< AdjustmentMethodResponse > {
|
||||
|
||||
// Temporarily disabled since OWM forecast data is checking if rain is forecasted for 3 hours in the future.
|
||||
/*
|
||||
// Don't water if it is currently raining.
|
||||
if ( wateringData && wateringData.raining ) {
|
||||
return {
|
||||
scale: 0,
|
||||
rawData: { raining: 1 }
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const rawData = {
|
||||
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
|
||||
};
|
||||
|
||||
// Check to make sure valid data exists for all factors
|
||||
if ( !validateValues( [ "temp", "humidity", "precip" ], wateringData ) ) {
|
||||
// Default to a scale of 100% if fields are missing.
|
||||
return {
|
||||
scale: 100,
|
||||
rawData: rawData,
|
||||
errorMessage: "Necessary field(s) were missing from WateringData."
|
||||
};
|
||||
}
|
||||
|
||||
let humidityBase = 30, tempBase = 70, precipBase = 0;
|
||||
|
||||
// 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 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
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// Apply all of the weather modifying factors and clamp the result between 0 and 200%.
|
||||
scale: Math.floor( Math.min( Math.max( 0, 100 + humidityFactor + tempFactor + precipFactor ), 200 ) ),
|
||||
rawData: rawData
|
||||
}
|
||||
}
|
||||
|
||||
export interface ZimmermanAdjustmentOptions extends 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;
|
||||
}
|
||||
|
||||
|
||||
const ZimmermanAdjustmentMethod: AdjustmentMethod = {
|
||||
calculateWateringScale: calculateZimmermanWateringScale
|
||||
};
|
||||
export default ZimmermanAdjustmentMethod;
|
||||
Reference in New Issue
Block a user