Rebase on master
This commit is contained in:
20
package-lock.json
generated
20
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "os-weather-service",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -54,6 +54,16 @@
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"@types/cron": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cron/-/cron-1.7.0.tgz",
|
||||
"integrity": "sha512-LRu/XiiOExELholyEwEuSTPAEiO+sVR1nXmWEjezneGgYpDyMNVIsjiaHYBoCEUJo4F1hCOlAzQAh80iEUVbKw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"moment": ">=2.14.0"
|
||||
}
|
||||
},
|
||||
"@types/dotenv": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz",
|
||||
@@ -498,6 +508,14 @@
|
||||
"vary": "^1"
|
||||
}
|
||||
},
|
||||
"cron": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cron/-/cron-1.7.1.tgz",
|
||||
"integrity": "sha512-gmMB/pJcqUVs/NklR1sCGlNYM7TizEw+1gebz20BMc/8bTm/r7QUp3ZPSPlG8Z5XRlvb7qhjEjq/+bdIfUCL2A==",
|
||||
"requires": {
|
||||
"moment-timezone": "^0.5.x"
|
||||
}
|
||||
},
|
||||
"currently-unhandled": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "os-weather-service",
|
||||
"description": "OpenSprinkler Weather Service",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"repository": "https://github.com/OpenSprinkler/Weather-Weather",
|
||||
"scripts": {
|
||||
"test": "mocha --exit test",
|
||||
@@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"cron": "^1.3.0",
|
||||
"dotenv": "^6.2.0",
|
||||
"express": "^4.16.4",
|
||||
"geo-tz": "^4.0.2",
|
||||
@@ -19,6 +20,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.5",
|
||||
"@types/cron": "^1.3.0",
|
||||
"@types/dotenv": "^6.1.1",
|
||||
"@types/express": "^4.16.1",
|
||||
"@types/moment-timezone": "^0.5.12",
|
||||
|
||||
85
routes/local.ts
Normal file
85
routes/local.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import * as express from "express";
|
||||
|
||||
const CronJob = require( "cron" ).CronJob,
|
||||
server = require( "../server.js" ),
|
||||
count = { temp: 0, humidity: 0 };
|
||||
|
||||
let today: PWSStatus = {},
|
||||
yesterday: PWSStatus = {},
|
||||
last_bucket: Date,
|
||||
current_date: Date = new Date();
|
||||
|
||||
function sameDay(d1: Date, d2: Date): boolean {
|
||||
return d1.getFullYear() === d2.getFullYear() &&
|
||||
d1.getMonth() === d2.getMonth() &&
|
||||
d1.getDate() === d2.getDate();
|
||||
}
|
||||
|
||||
exports.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") )) {
|
||||
res.send( "Error: Bad date range\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ( "tempf" in req.query ) && !isNaN( curr = parseFloat( req.query.tempf ) ) && curr !== -9999.0 ) {
|
||||
prev = ( "temp" in today ) ? today.temp : 0;
|
||||
today.temp = ( prev * count.temp + curr ) / ( ++count.temp );
|
||||
}
|
||||
if ( ( "humidity" in req.query ) && !isNaN( curr = parseFloat( req.query.humidity ) ) && curr !== -9999.0 ) {
|
||||
prev = ( "humidity" in today ) ? today.humidity : 0;
|
||||
today.humidity = ( prev * count.humidity + curr ) / ( ++count.humidity );
|
||||
}
|
||||
if ( ( "dailyrainin" in req.query ) && !isNaN( curr = parseFloat( req.query.dailyrainin ) ) && curr !== -9999.0 ) {
|
||||
today.precip = curr;
|
||||
}
|
||||
if ( ( "rainin" in req.query ) && !isNaN( curr = parseFloat( req.query.rainin ) ) && curr > 0 ) {
|
||||
last_bucket = new Date();
|
||||
}
|
||||
|
||||
console.log( "OpenSprinkler Weather Observation: %s", JSON.stringify( req.query ) );
|
||||
|
||||
res.send( "success\n" );
|
||||
};
|
||||
|
||||
exports.useLocalWeather = function(): boolean {
|
||||
return server.pws !== "none" ? true : false;
|
||||
};
|
||||
|
||||
exports.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);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
new CronJob( "0 0 0 * * *", function() {
|
||||
|
||||
yesterday = Object.assign( {}, today );
|
||||
today = Object.assign( {} );
|
||||
count.temp = 0; count.humidity = 0;
|
||||
current_date = new Date();
|
||||
}, null, true );
|
||||
|
||||
|
||||
interface PWSStatus {
|
||||
temp?: number;
|
||||
humidity?: number;
|
||||
precip?: number;
|
||||
}
|
||||
|
||||
export interface LocalWeather extends PWSStatus {
|
||||
raining?: boolean;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as express from "express";
|
||||
import { AdjustmentOptions, GeoCoordinates, TimeData, WateringData, WeatherData } from "../types";
|
||||
|
||||
const http = require( "http" ),
|
||||
local = require( "./local"),
|
||||
SunCalc = require( "suncalc" ),
|
||||
moment = require( "moment-timezone" ),
|
||||
geoTZ = require( "geo-tz" ),
|
||||
@@ -15,21 +17,26 @@ const http = require( "http" ),
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the Weather Underground API to resolve a location name (ZIP code, city name, country name, etc.) to geographic
|
||||
* coordinates.
|
||||
* @param location A zip code or partial city/country name.
|
||||
* Resolves a location description to geographic coordinates.
|
||||
* @param location A partial zip/city/country or a coordinate pair.
|
||||
* @return A promise that will be resolved with the coordinates of the best match for the specified location, or
|
||||
* rejected with an error message if unable to resolve the location.
|
||||
*/
|
||||
async function resolveCoordinates( location: string ): Promise< GeoCoordinates > {
|
||||
|
||||
if ( filters.pws.test( location ) ) {
|
||||
throw "Unable to resolve location";
|
||||
} else if ( filters.gps.test( location ) ) {
|
||||
const split: string[] = location.split( "," );
|
||||
return [ parseFloat( split[ 0 ] ), parseFloat( split[ 1 ] ) ];
|
||||
} else {
|
||||
// Generate URL for autocomplete request
|
||||
const url = "http://autocomplete.wunderground.com/aq?h=0&query=" +
|
||||
encodeURIComponent( location );
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = await getData( url );
|
||||
data = await httpJSONRequest( url );
|
||||
} catch (err) {
|
||||
// If the request fails, indicate no data was found.
|
||||
throw "An API error occurred while attempting to resolve location";
|
||||
@@ -45,15 +52,16 @@ async function resolveCoordinates( location: string ): Promise< GeoCoordinates >
|
||||
// Otherwise, indicate no data was found
|
||||
throw "Unable to resolve location";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an HTTP GET request to the specified URL and parses the JSON response body.
|
||||
* Makes an HTTP/HTTPS GET request to the specified URL and parses the JSON response body.
|
||||
* @param url The URL to fetch.
|
||||
* @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 getData( url: string ): Promise< any > {
|
||||
async function httpJSONRequest(url: string ): Promise< any > {
|
||||
try {
|
||||
const data: string = await httpRequest(url);
|
||||
return JSON.parse(data);
|
||||
@@ -66,17 +74,17 @@ async function getData( 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 OWMWateringData if the API calls succeed, or undefined if an
|
||||
* error occurs while retrieving the weather data.
|
||||
* @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< OWMWateringData > {
|
||||
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 getData( forecastUrl );
|
||||
forecast = await httpJSONRequest( forecastUrl );
|
||||
} catch (err) {
|
||||
// Indicate watering data could not be retrieved if an API error occurs.
|
||||
return undefined;
|
||||
@@ -109,18 +117,18 @@ async function getOWMWateringData( coordinates: GeoCoordinates ): Promise< OWMWa
|
||||
/**
|
||||
* 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 OWMWeatherData if the API calls succeed, or undefined if
|
||||
* an error occurs while retrieving the weather data.
|
||||
* @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< OWMWeatherData > {
|
||||
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 getData( currentUrl );
|
||||
forecast = await getData( forecastDailyUrl );
|
||||
current = await httpJSONRequest( currentUrl );
|
||||
forecast = await httpJSONRequest( forecastDailyUrl );
|
||||
} catch (err) {
|
||||
// Indicate watering data could not be retrieved if an API error occurs.
|
||||
return undefined;
|
||||
@@ -131,7 +139,7 @@ async function getOWMWeatherData( coordinates: GeoCoordinates ): Promise< OWMWea
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const weather: OWMWeatherData = {
|
||||
const weather: WeatherData = {
|
||||
temp: parseInt( current.main.temp ),
|
||||
humidity: parseInt( current.main.humidity ),
|
||||
wind: parseInt( current.wind.speed ),
|
||||
@@ -159,6 +167,15 @@ async function getOWMWeatherData( coordinates: GeoCoordinates ): Promise< OWMWea
|
||||
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.
|
||||
* @return A Promise that will be resolved with WateringData.
|
||||
*/
|
||||
async function getLocalWateringData( coordinates: GeoCoordinates ): Promise< WateringData > {
|
||||
return local.getLocalWeather();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates timezone and sunrise/sunset for the specified coordinates.
|
||||
* @param coordinates The coordinates to use to calculate time data.
|
||||
@@ -189,7 +206,7 @@ function getTimeData( coordinates: GeoCoordinates ): TimeData {
|
||||
* @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.
|
||||
*/
|
||||
function calculateWeatherScale( adjustmentMethod: number, adjustmentOptions: AdjustmentOptions, wateringData: OWMWateringData ): number {
|
||||
function calculateWeatherScale( adjustmentMethod: number, adjustmentOptions: AdjustmentOptions, wateringData: WateringData ): number {
|
||||
|
||||
// Zimmerman method
|
||||
if ( adjustmentMethod === 1 ) {
|
||||
@@ -244,7 +261,7 @@ function calculateWeatherScale( adjustmentMethod: number, adjustmentOptions: Adj
|
||||
* @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 {
|
||||
function checkWeatherRestriction( adjustmentValue: number, weather: WateringData ): boolean {
|
||||
|
||||
const californiaRestriction = ( adjustmentValue >> 7 ) & 1;
|
||||
|
||||
@@ -263,28 +280,23 @@ function checkWeatherRestriction( adjustmentValue: number, weather: OWMWateringD
|
||||
|
||||
exports.getWeatherData = async function( req: express.Request, res: express.Response ) {
|
||||
const location: string = getParameter(req.query.loc);
|
||||
|
||||
if ( !location ) {
|
||||
res.send( "Error: Unable to resolve location" );
|
||||
return;
|
||||
}
|
||||
|
||||
let coordinates: GeoCoordinates;
|
||||
|
||||
if ( filters.gps.test( location ) ) {
|
||||
|
||||
// Handle GPS coordinates by storing each coordinate in an array
|
||||
const split: string[] = location.split( "," );
|
||||
coordinates = [ parseFloat( split[ 0 ] ), parseFloat( split[ 1 ] ) ];
|
||||
} else {
|
||||
|
||||
// Attempt to resolve provided location to GPS coordinates when it does not match
|
||||
// a GPS coordinate or Weather Underground location using Weather Underground autocomplete
|
||||
try {
|
||||
coordinates = await resolveCoordinates( location );
|
||||
} catch (err) {
|
||||
res.send( "Error: Unable to resolve location" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue with the weather request
|
||||
const timeData: TimeData = getTimeData( coordinates );
|
||||
const weatherData: OWMWeatherData = await getOWMWeatherData( coordinates );
|
||||
const weatherData: WeatherData = await getOWMWeatherData( coordinates );
|
||||
|
||||
res.json( {
|
||||
...timeData,
|
||||
@@ -363,7 +375,12 @@ exports.getWateringData = async function( req: express.Request, res: express.Res
|
||||
|
||||
// Continue with the weather request
|
||||
let timeData: TimeData = getTimeData( coordinates );
|
||||
const wateringData: OWMWateringData = await getOWMWateringData( coordinates );
|
||||
let wateringData: WateringData;
|
||||
if ( local.useLocalWeather() ) {
|
||||
wateringData = await getLocalWateringData( coordinates );
|
||||
} else {
|
||||
wateringData = await getOWMWateringData(coordinates);
|
||||
}
|
||||
|
||||
|
||||
// Process data to retrieve the resulting scale, sunrise/sunset, timezone,
|
||||
@@ -420,6 +437,10 @@ exports.getWateringData = async function( req: express.Request, res: express.Res
|
||||
}
|
||||
};
|
||||
|
||||
if ( local.useLocalWeather() ) {
|
||||
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 );
|
||||
@@ -557,70 +578,3 @@ function getParameter( parameter: string | string[] ): string {
|
||||
// Return an empty string if the parameter is undefined.
|
||||
return parameter || "";
|
||||
}
|
||||
|
||||
|
||||
/** Geographic coordinates. The 1st element is the latitude, and the 2nd element is the longitude. */
|
||||
type GeoCoordinates = [number, number];
|
||||
|
||||
interface TimeData {
|
||||
/** The UTC offset, in minutes. This uses POSIX offsets, which are the negation of typically used offsets
|
||||
* (https://github.com/eggert/tz/blob/2017b/etcetera#L36-L42).
|
||||
*/
|
||||
timezone: number;
|
||||
/** The time of sunrise, in minutes from UTC midnight. */
|
||||
sunrise: number;
|
||||
/** The time of sunset, in minutes from UTC midnight. */
|
||||
sunset: number;
|
||||
}
|
||||
|
||||
interface OWMWeatherData {
|
||||
/** The current temperature (in Fahrenheit). */
|
||||
temp: number;
|
||||
/** The current humidity (as a percentage). */
|
||||
humidity: number;
|
||||
wind: number;
|
||||
description: string;
|
||||
icon: string;
|
||||
region: string;
|
||||
city: string;
|
||||
minTemp: number;
|
||||
maxTemp: number;
|
||||
precip: number;
|
||||
forecast: OWMWeatherDataForecast[]
|
||||
}
|
||||
|
||||
interface OWMWeatherDataForecast {
|
||||
temp_min: number;
|
||||
temp_max: number;
|
||||
date: number;
|
||||
icon: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface OWMWateringData {
|
||||
/** The average forecasted temperature over the next 30 hours (in Fahrenheit). */
|
||||
temp: number;
|
||||
/** The average forecasted humidity over the next 30 hours (as a percentage). */
|
||||
humidity: number;
|
||||
/** The forecasted total precipitation over the next 30 hours (in inches). */
|
||||
precip: number;
|
||||
/** A boolean indicating if it is currently raining. */
|
||||
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;
|
||||
}
|
||||
|
||||
27
server.ts
27
server.ts
@@ -1,14 +1,19 @@
|
||||
var express = require( "express" ),
|
||||
const packageJson = require( "../package.json" ),
|
||||
express = require( "express" ),
|
||||
weather = require( "./routes/weather.js" ),
|
||||
cors = require( "cors" ),
|
||||
host = process.env.HOST || "127.0.0.1",
|
||||
local = require( "./routes/local.js" ),
|
||||
cors = require( "cors" );
|
||||
|
||||
let host = process.env.HOST || "127.0.0.1",
|
||||
port = process.env.PORT || 3000,
|
||||
pws = process.env.PWS || "none",
|
||||
app = express();
|
||||
|
||||
if ( !process.env.HOST || !process.env.PORT ) {
|
||||
if ( !process.env.HOST || !process.env.PORT || !process.env.LOCAL_PWS ) {
|
||||
require( "dotenv" ).load();
|
||||
host = process.env.HOST || host;
|
||||
port = process.env.PORT || port;
|
||||
pws = process.env.PWS || pws;
|
||||
}
|
||||
|
||||
// Handle requests matching /weatherID.py where ID corresponds to the
|
||||
@@ -21,8 +26,13 @@ app.get( /(\d+)/, weather.getWateringData );
|
||||
app.options( /weatherData/, cors() );
|
||||
app.get( /weatherData/, cors(), weather.getWeatherData );
|
||||
|
||||
// Endpoint to stream Weather Underground data from local PWS
|
||||
if ( pws === "WU" ) {
|
||||
app.get( "/weatherstation/updateweatherstation.php", local.captureWUStream );
|
||||
}
|
||||
|
||||
app.get( "/", function( req, res ) {
|
||||
res.send( "OpenSprinkler Weather Service" );
|
||||
res.send( packageJson.description + " v" + packageJson.version );
|
||||
} );
|
||||
|
||||
// Handle 404 error
|
||||
@@ -33,7 +43,12 @@ app.use( function( req, res ) {
|
||||
|
||||
// Start listening on the service port
|
||||
app.listen( port, host, function() {
|
||||
console.log( "OpenSprinkler Weather Service now listening on %s:%s", host, port );
|
||||
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;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
},
|
||||
"include": [
|
||||
"server.ts",
|
||||
"types.ts",
|
||||
"routes/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
65
types.ts
Normal file
65
types.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/** Geographic coordinates. The 1st element is the latitude, and the 2nd element is the longitude. */
|
||||
export type GeoCoordinates = [number, number];
|
||||
|
||||
export interface TimeData {
|
||||
/** The UTC offset, in minutes. This uses POSIX offsets, which are the negation of typically used offsets
|
||||
* (https://github.com/eggert/tz/blob/2017b/etcetera#L36-L42).
|
||||
*/
|
||||
timezone: number;
|
||||
/** The time of sunrise, in minutes from UTC midnight. */
|
||||
sunrise: number;
|
||||
/** The time of sunset, in minutes from UTC midnight. */
|
||||
sunset: number;
|
||||
}
|
||||
|
||||
export interface WeatherData {
|
||||
/** The current temperature (in Fahrenheit). */
|
||||
temp: number;
|
||||
/** The current humidity (as a percentage). */
|
||||
humidity: number;
|
||||
wind: number;
|
||||
description: string;
|
||||
icon: string;
|
||||
region: string;
|
||||
city: string;
|
||||
minTemp: number;
|
||||
maxTemp: number;
|
||||
precip: number;
|
||||
forecast: WeatherDataForecast[]
|
||||
}
|
||||
|
||||
export interface WeatherDataForecast {
|
||||
temp_min: number;
|
||||
temp_max: number;
|
||||
date: number;
|
||||
icon: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface WateringData {
|
||||
/** The average forecasted temperature over the next 30 hours (in Fahrenheit). */
|
||||
temp: number;
|
||||
/** The average forecasted humidity over the next 30 hours (as a percentage). */
|
||||
humidity: number;
|
||||
/** The forecasted total precipitation over the next 30 hours (in inches). */
|
||||
precip: number;
|
||||
/** A boolean indicating if it is currently raining. */
|
||||
raining: boolean;
|
||||
}
|
||||
|
||||
export 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