From 35af6849b6091cbdc61e0dcc34ab738092c5efd7 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 4 Jun 2023 15:49:10 +0930 Subject: [PATCH] Update format of weather resource entries --- helper/alarms/alarms.ts | 29 ++-- helper/index.ts | 22 ++- helper/openApi.json | 295 ++++++++++++++++++++-------------------- helper/weather.ts | 78 +++++------ 4 files changed, 210 insertions(+), 214 deletions(-) diff --git a/helper/alarms/alarms.ts b/helper/alarms/alarms.ts index 261bf6a9..b1097665 100644 --- a/helper/alarms/alarms.ts +++ b/helper/alarms/alarms.ts @@ -45,14 +45,7 @@ export const initAlarms = (app: FreeboardHelperApp, id: string) => { initAlarmEndpoints(); - /* - curl -H "Content-Type: application/json" -X PUT -d '{"message": "Man Overboard!"}'\ - http://localhost:3000/signalk/v2/api/notifications/mob - - curl -H "Content-Type: application/json" -X DELETE \ - http://localhost:3000/signalk/v2/api/notifications/mob - */ -}; +} const initAlarmEndpoints = () => { server.debug(`** Registering Alarm Action API endpoint(s) **`); @@ -67,13 +60,15 @@ const initAlarmEndpoints = () => { return; } try { + const msg = req.body.message + ? req.body.message + : (req.params.alarmType as string); + const r = handlePutAlarmState( 'vessels.self', `notifications.${req.params.alarmType}`, { - message: req.body.message - ? req.body.message - : (req.params.alarmType as string), + message: msg, method: [ALARM_METHOD.sound, ALARM_METHOD.visual], state: ALARM_STATE.emergency } @@ -145,7 +140,7 @@ const handlePutAlarmState = ( if (value) { noti = new Notification( alarmType, - value.message ?? '', + buildAlarmMessage(value.message, alarmType), value.state ?? null, value.method ?? null ); @@ -170,6 +165,16 @@ const handlePutAlarmState = ( } }; +const buildAlarmMessage = (message: string, alarmType?: string): string => { + let msgAttrib = ''; + if (['mob', 'sinking'].includes(alarmType)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const pos: any = server.getSelfPath('navigation.position'); + msgAttrib = pos ? JSON.stringify(pos?.value) : ''; + } + return `${message}\n\r${msgAttrib}`; +}; + // ** send notification delta message ** const emitNotification = (n: Notification | DeltaMessage) => { const msg = n instanceof Notification ? n.message : n; diff --git a/helper/index.ts b/helper/index.ts index 2e9ae459..1fd4dd83 100644 --- a/helper/index.ts +++ b/helper/index.ts @@ -105,12 +105,14 @@ const CONFIG_UISCHEMA = { }; interface SETTINGS { + // eslint-disable-next-line @typescript-eslint/no-explicit-any alarms: { [key: string]: any }; weather: WEATHER_CONFIG; pypilot: PYPILOT_CONFIG; } interface OpenApiPlugin extends Plugin { + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOpenApi: () => any; } @@ -126,11 +128,14 @@ export interface FreeboardHelperApp setProviderStatus: (providerId: string, status?: string) => void; setProviderError: (providerId: string, status?: string) => void; getSelfPath: (path: string) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any savePluginOptions: (options: any, callback: () => void) => void; config: { configPath: string }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any handleMessage: (id: string | null, msg: any, version?: string) => void; streambundle: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any getSelfBus: (path: string | void) => any; }; registerPutHandler: ( @@ -139,6 +144,7 @@ export interface FreeboardHelperApp callback: ( context: string, path: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any, actionResultCallback: (actionResult: ActionResult) => void ) => ActionResult @@ -169,6 +175,7 @@ module.exports = (server: FreeboardHelperApp): OpenApiPlugin => { name: 'Freeboard-SK', schema: () => CONFIG_SCHEMA, uiSchema: () => CONFIG_UISCHEMA, + // eslint-disable-next-line @typescript-eslint/no-explicit-any start: (settings: any) => { doStartup(settings); }, @@ -219,12 +226,14 @@ module.exports = (server: FreeboardHelperApp): OpenApiPlugin => { initAlarms(server, plugin.id); } - const result = registerProvider('weather'); - const msg = `Started - ${ - result.length !== 0 ? `${result} not registered!` : 'Providing: weather' - }`; - + let msg = ''; if (settings.weather.enable) { + const result = registerProvider('weather'); + msg = `Started - ${ + result.length !== 0 + ? `${result} not registered!` + : 'Providing: weather' + }`; initWeather(server, plugin.id, settings.weather); } if (settings.pypilot.enable) { @@ -232,6 +241,7 @@ module.exports = (server: FreeboardHelperApp): OpenApiPlugin => { } server.setPluginStatus(msg); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { const msg = 'Started with errors!'; server.setPluginError(msg); @@ -256,12 +266,14 @@ module.exports = (server: FreeboardHelperApp): OpenApiPlugin => { server.registerResourceProvider({ type: resType, methods: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any listResources: (params: object): any => { return listWeather(params); }, getResource: (path: string, property?: string) => { return getWeather(path, property); }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any setResource: (id: string, value: any) => { throw 'Not implemented!'; }, diff --git a/helper/openApi.json b/helper/openApi.json index 43954c31..f8ff8655 100644 --- a/helper/openApi.json +++ b/helper/openApi.json @@ -15,13 +15,17 @@ }, "servers": [ { - "url": "/signalk/v2/api/resources/weather" + "url": "/signalk/v2/api/resources" } ], "tags": [ { "name": "weather", - "description": "Weather resources" + "description": "Weather resources." + }, + { + "name": "stations", + "description": "Weather station data." } ], "components": { @@ -40,33 +44,6 @@ } } }, - "DescriptionAttribute": { - "type": "object", - "required": ["description"], - "properties": { - "description": { - "type": "string" - } - } - }, - "PositionAttribute": { - "type": "object", - "required": ["position"], - "properties": { - "position": { - "description": "Resource location.", - "example": { - "latitude": 65.4567, - "longitude": 3.3452 - }, - "allOf": [ - { - "$ref": "#/components/schemas/SignalKPosition" - } - ] - } - } - }, "IsoTime": { "type": "string", "pattern": "^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2}(?:\\.\\d*)?)((-(\\d{2}):(\\d{2})|Z)?)$", @@ -95,12 +72,12 @@ ] } }, - "WeatherStationResponseModel": { + "WeatherStationInfoModel": { "type": "object", "properties": { "id": { "type": "string", - "example": "self" + "example": "12345678" }, "name": { "type": "string", @@ -116,8 +93,7 @@ "required": ["timestamp"], "properties": { "timestamp": { - "$ref": "#/components/schemas/IsoTime", - "description": "Forecast period." + "$ref": "#/components/schemas/IsoTime" }, "description": { "type": "string", @@ -197,49 +173,51 @@ } } } - }, - "additionalProperties": { - "anyOf": [ - { "$ref": "#/components/schemas/ValueGroup"}, - { "$ref": "#/components/schemas/NumberValue"} - ] } }, - "ObservationResponseModel": { + "ObservationDataModel": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseStationResponseModel" + }, + { + "$ref": "#/components/schemas/ObservationProperties" + } + ] + }, + "ObservationProperties": { "type": "object", "properties": { - "anyOf": [ - { - "$ref": "#/components/schemas/BaseStationResponseModel" - } - ], "sunrise": { - "$ref": "#/components/schemas/IsoTime", - "description": "Sunrise time." + "$ref": "#/components/schemas/IsoTime" }, "sunset": { - "$ref": "#/components/schemas/IsoTime", - "description": "Sunset time." + "$ref": "#/components/schemas/IsoTime" }, "visibility": { "$ref": "#/components/schemas/NumberValue" } } }, - "ForecastResponseModel": { + "ForecastDataModel": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseStationResponseModel" + }, + { + "$ref": "#/components/schemas/ForecastProperties" + } + ] + }, + "ForecastProperties": { "type": "object", "properties": { - "anyOf": [ - { - "$ref": "#/components/schemas/BaseStationResponseModel" - } - ], "pop": { "$ref": "#/components/schemas/NumberValue" } } }, - "WarningResponseModel": { + "WarningDataModel": { "type": "object", "required": ["startTime","endTime"], "properties": { @@ -264,6 +242,58 @@ "example": "HEAT ADVISORY REMAINS IN EFFECT FROM 1 PM THIS AFTERNOON...." } } + }, + "ObservationsAttrib": { + "type": "object", + "required": ["observations"], + "properties": { + "observations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseStationResponseModel" + } + } + } + }, + "ForecastsAttrib": { + "type": "object", + "required": ["forecasts"], + "properties": { + "forecasts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseStationResponseModel" + } + } + } + }, + "WarningsAttrib": { + "type": "object", + "required": ["warnings"], + "properties": { + "warnings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WarningDataModel" + } + } + } + }, + "WeatherStationResponseModel": { + "allOf": [ + { + "$ref": "#/components/schemas/WeatherStationInfoModel" + }, + { + "$ref": "#/components/schemas/WarningsAttrib" + }, + { + "$ref": "#/components/schemas/ObservationsAttrib" + }, + { + "$ref": "#/components/schemas/ForecastsAttrib" + } + ] } }, "responses": { @@ -294,6 +324,15 @@ } }, "parameters": { + "StationIdParam": { + "in": "path", + "required": true, + "name": "id", + "description": "Weather station identifier.", + "schema": { + "type": "string" + } + } }, "securitySchemes": { "bearerAuth": { @@ -310,65 +349,19 @@ }, "security": [{ "cookieAuth": [] }, { "bearerAuth": [] }], "paths": { - "/": { + "/weather": { "get": { "tags": ["weather"], "summary": "Retrieve list of weather stations providing forecast, observation & warning information", "responses": { "default": { - "description": "List of weather stations by information type.", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["observations","forecasts","warnings"], - "properties": { - "observations": { - "allOf": [ - { - "$ref": "#/components/schemas/DescriptionAttribute" - } - ] - }, - "forecasts": { - "allOf": [ - { - "$ref": "#/components/schemas/DescriptionAttribute" - } - ] - }, - "warnings": { - "allOf": [ - { - "$ref": "#/components/schemas/DescriptionAttribute" - } - ] - } - } - } - } - } - } - } - } - }, - "/weather/observations": { - "get": { - "tags": ["observations"], - "summary": "List weather stations providing observation information.", - "responses": { - "default": { - "description": "List of weather stations identified by id.", + "description": "List of weather stations by identified by id.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": { - "allOf": [ - { - "$ref": "#/components/schemas/WeatherStationResponseModel" - } - ] + "$ref": "#/components/schemas/WeatherStationInfoModel" } } } @@ -377,20 +370,22 @@ } } }, - "/weather/observations/{id}": { + "/weather/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/StationIdParam" + } + ], "get": { - "tags": ["observations"], - "summary": "List weather stations providing observation data.", + "tags": ["stations"], + "summary": "Weather station data.", "responses": { "default": { - "description": "Observation data from identified weather station.", + "description": "Data from weather station with the supplied identifier.", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ObservationResponseModel" - } + "$ref": "#/components/schemas/WeatherStationResponseModel" } } } @@ -398,13 +393,18 @@ } } }, - "/weather/forecasts": { + "/weather/{id}/observations": { + "parameters": [ + { + "$ref": "#/components/parameters/StationIdParam" + } + ], "get": { - "tags": ["forecasts"], - "summary": "List weather stations providing forecast information.", + "tags": ["stations"], + "summary": "Weather station observation data.", "responses": { "default": { - "description": "List of weather stations identified by id.", + "description": "Observation data from weather station with the supplied identifier.", "content": { "application/json": { "schema": { @@ -412,7 +412,10 @@ "additionalProperties": { "allOf": [ { - "$ref": "#/components/schemas/WeatherStationResponseModel" + "$ref": "#/components/schemas/BaseStationResponseModel" + }, + { + "$ref": "#/components/schemas/ObservationProperties" } ] } @@ -423,34 +426,18 @@ } } }, - "/weather/forecasts/{id}": { - "get": { - "tags": ["forecasts"], - "summary": "Weather stations forecast data.", - "responses": { - "default": { - "description": "Forecast data from identified weather station.", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ForecastResponseModel" - } - } - } - } - } + "/weather/{id}/forecasts": { + "parameters": [ + { + "$ref": "#/components/parameters/StationIdParam" } - } - }, - "/weather/warnings": { + ], "get": { - "tags": ["warnings"], - "summary": "List weather stations providing warning information.", + "tags": ["stations"], + "summary": "Weather station forecast data.", "responses": { "default": { - "description": "List of weather stations identified by id.", + "description": "Forecast data from weather station with the supplied identifier.", "content": { "application/json": { "schema": { @@ -458,7 +445,10 @@ "additionalProperties": { "allOf": [ { - "$ref": "#/components/schemas/WeatherStationResponseModel" + "$ref": "#/components/schemas/BaseStationResponseModel" + }, + { + "$ref": "#/components/schemas/ForecastProperties" } ] } @@ -469,19 +459,24 @@ } } }, - "/weather/warnings/{id}": { + "/weather/{id}/warnings": { + "parameters": [ + { + "$ref": "#/components/parameters/StationIdParam" + } + ], "get": { - "tags": ["warnings"], - "summary": "Weather stations warning data.", + "tags": ["stations"], + "summary": "Weather station warning data.", "responses": { "default": { - "description": "Warning data from identified weather station.", + "description": "Warnings from weather station with the supplied identifier.", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/WarningResponseModel" + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/WarningDataModel" } } } diff --git a/helper/weather.ts b/helper/weather.ts index f45c8be3..35027b85 100644 --- a/helper/weather.ts +++ b/helper/weather.ts @@ -17,6 +17,7 @@ interface SKWeatherValue { } interface SKWeatherGroup { + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: SKWeatherValue | SKWeatherGroup | any; } @@ -57,7 +58,9 @@ let pluginId: string; let lastFetch: number; // last successful fetch const fetchInterval = 3600000; // 1hr +// eslint-disable-next-line @typescript-eslint/no-explicit-any let timer: any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any let retryTimer: any; const retryCountMax = 5; // max number of retries on failed api connection let retryCount = 0; // number of retries on failed api connection @@ -93,67 +96,47 @@ export const stopWeather = () => { lastFetch = fetchInterval - 1; }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const listWeather = async (params: any): Promise => { - server.debug(`getWeather ${params}`); - return { - observations: { - description: 'Weather stations providing observation data.' - }, - forecasts: { - description: 'Weather stations providing forecast data.' - }, - warnings: { - description: 'Weather stations providing warning information.' + server.debug(`getWeather ${JSON.stringify(params)}`); + const res = {}; + if (weatherData) { + for (const o in weatherData) { + const { id, name, position } = weatherData[o]; + res[o] = { id, name, position }; } - }; + } + return res; }; export const getWeather = async ( path: string, property: string + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise => { server.debug(`getWeather ${path}, ${property}`); - if (!property) { - if (weatherData) { - const stations = JSON.parse(JSON.stringify(weatherData)); - for (const o in stations) { - if (stations[o][path]) { - delete stations[o].observations; - delete stations[o].forecasts; - delete stations[o].warnings; - } else { - delete stations[o]; - } - } - return stations; - } else { - return {}; - } + + if (!weatherData) { + return {}; + } + + const station = weatherData[path]; + if (!station) { + throw `Weather station ${path} not found!`; + } + + if (property) { + const value = property.split('.').reduce((acc, val) => { + return acc[val]; + }, station); + return value ?? {}; } else { - if (weatherData) { - const p = property.split('.'); - const stationId = p[0]; - p.shift(); - if (stationId in weatherData) { - if (weatherData[stationId][path]) { - const res = p.reduce((acc, val) => { - acc = acc[val]; - return acc; - }, weatherData[stationId][path]); - return res ?? {}; - } else { - return {}; - } - } else { - throw `${stationId} not found!`; - } - } else { - return {}; - } + return station; } }; const fetchWeatherData = () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const pos: any = server.getSelfPath('navigation.position'); if (!pos) { server.debug(`*** Weather: No vessel position detected!`); @@ -216,6 +199,7 @@ const checkForWarnings = () => { // emit weather warning notification const emitWarningNotification = (warning?: SKWeatherWarning) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any let delta: any; if (warning) { server.debug(`** Setting Notification **`);