diff --git a/README.md b/README.md index 5c635fa..aed2106 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,6 @@ $ npm install node-red-contrib-dutch-weather For all nodes, you'll need to create at least one configuration. Drag one of the exposed nodes to your flow and set it up just like all other config nodes. After that, you can use the event emitters in your own code. -## Release 3.0.0 - -* Fixed issue where `prediction` field was not present in rain-data payload. -* Node `dutch-weather-meteoplaza` was **removed**: Meteoplaza has been acquired by Buienradar and the original API used in this code is no longer accessible. You should decide yourself if you are going to keep using this node for rain prediction (since it is still based on both Buienalarm and Buienradar) - - ## Release 2.0.7 * Added 'lastUpdate' field. @@ -40,7 +34,7 @@ For all nodes, you'll need to create at least one configuration. Drag one of the ## Release 2.0.3 -* Node `dutch-weather-sun-position` (and) `dutch-weather-solar-events` **removed**: There is a much better node-red node you can use: [node-red-contrib-sun-position](https://flows.nodered.org/node/node-red-contrib-sun-position/) +* `dutch-weather-sun-position` (and) `dutch-weather-solar-events` **removed**: There is a much better node-red node you can use: [node-red-contrib-sun-position](https://flows.nodered.org/node/node-red-contrib-sun-position/) * **Automatic refresh removed**: The javascript timers sometimes stopped working, which in the worst case led to rain being reported while it was already dry for days; since Node-red has timer nodes built in, i've removed the javascript timers from the code completely. In order to get updated data you have to send a message on the input of a node **with payload** `{ trigger: true }` for it to update. ## Release 2.0.0 diff --git a/dutch-weather.html b/dutch-weather.html index 0b0cf15..0598c7c 100644 --- a/dutch-weather.html +++ b/dutch-weather.html @@ -68,4 +68,43 @@ return 'Rain state'; } }) + + + + + + + + + + + \ No newline at end of file diff --git a/dutch-weather.js b/dutch-weather.js index e875813..3c7dc84 100644 --- a/dutch-weather.js +++ b/dutch-weather.js @@ -60,4 +60,31 @@ module.exports = function(RED) { }); } RED.nodes.registerType("dutch-weather-rain-state", dutchWeatherRainState); + + + + /** + * Meteo events node + */ + function dutchWeatherMeteoplaza(n) { + RED.nodes.createNode(this, n); + this.conf = RED.nodes.getNode(n.conf); + + if (!this.conf) { + return null; + } + + var node = this; + this.conf.weatherLogic.on('meteoplaza', function (meteoplaza) { + node.send({ 'topic': 'meteoplaza', 'payload': meteoplaza }); + }); + + node.on('input', function(msg) { + if (msg && msg.hasOwnProperty('payload') && (msg.payload.trigger == true)) { + node.conf.weatherLogic.updateMeteoplaza(true); + } + }); + + } + RED.nodes.registerType("dutch-weather-meteoplaza", dutchWeatherMeteoplaza); } diff --git a/lib/Meteoplaza.js b/lib/Meteoplaza.js new file mode 100644 index 0000000..11ee6a7 --- /dev/null +++ b/lib/Meteoplaza.js @@ -0,0 +1,143 @@ +'use strict'; + +const Fetch = require('node-fetch'); +const Moment = require('moment'); + +module.exports = class Meteoplaza { + + constructor(lat, lng) { + this.lat = parseFloat(lat).toFixed(2) * 100; + this.lng = parseFloat(lng).toFixed(2) * 100; + this.forecast = { updated: null, parsed: null }; + } + + getHourlyData(json) { + return { + "icon": json.WXCO_EXTENDED, + "temp": json.TTTT, + "tempFeelsLike": json.FEELS_LIKE, + "sunProbability": parseFloat(json.SSSP), + "precipitation": json.RRRR * ((json.TTTT < 0) ? 10 : 1), + "pressure": json.PPPP, + "humidity": json.RHRH, + "cloudCoverage": parseFloat(json.NNNN), + "wind": { + "direction": json.DDDD, + "speed": json.FFFF, + "speedMs": json.FFFF_MS, + "speedKmh": json.FFFF_KMH + } + }; + } + + async update() { + // Check if we still have data + var now = new Date(); + var getNewForecast = true; + + // Data is only updated once every 10 minutes, doesn't make sense to retrieve it before it changes, so we take 245 seconds (4 minutes and 5 seconds) + if (this.forecast.updated != null) { + var rounded = new Date(Math.ceil(this.forecast.updated.getTime() / 604800) * 604800); + getNewForecast = (now >= rounded); + } + + if (this.forecast.parsed == null) { + getNewForecast = true; + } + + if (!getNewForecast) { + //Helper.Debug('getForecasts()::Used cached data'); + return this.forecast.parsed; + } + + try { + // Fetch new data + var url = `https://api.meteoplaza.com/v2/meteo/completelocation/${this.lat}.${this.lng}?lang=nl`; + const response = await Fetch(url); + const json = await response.json(); + let today = now.getUTCFullYear() + '-' + ('0' + (now.getMonth() + 1)).slice(-2) + '-' + ('0' + now.getDate()).slice(-2); + + // Parse if needed + //Helper.Debug('getForecasts()::Got new data - ' + url); + + // Parse the forecasts + let startTime = new Date(Moment(json.start_human, 'HH:mm')); + + // Parse if needed + var newData = this.forecast.parsed; + if (newData == null) { + newData = { + "now": { + "temp": null, + "tempFeelsLike": null, + "pressure": null, + "humidity": null, + "wind": { + "direction": '', + "speed": null, + "speedMs": null, + "speedKmh": null + } + }, + "today": { + "tempMin": null, + "tempMax": null, + "astro": { + "currentTime": null, + "sunRise": null, + "sunSet": null, + "dayLength": null, + "dayLengthDiff": null, + "moonPhase":{ + "newMoon": null, + "fullMoon": null, + "firstQuarter": null, + "lastQuarter": null + } + }, + "dataPerHour": new Map() + } + }; + } + + // Astro events + newData.today.astro ={ + "currentTime": json.Astro.CurrentTime_ISO, + "sunRise": json.Astro.SunRise_ISO, + "sunSet": json.Astro.SunSet_ISO, + "dayLengthHrs": json.Astro.DayLength, + "moonPhase":{ + "newMoon": json.Astro.MoonPhase.NM, + "fullMoon": json.Astro.MoonPhase.VM, + "firstQuarter": json.Astro.MoonPhase.EK, + "lastQuarter": json.Astro.MoonPhase.LK + } + }; + + // Update hourly temperatures in case something changes + json.Hourly.forEach((hour) => { + if (hour.ValidDt.substring(0, 10) == today) { + var key = hour.ValidDt.substring(11, 13); + newData.today.dataPerHour[key] = this.getHourlyData(hour); + + // Calculate min/max temps + newData.today.tempMin = (newData.today.tempMin == null) ? newData.today.dataPerHour[key].temp : Math.min(newData.today.tempMin, newData.today.dataPerHour[key].temp); + newData.today.tempMax = (newData.today.tempMax == null) ? newData.today.dataPerHour[key].temp : Math.max(newData.today.tempMax, newData.today.dataPerHour[key].temp); + } + }); + + // Get current data + var currentHr = json.Astro.CurrentTime.substr(0,2); + newData.now = newData.today.dataPerHour[currentHr]; + + // Set new data + this.forecast.parsed = newData; + this.forecast.updated = Moment(response.headers.Date).toDate(); + } catch(e) { + console.log(e); + } + + // Save & parse forcecast + return this.forecast.parsed; + } +} \ No newline at end of file diff --git a/lib/WeatherLogic.js b/lib/WeatherLogic.js index 35cd53d..961f391 100644 --- a/lib/WeatherLogic.js +++ b/lib/WeatherLogic.js @@ -8,6 +8,7 @@ const isEqual = require('lodash.isequal'); const Buienalarm = require('./Buienalarm'); const Buienradar = require('./Buienradar'); +const Meteoplaza = require('./Meteoplaza'); const RAIN_LIGHT = 0.2; const RAIN_MODERATE = 1; @@ -24,10 +25,13 @@ class WeatherLogic extends EventEmitter { this.rainTimer = null; this.rainState = null; + this.meteoplaza = null; + this.meteoplazaTimer = null; // Initialize providers this.Buienalarm = new Buienalarm(lat, lng); this.Buienradar = new Buienradar(lat, lng); + this.Meteoplaza = new Meteoplaza(lat, lng); } /** @@ -171,7 +175,6 @@ class WeatherLogic extends EventEmitter { var tempState = { lastUpdate: after }; // Loop over possibilities for rain starting or stopping in the next 120 minutes - var stateAtTime; for (let i = 0; i < 23; i++) { // Calculate minutes let inMinutes = i * 5; @@ -204,7 +207,7 @@ class WeatherLogic extends EventEmitter { } // First loop we set the 'current' state - stateAtTime = this.rainStatePayload(atTime, inMinutes, buienradarPrecipitation, buienalarmPrecipitation); + var stateAtTime = this.rainStatePayload(atTime, inMinutes, buienradarPrecipitation, buienalarmPrecipitation); if (!tempState.hasOwnProperty('now')) { tempState.now = stateAtTime; } @@ -218,28 +221,27 @@ class WeatherLogic extends EventEmitter { // Only emit in case we have data if (tempState.hasOwnProperty('now')) { - // If we still don't have a 'prediction' then that means that, according to our data, nothing will change. - if (!tempState.hasOwnProperty('prediction')) { - tempState.prediction = stateAtTime; - } - - // Fix the prediction message - var dt = this.iso8601dateTime(atTime); - var msg = (tempState.prediction.probability == 100) ? 'It ' : 'There is a ' + tempState.prediction.probability + '% chance that it '; - msg+= ((tempState.prediction.inMinutes > 0) ? 'will be' : 'is') + ' ' + tempState.prediction.state; - msg+= (tempState.now.state === tempState.prediction.state) ? ' for at least ' : ' in '; - if (tempState.prediction.inMinutes > 0) { - msg+= tempState.prediction.inMinutes + ' minutes from now, '; - msg+= ((tempState.now.state === tempState.prediction.state) ? 'untill' : 'at') + ' '; - msg+= dt.substring(0, 10) + ' at ' + dt.substring(11, 19); - } - msg+= '.'; - tempState.prediction.message = msg; - - // Send updates this.rainStateUpdate(tempState, force || false); } } + + /** + * Retreives new information from Meteoplaza + * + * @param boolean Force update? + */ + async updateMeteoplaza(force) { + force = force || false; + let lastUpdated = null; + if (this.meteoplaza !== null) + lastUpdated = this.meteoplaza.updated; + this.meteoplaza = await this.Meteoplaza.update(); + + // Notify in case we have a new update + if (force || (lastUpdated == null) || (this.meteoplaza.updated > lastUpdated)) { + this.emit('meteoplaza', this.meteoplaza); + } + } } module.exports = WeatherLogic; diff --git a/local-test.js b/local-test.js index b8c7bc5..e7cd8d6 100644 --- a/local-test.js +++ b/local-test.js @@ -56,13 +56,15 @@ console.log('* Watchting the weather ...'); var Weather = new WeatherLogic(51.42408, 5.442794); Weather.on('rain-state', function (prediction) { - if (prediction === null) { - console.log('* Rain state update: invalid result!'); - return; - } console.log('* Rain state update:'); - console.log(' - ' + JSON.stringify(prediction, null, '\t')); - + console.log(' - ' + JSON.stringify(prediction)); +}); + +Weather.on('meteoplaza', function (meteoplaza) { + console.log('* Meteoplaza update:'); + console.log(' - ' + JSON.stringify(meteoplaza)); }); +Weather.startMonitor(); setTimeout(function() { Weather.checkRain(); }, 1000); +setTimeout(function() { Weather.updateMeteoplaza(); }, 1000);