Merge pull request #24 from Derpthemeus/darksky

Add support for the Dark Sky API
This commit is contained in:
Samer Albahra
2019-05-13 15:38:29 -05:00
committed by GitHub
8 changed files with 277 additions and 146 deletions

View File

@@ -52,21 +52,37 @@ pi@OSPi:~ $ git clone https://github.com/OpenSprinkler/OpenSprinkler-Weather.git
pi@OSPi:~ $ cd weather pi@OSPi:~ $ cd weather
pi@OSPi:~/weather $ npm install 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. **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 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 HOST=0.0.0.0
PORT=3000 PORT=3000
OWM_API_KEY=<YOUR 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=<YOUR OWM 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=<YOUR DARK SKY KEY>
```
**Step 6:** Setup the Weather Server to start whenever the Raspberry Pi boots up using the built-in service manager: **Step 6:** Setup the Weather Server to start whenever the Raspberry Pi boots up using the built-in service manager:
``` ```

View File

@@ -1,8 +1,9 @@
import * as express from "express"; import * as express from "express";
import { CronJob } from "cron";
const CronJob = require( "cron" ).CronJob, import * as server from "../server";
server = require( "../server.js" ),
count = { temp: 0, humidity: 0 }; const count = { temp: 0, humidity: 0 };
let today: PWSStatus = {}, let today: PWSStatus = {},
yesterday: PWSStatus = {}, yesterday: PWSStatus = {},
@@ -15,7 +16,7 @@ function sameDay(d1: Date, d2: Date): boolean {
d1.getDate() === d2.getDate(); 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; let prev: number, curr: number;
if ( !( "dateutc" in req.query ) || !sameDay( current_date, new Date( req.query.dateutc + "Z") )) { 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" ); res.send( "success\n" );
}; };
exports.useLocalWeather = function(): boolean { export const useLocalWeather = function(): boolean {
return server.pws !== "none" ? true : false; return server.pws !== "none" ? true : false;
}; };
exports.getLocalWeather = function(): LocalWeather { export const getLocalWeather = function(): LocalWeather {
const result: LocalWeather = {}; const result: LocalWeather = {};
// Use today's weather if we dont have information for yesterday yet (i.e. on startup) // Use today's weather if we dont have information for yesterday yet (i.e. on startup)

View File

@@ -1,14 +1,16 @@
import * as express from "express"; import * as express from "express";
import { AdjustmentOptions, GeoCoordinates, TimeData, WateringData, WeatherData } from "../types"; 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";
const http = require( "http" ), import * as local from "./local";
local = require( "./local"), import { AdjustmentOptions, GeoCoordinates, TimeData, WateringData, WeatherData, WeatherProvider } from "../types";
SunCalc = require( "suncalc" ), const weatherProvider: WeatherProvider = require("./weatherProviders/" + ( process.env.WEATHER_PROVIDER || "OWM" ) ).default;
moment = require( "moment-timezone" ),
geoTZ = require( "geo-tz" ),
// Define regex filters to match against location // Define regex filters to match against location
filters = { const filters = {
gps: /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/, gps: /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/,
pws: /^(?:pws|icao|zmw):/, pws: /^(?:pws|icao|zmw):/,
url: /^https?:\/\/([\w\.-]+)(:\d+)?(\/.*)?$/, url: /^https?:\/\/([\w\.-]+)(:\d+)?(\/.*)?$/,
@@ -61,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 * @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. * 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 { try {
const data: string = await httpRequest(url); const data: string = await httpRequest(url);
return JSON.parse(data); return JSON.parse(data);
@@ -71,109 +73,14 @@ 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. * Retrieves weather data necessary for watering level calculations from the a local record.
* @param coordinates The coordinates to retrieve the watering data for. * @param coordinates The coordinates to retrieve the watering data for.
* @return A Promise that will be resolved with WateringData. * @return A Promise that will be resolved with WateringData.
*/ */
async function getLocalWateringData( coordinates: GeoCoordinates ): Promise< WateringData > { async function getLocalWateringData( coordinates: GeoCoordinates ): Promise< WateringData > {
return local.getLocalWeather(); // TODO is this type assertion safe?
return local.getLocalWeather() as WateringData;
} }
/** /**
@@ -278,7 +185,7 @@ function checkWeatherRestriction( adjustmentValue: number, weather: WateringData
return false; 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); const location: string = getParameter(req.query.loc);
if ( !location ) { if ( !location ) {
@@ -296,7 +203,7 @@ exports.getWeatherData = async function( req: express.Request, res: express.Resp
// Continue with the weather request // Continue with the weather request
const timeData: TimeData = getTimeData( coordinates ); const timeData: TimeData = getTimeData( coordinates );
const weatherData: WeatherData = await getOWMWeatherData( coordinates ); const weatherData: WeatherData = await weatherProvider.getWeatherData( coordinates );
res.json( { res.json( {
...timeData, ...timeData,
@@ -308,7 +215,7 @@ 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: 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 // 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
@@ -379,7 +286,7 @@ exports.getWateringData = async function( req: express.Request, res: express.Res
if ( local.useLocalWeather() ) { if ( local.useLocalWeather() ) {
wateringData = await getLocalWateringData( coordinates ); wateringData = await getLocalWateringData( coordinates );
} else { } else {
wateringData = await getOWMWateringData(coordinates); wateringData = await weatherProvider.getWateringData(coordinates);
} }
@@ -430,7 +337,7 @@ exports.getWateringData = async function( req: express.Request, res: express.Res
eip: ipToInt( remoteAddress ), eip: ipToInt( remoteAddress ),
// TODO this may need to be changed (https://github.com/OpenSprinkler/OpenSprinkler-Weather/pull/11#issuecomment-491037948) // TODO this may need to be changed (https://github.com/OpenSprinkler/OpenSprinkler-Weather/pull/11#issuecomment-491037948)
rawData: { rawData: {
h: wateringData ? wateringData.humidity : null, h: wateringData ? Math.round( wateringData.humidity * 100) / 100 : null,
p: wateringData ? Math.round( wateringData.precip * 100 ) / 100 : null, p: wateringData ? Math.round( wateringData.precip * 100 ) / 100 : null,
t: wateringData ? Math.round( wateringData.temp * 10 ) / 10 : null, t: wateringData ? Math.round( wateringData.temp * 10 ) / 10 : null,
raining: wateringData ? ( wateringData.raining ? 1 : 0 ) : null raining: wateringData ? ( wateringData.raining ? 1 : 0 ) : null
@@ -458,7 +365,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. * @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 * @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. * Error if the request fails.
@@ -467,14 +374,15 @@ async function httpRequest( url: string ): Promise< string > {
return new Promise< any >( ( resolve, reject ) => { return new Promise< any >( ( resolve, reject ) => {
const splitUrl: string[] = url.match( filters.url ); const splitUrl: string[] = url.match( filters.url );
const isHttps = url.startsWith("https");
const options = { const options = {
host: splitUrl[ 1 ], host: splitUrl[ 1 ],
port: splitUrl[ 2 ] || 80, port: splitUrl[ 2 ] || ( isHttps ? 443 : 80 ),
path: splitUrl[ 3 ] path: splitUrl[ 3 ]
}; };
http.get( options, ( response ) => { ( isHttps ? https : http ).get( options, ( response ) => {
let data = ""; let data = "";
// Reassemble the data as it comes in // Reassemble the data as it comes in

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,18 +1,22 @@
const packageJson = require( "../package.json" ), import * as express from "express";
express = require( "express" ), import * as cors from "cors";
weather = require( "./routes/weather.js" ), import * as dotenv from "dotenv";
local = require( "./routes/local.js" ),
cors = require( "cors" ); 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", let host = process.env.HOST || "127.0.0.1",
port = process.env.PORT || 3000, port = parseInt( process.env.PORT ) || 3000;
pws = process.env.PWS || "none",
app = express(); export let pws = process.env.PWS || "none";
export const app = express();
if ( !process.env.HOST || !process.env.PORT || !process.env.LOCAL_PWS ) { if ( !process.env.HOST || !process.env.PORT || !process.env.LOCAL_PWS ) {
require( "dotenv" ).config(); dotenv.config();
host = process.env.HOST || host; host = process.env.HOST || host;
port = process.env.PORT || port; port = parseInt( process.env.PORT ) || port;
pws = process.env.PWS || pws; pws = process.env.PWS || pws;
} }
@@ -43,12 +47,9 @@ app.use( function( req, res ) {
// Start listening on the service port // 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 ); console.log( "%s now listening on %s:%d", packageJson.description, host, port );
if (pws !== "none" ) { if (pws !== "none" ) {
console.log( "%s now listening for local weather stream", packageJson.description ); console.log( "%s now listening for local weather stream", packageJson.description );
} }
} ); } );
exports.app = app;
exports.pws = pws;

View File

@@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es6", "target": "es5",
"noImplicitReturns": true, "noImplicitReturns": true,
"noEmitOnError": true, "noEmitOnError": true,
"outDir": "js/", "outDir": "js/",

View File

@@ -17,22 +17,34 @@ export interface WeatherData {
temp: number; temp: number;
/** The current humidity (as a percentage). */ /** The current humidity (as a percentage). */
humidity: number; humidity: number;
/** The current wind speed (in miles per hour). */
wind: number; wind: number;
/** A human-readable description of the weather. */
description: string; description: string;
/** An icon ID that represents the current weather. This will be used in http://openweathermap.org/img/w/<ICON_ID>.png */
icon: string; icon: string;
region: string; region: string;
city: string; city: string;
/** The forecasted minimum temperature for the current day (in Fahrenheit). */
minTemp: number; minTemp: number;
/** The forecasted minimum temperature for the current day (in Fahrenheit). */
maxTemp: number; maxTemp: number;
/** The forecasted total precipitation for the current day (in inches). */
precip: number; precip: number;
forecast: WeatherDataForecast[] forecast: WeatherDataForecast[]
} }
/** The forecasted weather for a specific day in the future. */
export interface WeatherDataForecast { export interface WeatherDataForecast {
/** The forecasted minimum temperature for this day (in Fahrenheit). */
temp_min: number; temp_min: number;
/** The forecasted maximum temperature for this day (in Fahrenheit). */
temp_max: number; temp_max: number;
/** The timestamp of the day this forecast is for (in Unix epoch seconds). */
date: number; date: number;
/** An icon ID that represents the weather at this forecast window. This will be used in http://openweathermap.org/img/w/<ICON_ID>.png */
icon: string; icon: string;
/** A human-readable description of the weather. */
description: string; description: string;
} }
@@ -63,3 +75,21 @@ export interface AdjustmentOptions {
/** The rain delay to use (in hours). */ /** The rain delay to use (in hours). */
d?: number; 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 >;
}