@signalk/freeboard-sk 2.19.0-beta.1 → 2.19.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,175 +0,0 @@
1
- "use strict";
2
- // OpenWeather
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.OpenWeather = void 0;
5
- const fetch_1 = require("../lib/fetch");
6
- const weather_service_1 = require("./weather-service");
7
- class OpenWeather {
8
- settings;
9
- constructor(config) {
10
- this.settings = config;
11
- }
12
- getUrl(position) {
13
- const v2 = 'https://api.openweathermap.org/data/2.5/onecall';
14
- const v3 = 'https://api.openweathermap.org/data/3.0/onecall';
15
- const api = this.settings.apiVersion === 3 ? v3 : v2;
16
- if (!this.settings.apiKey || !position) {
17
- return '';
18
- }
19
- else {
20
- return `${api}?lat=${position.latitude}&lon=${position.longitude}&exclude=minutely,daily&appid=${this.settings.apiKey}`;
21
- }
22
- }
23
- fetchData = async (position) => {
24
- const url = this.getUrl(position);
25
- const response = await (0, fetch_1.fetch)(url);
26
- if ('cod' in response) {
27
- throw new Error(response.message);
28
- }
29
- else {
30
- return this.parseResponse(response);
31
- }
32
- };
33
- parseResponse = (owData) => {
34
- const res = {};
35
- res[weather_service_1.defaultStationId] = {
36
- id: weather_service_1.defaultStationId,
37
- name: 'Weather data relative to supplied position.',
38
- position: {
39
- latitude: owData.lat,
40
- longitude: owData.lon
41
- },
42
- observations: this.parseOWObservations(owData),
43
- forecasts: this.parseOWForecasts(owData),
44
- warnings: this.parseOWWarnings(owData)
45
- };
46
- return res;
47
- };
48
- parseOWObservations(owData) {
49
- //server.debug(JSON.stringify(weatherData.current))
50
- const data = [];
51
- let obs;
52
- if (owData && owData.current) {
53
- const current = owData.current;
54
- obs = {
55
- date: current.dt
56
- ? new Date(current.dt * 1000).toISOString()
57
- : new Date().toISOString(),
58
- description: current.weather[0].description ?? '',
59
- sun: {
60
- sunrise: new Date(current.sunrise * 1000).toISOString() ?? null,
61
- sunset: new Date(current.sunset * 1000).toISOString() ?? null
62
- },
63
- outside: {
64
- uvIndex: current.uvi ?? null,
65
- cloudCover: current.clouds ? current.clouds / 100 : null,
66
- horizontalVisibility: current.visibility ?? null,
67
- temperature: current.temp ?? null,
68
- feelsLikeTemperature: current.feels_like ?? null,
69
- dewPointTemperature: current.dew_point ?? null,
70
- pressure: current.pressure ? current.pressure * 100 : null,
71
- absoluteHumidity: current.humidity ? current.humidity / 100 : null,
72
- precipitationType: current.rain && typeof current.rain['1h'] !== 'undefined'
73
- ? 'rain'
74
- : current.snow && typeof current.snow['1h'] !== 'undefined'
75
- ? 'snow'
76
- : null,
77
- precipitationVolume: current.rain && typeof current.rain['1h'] !== 'undefined'
78
- ? current.rain['1h']
79
- : current.snow && typeof current.snow['1h'] !== 'undefined'
80
- ? current.snow['1h']
81
- : null
82
- },
83
- water: {},
84
- wind: {
85
- speedTrue: current.wind_speed ?? null,
86
- directionTrue: typeof current.wind_deg !== 'undefined'
87
- ? (Math.PI / 180) * current.wind_deg
88
- : null
89
- }
90
- };
91
- data.push(obs);
92
- }
93
- return data;
94
- }
95
- parseOWForecasts(owData, period = 'hourly') {
96
- //server.debug(JSON.stringify(owData[period]))
97
- const data = [];
98
- if (owData && owData[period] && Array.isArray(owData[period])) {
99
- const forecasts = owData[period];
100
- forecasts.forEach((f) => {
101
- const forecast = {
102
- date: f.dt
103
- ? new Date(f.dt * 1000).toISOString()
104
- : new Date().toISOString(),
105
- description: f.weather[0].description ?? '',
106
- sun: {},
107
- outside: {},
108
- water: {},
109
- wind: {}
110
- };
111
- if (period === 'daily') {
112
- forecast.sun.sunrise =
113
- new Date(f.sunrise * 1000).toISOString() ?? null;
114
- forecast.sun.sunset = new Date(f.sunset * 1000).toISOString() ?? null;
115
- forecast.outside.minTemperature = f.temp.min ?? null;
116
- forecast.outside.maxTemperature = f.temp.max ?? null;
117
- forecast.outside.feelsLikeTemperature = f.feels_like.day ?? null;
118
- }
119
- else {
120
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
121
- forecast.outside.feelsLikeTemperature = f.feels_like ?? null;
122
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
- forecast.outside.temperature = f.temp ?? null;
124
- }
125
- forecast.outside.dewPointTemperature = f.dew_point ?? null;
126
- forecast.outside.uvIndex = f.uvi ?? null;
127
- forecast.outside.cloudCover = f.clouds ? f.clouds / 100 : null;
128
- forecast.outside.pressure =
129
- typeof f.pressure !== 'undefined' ? f.pressure * 100 : null;
130
- forecast.outside.absoluteHumidity = f.humidity
131
- ? f.humidity / 100
132
- : null;
133
- forecast.wind.speedTrue = f.wind_speed ?? null;
134
- forecast.wind.directionTrue =
135
- typeof f.wind_deg !== 'undefined'
136
- ? (Math.PI / 180) * f.wind_deg
137
- : null;
138
- forecast.wind.gust = f.wind_gust ?? null;
139
- if (f.rain && typeof f.rain['1h'] !== 'undefined') {
140
- forecast.outside.precipitationType = 'rain';
141
- forecast.outside.precipitationVolume = f.rain['1h'] ?? null;
142
- }
143
- else if (f.snow && typeof f.snow['1h'] !== 'undefined') {
144
- forecast.outside.precipitationType = 'snow';
145
- forecast.outside.precipitationVolume = f.snow['1h'] ?? null;
146
- }
147
- data.push(forecast);
148
- });
149
- }
150
- return data;
151
- }
152
- parseOWWarnings(owData) {
153
- //server.debug(JSON.stringify(weatherData.alerts))
154
- const data = [];
155
- if (owData && owData.alerts) {
156
- const alerts = owData.alerts;
157
- alerts.forEach((alert) => {
158
- const warn = {
159
- startTime: alert.start
160
- ? new Date(alert.start * 1000).toISOString()
161
- : null,
162
- endTime: alert.end
163
- ? new Date(alert.start * 1000).toISOString()
164
- : null,
165
- details: alert.description ?? null,
166
- source: alert.sender_name ?? null,
167
- type: alert.event ?? null
168
- };
169
- data.push(warn);
170
- });
171
- }
172
- return data;
173
- }
174
- }
175
- exports.OpenWeather = OpenWeather;
@@ -1,413 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getWeather = exports.listWeather = exports.stopWeather = exports.initWeather = exports.WEATHER_POLL_INTERVAL = exports.defaultStationId = void 0;
4
- // **** Experiment: OpenWeather integration ****
5
- const server_api_1 = require("@signalk/server-api");
6
- const openweather_1 = require("./openweather");
7
- // default weather station context
8
- exports.defaultStationId = `freeboard-sk`;
9
- let server;
10
- let pluginId;
11
- const wakeInterval = 60000;
12
- let lastWake; // last wake time
13
- let lastFetch; // last successful fetch
14
- let fetchInterval = 3600000; // 1hr
15
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
- let timer;
17
- const errorCountMax = 5; // max number of consecutive errors before terminating timer
18
- let errorCount = 0; // number of consecutive fetch errors (no position / failed api connection, etc)
19
- let weatherData;
20
- let weatherService;
21
- let weatherServiceName;
22
- exports.WEATHER_POLL_INTERVAL = [60, 30, 15];
23
- const initWeather = (app, id, config) => {
24
- server = app;
25
- pluginId = id;
26
- fetchInterval = (config.pollInterval ?? 60) * 60000;
27
- if (isNaN(fetchInterval)) {
28
- fetchInterval = 60 * 60000;
29
- }
30
- server.debug(`*** Weather: settings: ${JSON.stringify(config)}`);
31
- server.debug(`*** fetchInterval: ${fetchInterval}`);
32
- weatherService = new openweather_1.OpenWeather(config);
33
- weatherServiceName = 'openweather';
34
- initMeteoEndpoints();
35
- if (!timer) {
36
- server.debug(`*** Weather: startTimer..`);
37
- timer = setInterval(() => fetchWeatherData(), wakeInterval);
38
- }
39
- fetchWeatherData();
40
- };
41
- exports.initWeather = initWeather;
42
- /** Initialise API endpoints */
43
- const initMeteoEndpoints = () => {
44
- const meteoPath = '/signalk/v2/api/meteo';
45
- server.get(`${meteoPath}`, async (req, res) => {
46
- server.debug(`${req.method} ${meteoPath}`);
47
- const r = await (0, exports.listWeather)({});
48
- res.status(200);
49
- res.json(r);
50
- });
51
- server.get(`${meteoPath}/:id`, async (req, res) => {
52
- server.debug(`${req.method} ${meteoPath}/:id`);
53
- const r = weatherData && weatherData[req.params.id]
54
- ? weatherData[req.params.id]
55
- : {};
56
- res.status(200);
57
- res.json(r);
58
- });
59
- server.get(`${meteoPath}/:id/observations`, async (req, res) => {
60
- server.debug(`${req.method} ${meteoPath}/:id/observations`);
61
- const r = weatherData &&
62
- weatherData[req.params.id] &&
63
- weatherData[req.params.id].observations
64
- ? weatherData[req.params.id].observations
65
- : {};
66
- res.status(200);
67
- res.json(r);
68
- });
69
- server.get(`${meteoPath}/:id/observations/:index`, async (req, res) => {
70
- server.debug(`${req.method} ${meteoPath}/:id/observations/:index`);
71
- const r = weatherData &&
72
- weatherData[req.params.id] &&
73
- weatherData[req.params.id].observations &&
74
- weatherData[req.params.id].observations[req.params.index]
75
- ? weatherData[req.params.id].observations[req.params.index]
76
- : {};
77
- res.status(200);
78
- res.json(r);
79
- });
80
- server.get(`${meteoPath}/:id/forecasts`, async (req, res) => {
81
- server.debug(`${req.method} ${meteoPath}/:id/forecasts`);
82
- const r = weatherData &&
83
- weatherData[req.params.id] &&
84
- weatherData[req.params.id].forecasts
85
- ? weatherData[req.params.id].forecasts
86
- : {};
87
- res.status(200);
88
- res.json(r);
89
- });
90
- server.get(`${meteoPath}/:id/forecasts/:index`, async (req, res) => {
91
- server.debug(`${req.method} ${meteoPath}/:id/forecasts/:index`);
92
- const r = weatherData &&
93
- weatherData[req.params.id] &&
94
- weatherData[req.params.id].forecasts &&
95
- weatherData[req.params.id].forecasts[req.params.index]
96
- ? weatherData[req.params.id].forecasts[req.params.index]
97
- : {};
98
- res.status(200);
99
- res.json(r);
100
- });
101
- server.get(`${meteoPath}/:id/warnings`, async (req, res) => {
102
- server.debug(`${req.method} ${meteoPath}/:id/warnings`);
103
- const r = weatherData &&
104
- weatherData[req.params.id] &&
105
- weatherData[req.params.id].warnings
106
- ? weatherData[req.params.id].warnings
107
- : {};
108
- res.status(200);
109
- res.json(r);
110
- });
111
- server.get(`${meteoPath}/:id/warnings/:index`, async (req, res) => {
112
- server.debug(`${req.method} ${meteoPath}/:id/warnings/:index`);
113
- const r = weatherData &&
114
- weatherData[req.params.id] &&
115
- weatherData[req.params.id].warnings &&
116
- weatherData[req.params.id].warnings[req.params.index]
117
- ? weatherData[req.params.id].warnings[req.params.index]
118
- : {};
119
- res.status(200);
120
- res.json(r);
121
- });
122
- };
123
- /** stop weather service */
124
- const stopWeather = () => {
125
- stopTimer();
126
- lastFetch = fetchInterval - 1;
127
- };
128
- exports.stopWeather = stopWeather;
129
- /** stop interval timer */
130
- const stopTimer = () => {
131
- if (timer) {
132
- server.debug(`*** Weather: Stopping timer.`);
133
- clearInterval(timer);
134
- }
135
- };
136
- /**
137
- * Handle fetch errors
138
- * @param msg mesgage to log
139
- */
140
- const handleError = (msg) => {
141
- console.log(msg);
142
- errorCount++;
143
- if (errorCount >= errorCountMax) {
144
- // max retries exceeded.... going to sleep
145
- console.log(`*** Weather: Failed to fetch data after ${errorCountMax} attempts.\nRestart ${pluginId} plugin to retry.`);
146
- stopTimer();
147
- }
148
- else {
149
- console.log(`*** Weather: Error count = ${errorCount} of ${errorCountMax}`);
150
- console.log(`*** Retry in ${wakeInterval / 1000} seconds.`);
151
- }
152
- };
153
- /** Fetch weather data from provider */
154
- const fetchWeatherData = () => {
155
- server.debug(`*** Weather: fetchWeatherData()`);
156
- // runaway check
157
- if (lastWake) {
158
- const dt = Date.now() - lastWake;
159
- const flagValue = wakeInterval - 10000;
160
- if (dt < flagValue) {
161
- server.debug(`Watchdog -> Awake!...(${dt / 1000} secs)... stopping timer...`);
162
- stopTimer();
163
- server.setPluginError('Weather timer stopped by watchdog!');
164
- return;
165
- }
166
- }
167
- lastWake = Date.now();
168
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
169
- const pos = server.getSelfPath('navigation.position');
170
- if (!pos) {
171
- handleError(`*** Weather: No vessel position detected!`);
172
- return;
173
- }
174
- server.debug(`*** Vessel position: ${JSON.stringify(pos.value)}.`);
175
- // check if fetchInterval has lapsed
176
- if (lastFetch) {
177
- const e = Date.now() - lastFetch;
178
- if (e < fetchInterval) {
179
- server.debug(`*** Weather: Next poll due in ${Math.round((fetchInterval - e) / 60000)} min(s)... sleeping for ${wakeInterval / 1000} seconds...`);
180
- return;
181
- }
182
- }
183
- if (errorCount < errorCountMax) {
184
- server.debug(`*** Weather: Calling service API.....`);
185
- server.debug(`Position: ${JSON.stringify(pos.value)}`);
186
- server.debug(`*** Weather: polling weather provider.`);
187
- weatherService
188
- .fetchData(pos.value)
189
- .then((data) => {
190
- server.debug(`*** Weather: data received....`);
191
- server.debug(JSON.stringify(data));
192
- errorCount = 0;
193
- lastFetch = Date.now();
194
- lastWake = Date.now();
195
- weatherData = data;
196
- emitMeteoDeltas();
197
- checkForWarnings();
198
- })
199
- .catch((err) => {
200
- handleError(`*** Weather: ERROR polling weather provider!`);
201
- console.log(err.message);
202
- server.setPluginError(err.message);
203
- });
204
- }
205
- };
206
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
207
- const listWeather = async (params) => {
208
- server.debug(`getWeather ${JSON.stringify(params)}`);
209
- const res = {};
210
- if (weatherData) {
211
- for (const o in weatherData) {
212
- const { id, name, position } = weatherData[o];
213
- res[o] = { id, name, position };
214
- }
215
- }
216
- return res;
217
- };
218
- exports.listWeather = listWeather;
219
- const getWeather = async (path, property
220
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
221
- ) => {
222
- server.debug(`getWeather ${path}, ${property}`);
223
- if (!weatherData) {
224
- return {};
225
- }
226
- const station = weatherData[path];
227
- if (!station) {
228
- throw `Weather station ${path} not found!`;
229
- }
230
- if (property) {
231
- const value = property.split('.').reduce((acc, val) => {
232
- return acc[val];
233
- }, station);
234
- return value ?? {};
235
- }
236
- else {
237
- return station;
238
- }
239
- };
240
- exports.getWeather = getWeather;
241
- // check for weather warnings in returned data
242
- const checkForWarnings = () => {
243
- if ('defaultStationId' in weatherData) {
244
- if (weatherData[exports.defaultStationId].warnings &&
245
- Array.isArray(weatherData[exports.defaultStationId].warnings)) {
246
- server.debug(`*** No. Warnings ${weatherData[exports.defaultStationId].warnings.length}`);
247
- if (weatherData[exports.defaultStationId].warnings.length !== 0) {
248
- emitWarningNotification(weatherData[exports.defaultStationId].warnings[0]);
249
- }
250
- else {
251
- emitWarningNotification();
252
- }
253
- }
254
- else {
255
- emitWarningNotification();
256
- }
257
- }
258
- };
259
- // emit weather warning notification
260
- const emitWarningNotification = (warning) => {
261
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
262
- let delta;
263
- if (warning) {
264
- server.debug(`** Setting Notification **`);
265
- server.debug(JSON.stringify(warning));
266
- delta = {
267
- path: 'notifications.meteo.warning',
268
- value: {
269
- state: server_api_1.ALARM_STATE.warn,
270
- method: [server_api_1.ALARM_METHOD.visual],
271
- message: warning.details
272
- ? warning.details
273
- : warning.type ?? warning.source
274
- }
275
- };
276
- }
277
- else {
278
- server.debug(`** Clearing Notification **`);
279
- delta = {
280
- path: 'notifications.meteo.warning',
281
- value: {
282
- state: server_api_1.ALARM_STATE.normal,
283
- method: [],
284
- message: ''
285
- }
286
- };
287
- }
288
- server.handleMessage(pluginId, {
289
- context: `meteo.${exports.defaultStationId}`,
290
- updates: [{ values: [delta] }]
291
- }, server_api_1.SKVersion.v2);
292
- };
293
- // Meteo methods
294
- const emitMeteoDeltas = () => {
295
- const pathRoot = 'environment';
296
- const deltaValues = [];
297
- server.debug('**** METEO - emit deltas*****');
298
- if (weatherData) {
299
- deltaValues.push({
300
- path: 'navigation.position',
301
- value: weatherData[exports.defaultStationId].position
302
- });
303
- const obs = weatherData[exports.defaultStationId].observations;
304
- server.debug('**** METEO *****');
305
- if (obs && Array.isArray(obs)) {
306
- server.debug('**** METEO OBS *****');
307
- obs.forEach((o) => {
308
- deltaValues.push({
309
- path: ``,
310
- value: { name: weatherServiceName }
311
- });
312
- if (typeof o.date !== 'undefined') {
313
- deltaValues.push({
314
- path: `${pathRoot}.date`,
315
- value: o.date
316
- });
317
- }
318
- if (typeof o.outside.horizontalVisibility !== 'undefined') {
319
- deltaValues.push({
320
- path: `${pathRoot}.outside.horizontalVisibility`,
321
- value: o.outside.horizontalVisibility
322
- });
323
- }
324
- if (typeof o.sun.sunrise !== 'undefined') {
325
- deltaValues.push({
326
- path: `${pathRoot}.sun.sunrise`,
327
- value: o.sun.sunrise
328
- });
329
- }
330
- if (typeof o.sun.sunset !== 'undefined') {
331
- deltaValues.push({
332
- path: `${pathRoot}.sun.sunset`,
333
- value: o.sun.sunset
334
- });
335
- }
336
- if (typeof o.outside.uvIndex !== 'undefined') {
337
- deltaValues.push({
338
- path: `${pathRoot}.outside.uvIndex`,
339
- value: o.outside.uvIndex
340
- });
341
- }
342
- if (typeof o.outside.cloudCover !== 'undefined') {
343
- deltaValues.push({
344
- path: `${pathRoot}.outside.cloudCover`,
345
- value: o.outside.cloudCover
346
- });
347
- }
348
- if (typeof o.outside.temperature !== 'undefined') {
349
- deltaValues.push({
350
- path: `${pathRoot}.outside.temperature`,
351
- value: o.outside.temperature
352
- });
353
- }
354
- if (typeof o.outside.dewPointTemperature !== 'undefined') {
355
- deltaValues.push({
356
- path: `${pathRoot}.outside.dewPointTemperature`,
357
- value: o.outside.dewPointTemperature
358
- });
359
- }
360
- if (typeof o.outside.feelsLikeTemperature !== 'undefined') {
361
- deltaValues.push({
362
- path: `${pathRoot}.outside.feelsLikeTemperature`,
363
- value: o.outside.feelsLikeTemperature
364
- });
365
- }
366
- if (typeof o.outside.pressure !== 'undefined') {
367
- deltaValues.push({
368
- path: `${pathRoot}.outside.pressure`,
369
- value: o.outside.pressure
370
- });
371
- }
372
- if (typeof o.outside.relativeHumidity !== 'undefined') {
373
- deltaValues.push({
374
- path: `${pathRoot}.outside.relativeHumidity`,
375
- value: o.outside.relativeHumidity
376
- });
377
- }
378
- if (typeof o.outside.absoluteHumidity !== 'undefined') {
379
- deltaValues.push({
380
- path: `${pathRoot}.outside.absoluteHumidity`,
381
- value: o.outside.absoluteHumidity
382
- });
383
- }
384
- if (typeof o.outside.precipitationType !== 'undefined') {
385
- deltaValues.push({
386
- path: `${pathRoot}.outside.precipitationType`,
387
- value: o.outside.precipitationType
388
- });
389
- }
390
- if (typeof o.wind.speedTrue !== 'undefined') {
391
- deltaValues.push({
392
- path: `${pathRoot}.wind.speedTrue`,
393
- value: o.wind.speedTrue
394
- });
395
- }
396
- if (typeof o.wind.directionTrue !== 'undefined') {
397
- deltaValues.push({
398
- path: `${pathRoot}.wind.directionTrue`,
399
- value: o.wind.directionTrue
400
- });
401
- }
402
- });
403
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
404
- const updates = {
405
- values: deltaValues
406
- };
407
- server.handleMessage(pluginId, {
408
- context: `meteo.${exports.defaultStationId}`,
409
- updates: [updates]
410
- }, server_api_1.SKVersion.v1);
411
- }
412
- }
413
- };