From 9632777c8e744b1d44c0b79de41211201f1a2ee5 Mon Sep 17 00:00:00 2001 From: chasenicholl Date: Wed, 1 Nov 2023 18:36:11 -0400 Subject: [PATCH 01/12] checkpoint. --- src/platform.ts | 2 +- src/{tempestApi.ts => tempest.ts} | 115 +++++++++++++++++++++++++++++ src/test.ts | 119 ++++++++++++++++++++++++++++++ src/tester.py | 30 ++++++++ test.py | 72 ++++++++++++++++++ 5 files changed, 337 insertions(+), 1 deletion(-) rename src/{tempestApi.ts => tempest.ts} (63%) create mode 100644 src/test.ts create mode 100644 src/tester.py create mode 100644 test.py diff --git a/src/platform.ts b/src/platform.ts index 1271af6..8a5b50b 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -3,7 +3,7 @@ import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; import { WeatherFlowTempestPlatformAccessory } from './platformAccessory'; -import { TempestApi, Observation } from './tempestApi'; +import { TempestApi, Observation } from './tempest'; interface TempestSensor { name: string; diff --git a/src/tempestApi.ts b/src/tempest.ts similarity index 63% rename from src/tempestApi.ts rename to src/tempest.ts index 9db652e..31698b5 100644 --- a/src/tempestApi.ts +++ b/src/tempest.ts @@ -1,5 +1,6 @@ import { Logger } from 'homebridge'; import axios, { AxiosResponse } from 'axios'; +import * as dgram from 'dgram'; export interface Observation { // temperature sensors @@ -26,6 +27,120 @@ export interface Observation { brightness: number; // Lux } +export interface SocketObservation { + + timestamp: number; + windLull: number; + windSpeed: number; + windGust: number; + windDirection: number; + pressure: number; + temperature: number; + humidity: number; + illumination: number; + uvIndex: number; + solarRadiation: number; + rain: number; + strikes: number; + lightningDistance: number; + reportingInterval: number; + +} + + +export class TempestSocket { + + private log: Logger; + private s: dgram.Socket; + + constructor(log: Logger, address = '0.0.0.0', port = 50222) { + + this.log = log; + this.s = dgram.createSocket('udp4'); + this.setupSocket(address, port); + this.setupSignalHandlers(); + } + + private setupSocket(address: string, port: number) { + + // this.s.setsockopt(dgram.SOL_SOCKET, dgram.SO_REUSEADDR, 1); + // this.s.setsockopt(dgram.SOL_SOCKET, dgram.SO_REUSEPORT, 1); + this.s.bind({ address: address, port: port }); + this.s.on('message', (msg) => { + try { + const message_string = msg.toString('utf-8'); + const data = JSON.parse(message_string); + this.processReceivedData(data); + } catch (error) { + this.log.warn('JSON processing of data failed'); + this.log.error(error as string); + } + }); + + this.s.on('error', (err) => { + this.log.error('Socket error:', err); + }); + + } + + private processReceivedData(data: any) { + // if (data.type === 'obs_air') { + // console.log(data); + // // air_tm = this.air_data(data, air_tm); + // } + + if (data.type === 'obs_st') { + // console.log(data); + this.parseTempestData(data); + // st_tm = this.tempest_data(data, st_tm); + } + + // if (data.type === 'obs_sky') { + // console.log(data); + // // sky_tm = this.sky_data(data, sky_tm); + // } + } + + private parseTempestData(data: any): SocketObservation { + const obs = data.obs[0]; + const windLull = (obs[1] !== null) ? obs[1] * 2.2369 : 0; + const windSpeed = (obs[2] !== null) ? obs[2] * 2.2369 : 0; + const windGust = (obs[3] !== null) ? obs[3] * 2.2369 : 0; + return { + timestamp: obs[0], + windLull: windLull, + windSpeed: windSpeed, + windGust: windGust, + windDirection: obs[4], + pressure: obs[6], + temperature: obs[7], + humidity: obs[8], + illumination: obs[9], + uvIndex: obs[10], + solarRadiation: obs[11], + rain: parseFloat(obs[12]), + strikes: obs[14], + lightningDistance: obs[15], + reportingInterval: obs[17], + } as SocketObservation; + + } + + private setupSignalHandlers() { + + process.on('SIGTERM', () => { + this.log.info('Got SIGTERM, shutting down Tempest Homebridge...'); + }); + + process.on('SIGINT', () => { + this.log.info('Got SIGINT, shutting down Tempest Homebridge...'); + this.s.close(); + }); + + } + +} + export class TempestApi { private log: Logger; diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..23cb813 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,119 @@ +import * as dgram from 'dgram'; + +export interface SocketObservation { + + timestamp: number; + windLull: number; + windSpeed: number; + windGust: number; + windDirection: number; + pressure: number; + temperature: number; + humidity: number; + illumination: number; + uvIndex: number; + solarRadiation: number; + rain: number; + strikes: number; + lightningDistance: number; + reportingInterval: number; + +} + + +export class TempestSocket { + + // private log: Logger; + private s: dgram.Socket; + + constructor(address = '0.0.0.0', port = 50222) { + + // this.log = log; + this.s = dgram.createSocket('udp4'); + this.setupSocket(address, port); + this.setupSignalHandlers(); + } + + private setupSocket(address: string, port: number) { + + // this.s.setsockopt(dgram.SOL_SOCKET, dgram.SO_REUSEADDR, 1); + // this.s.setsockopt(dgram.SOL_SOCKET, dgram.SO_REUSEPORT, 1); + this.s.bind({ address: address, port: port }); + this.s.on('message', (msg) => { + try { + const message_string = msg.toString('utf-8'); + const data = JSON.parse(message_string); + console.log(data); + this.processReceivedData(data); + } catch (error) { + console.log('JSON processing of data failed'); + console.log(error as string); + } + }); + + this.s.on('error', (err) => { + console.log('Socket error:', err); + }); + + } + + private processReceivedData(data: any) { + // if (data.type === 'obs_air') { + // console.log(data); + // // air_tm = this.air_data(data, air_tm); + // } + + if (data.type === 'obs_st') { + // console.log(data); + const parsed_data = this.parseTempestData(data); + console.log(parsed_data); + // st_tm = this.tempest_data(data, st_tm); + } + + // if (data.type === 'obs_sky') { + // console.log(data); + // // sky_tm = this.sky_data(data, sky_tm); + // } + } + + private parseTempestData(data: any): SocketObservation { + const obs = data.obs[0]; + const windLull = (obs[1] !== null) ? obs[1] * 2.2369 : 0; + const windSpeed = (obs[2] !== null) ? obs[2] * 2.2369 : 0; + const windGust = (obs[3] !== null) ? obs[3] * 2.2369 : 0; + return { + timestamp: obs[0], + windLull: windLull, + windSpeed: windSpeed, + windGust: windGust, + windDirection: obs[4], + pressure: obs[6], + temperature: obs[7], + humidity: obs[8], + illumination: obs[9], + uvIndex: obs[10], + solarRadiation: obs[11], + rain: parseFloat(obs[12]), + strikes: obs[14], + lightningDistance: obs[15], + reportingInterval: obs[17], + } as SocketObservation; + + } + + private setupSignalHandlers() { + + process.on('SIGTERM', () => { + console.log('Got SIGTERM, shutting down Tempest Homebridge...'); + }); + + process.on('SIGINT', () => { + console.log('Got SIGINT, shutting down Tempest Homebridge...'); + this.s.close(); + }); + + } + +} + +new TempestSocket(); \ No newline at end of file diff --git a/src/tester.py b/src/tester.py new file mode 100644 index 0000000..7a06a7e --- /dev/null +++ b/src/tester.py @@ -0,0 +1,30 @@ +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) +s.bind(("0.0.0.0", 50222)) + +stopped = False +try: + while not stopped: + try: + hub = s.recvfrom(1024) + data = json.loads(hub[0].decode("utf-8")) # hub is a truple (json, ip, port) + except json.JSONDecodeError: + print("JSON processing of data failed") + continue + + if (data["type"] == "obs_air"): + print(data) + # air_tm = self.air_data(data, air_tm) + + if (data["type"] == "obs_st"): + print(data) + parse_tempest_data(data) + # st_tm = self.tempest_data(data, st_tm) + + if (data["type"] == "obs_sky"): + print(data) + # sky_tm = self.sky_data(data, sky_tm) +except KeyboardInterrupt: + print("Keyboard Interupt") +s.close() \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..876b1ea --- /dev/null +++ b/test.py @@ -0,0 +1,72 @@ +import json +import socket + + +def open_socket_connection(): + + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + s.bind(("0.0.0.0", 50222)) + + stopped = False + try: + while not stopped: + try: + hub = s.recvfrom(1024) + data = json.loads(hub[0].decode("utf-8")) # hub is a truple (json, ip, port) + except json.JSONDecodeError: + print("JSON processing of data failed") + continue + + if (data["type"] == "obs_air"): + print(data) + # air_tm = self.air_data(data, air_tm) + + if (data["type"] == "obs_st"): + print(data) + parse_tempest_data(data) + # st_tm = self.tempest_data(data, st_tm) + + if (data["type"] == "obs_sky"): + print(data) + # sky_tm = self.sky_data(data, sky_tm) + except KeyboardInterrupt: + print("Keyboard Interupt") + s.close() + + +def parse_tempest_data(data): + timestamp = data['obs'][0][0] # ts + # convert wind speed from m/s to MPH + if (data["obs"][0][1] is not None): + wind_lull = data["obs"][0][1] * 2.2369 # wind lull + else: + wind_lull = 0 + if (data["obs"][0][2] is not None): + wind_speed = data["obs"][0][2] * 2.2369 # wind speed + else: + wind_speed = 0 + if (data["obs"][0][3] is not None): + wind_gust = data["obs"][0][3] * 2.2369 # wind gust + else: + wind_gust = 0 + wind_direction = data['obs'][0][4] # wind direction + pressure = data['obs'][0][6] # pressure + temperature = data['obs'][0][7] # temp + humidity = data['obs'][0][8] # humidity + illumination = data['obs'][0][9] # Illumination + uv_index = data['obs'][0][10] # UV Index + solar_radiation = data['obs'][0][11] # solar radiation + rain = float(data['obs'][0][12]) # rain + strikes = data['obs'][0][14] # strikes + lightening_distance = data['obs'][0][15] # distance + reporting_interval = data['obs'][0][17] # reporting interval + + +def main(): + open_socket_connection() + + +if __name__ == "__main__": + main() \ No newline at end of file From ee27de5751f6e2504872f47f761ca55da38216f3 Mon Sep 17 00:00:00 2001 From: chasenicholl Date: Thu, 2 Nov 2023 14:28:22 -0400 Subject: [PATCH 02/12] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af3019b..95e77f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. This project uses [Semantic Versioning](https://semver.org/). ## v4.0.0 -* Added Local UDP API support! Optionally you can choose to listen to your Weather Stations observations directly over your local network. No Station ID or API Token needed. Observations are broadcasted every 60 seconds. +* Added Local UDP API support! Now you can choose to listen to your Weather Stations observations directly over your local network. No Station ID or API Token needed. Observations are broadcasted every 60 seconds. This leverages the `obs_st` message. See [documentation](https://weatherflow.github.io/Tempest/api/udp/v171/) for more information. ## v3.0.3 * Update node-version: [18.x, 20.x], remove 16.x which is no longer supported by homebridge. From f8ab3f599553d3a3f7517f4a69861656692de1ff Mon Sep 17 00:00:00 2001 From: chasenicholl Date: Thu, 2 Nov 2023 14:56:47 -0400 Subject: [PATCH 03/12] updated wait function --- src/platform.ts | 46 +++++++++++++++++++++++++++++----------------- src/tempest.ts | 4 ++-- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index ce865c0..f5dd0a1 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -61,15 +61,13 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { this.tempest_battery_level = 0; this.tempest_device_id = 0; - if (this.config.local_api === false) { - // Make sure the Station ID is the integer ID - if (isNaN(this.config.station_id)) { - log.warn( - 'Station ID is not an Integer! Please make sure you are using the ID integer found here: ' + - 'https://tempestwx.com/station//', - ); - return; - } + // Make sure the Station ID is the integer ID + if (this.config.local_api === false && isNaN(this.config.station_id)) { + log.warn( + 'Station ID is not an Integer! Please make sure you are using the ID integer found here: ' + + 'https://tempestwx.com/station//', + ); + return; } this.api.on('didFinishLaunching', () => { @@ -92,20 +90,15 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { } - private initializeBySocket() { + private async initializeBySocket() { try { this.log.info('Using Tempest Local API.'); this.tempestSocket = new TempestSocket(this.log); this.tempestSocket.start(); - // Hold thread for first message. - this.log.info('Waiting for first local broadcast. This could take up to 60 seconds...'); - while (!this.tempestSocket.hasData()) { - continue; - } - this.log.info('Local broadcast recieved.'); - // Set values + // Hold thread for first message and set values + await this.socketDataRecieved(); this.observation_data = this.tempestSocket.getStationCurrentObservation(); this.tempest_battery_level = this.tempestSocket.getBatteryLevel(); @@ -116,11 +109,30 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { // Poll every minute for local API this.pollLocalStationCurrentObservation(); + } catch(exception) { this.log.error(exception as string); } } + private socketDataRecieved(): Promise { + + this.log.info('Waiting for first local broadcast. This could take up to 60 seconds...'); + return new Promise((resolve) => { + const socket_interval = setInterval(() => { + if (this.tempestSocket === undefined) { + return; + } + if (this.tempestSocket.hasData()) { + clearInterval(socket_interval); + this.log.info('Initial local broadcast recieved.'); + resolve(); + } + }, 1000); + }); + + } + private initializeByApi() { try { diff --git a/src/tempest.ts b/src/tempest.ts index 853947f..df8c838 100644 --- a/src/tempest.ts +++ b/src/tempest.ts @@ -79,7 +79,7 @@ export class TempestSocket { } - private processReceivedData(data: any) { + private processReceivedData(data) { if (data.type === 'obs_st') { this.setTempestData(data); @@ -87,7 +87,7 @@ export class TempestSocket { } - private setTempestData(data: any): void { + private setTempestData(data): void { const obs = data.obs[0]; // const windLull = (obs[1] !== null) ? obs[1] * 2.2369 : 0; From c86bdb76da03cd0d20120dcb026b20bd088d7bfc Mon Sep 17 00:00:00 2001 From: Doug B Date: Sun, 5 Nov 2023 12:20:49 -0500 Subject: [PATCH 04/12] Local UDP API support * Added Local UDP API support! Now you can choose to listen to your Weather Stations observations directly over your local network. No Station ID or API Token needed. Observations are broadcasted every 60 seconds. This leverages the `obs_st` message. See [documentation](https://weatherflow.github.io/Tempest/api/udp/v171/) for more information. --- CHANGELOG.md | 3 + README.md | 109 ++++++++- config.schema.json | 392 ++++++++++++++++-------------- package-lock.json | 4 +- package.json | 2 +- src/platform.ts | 145 ++++++++--- src/platformAccessory.ts | 27 +- src/{tempestApi.ts => tempest.ts} | 122 ++++++++++ 8 files changed, 572 insertions(+), 232 deletions(-) rename src/{tempestApi.ts => tempest.ts} (57%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bebaa7..95e77f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. This project uses [Semantic Versioning](https://semver.org/). +## v4.0.0 +* Added Local UDP API support! Now you can choose to listen to your Weather Stations observations directly over your local network. No Station ID or API Token needed. Observations are broadcasted every 60 seconds. This leverages the `obs_st` message. See [documentation](https://weatherflow.github.io/Tempest/api/udp/v171/) for more information. + ## v3.0.3 * Update node-version: [18.x, 20.x], remove 16.x which is no longer supported by homebridge. * Reformated `getStationObservation()` and `getStationCurrentObservation()` in `tempestApi.ts`. diff --git a/README.md b/README.md index c052932..03fe8a4 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ +*New* in v4.0.0 Local API Support! + Homebridge Plugin providing basic WeatherFlow Tempest support. Exposing 7 Acessories. - Temperature Sensor @@ -34,9 +36,10 @@ It is recommended when upgrading to v3.0 of the plugin from a prior version that You will need to create an account at https://tempestwx.com/ and then generate a Personal Use Token https://tempestwx.com/settings/tokens. - `name`: _(Required)_ Must always be set to `WeatherFlow Tempest Platform`. -- `token`: _(Required)_ Oauth2 Personal Use Token, create via your tempestwx account. -- `station_id`: _(Required)_ The station ID you are pulling weather data from. -- `interval`: _(Required)_ How often to poll the Tempest REST API. Default 10 seconds. Minimum every second. +- `local_api`: _(Required)_ Use the Local API versus HTTP API. +- `token`: _(Required for HTTP API)_ Oauth2 Personal Use Token, create via your tempestwx account. +- `station_id`: _(Required for HTTP API)_ The station ID you are pulling weather data from. +- `interval`: _(Required for HTTP API)_ How often to poll the Tempest REST API. Default 10 seconds. Minimum every second. - `sensors`: _(Required)_ An array of sensors to create. This is dynamic incase you want to target different temperature or wind speed attributes. - `sensors[].name`: _(Required)_ Display name of Sensor in Apple Home. - `sensors[].sensor_type`: _(Required)_ The type of Home Sensor to create. There are 6 options ["Temperature Sensor", "Light Sensor", "Humidity Sensor", "Fan", "Motion Sensor", "Occupancy Sensor"]. @@ -69,11 +72,109 @@ sensor_type `{2}` | value_key | metric units | std units | additional_properties `{4}` NOTE: There is a current limitation with v3.0.0 of the plug-in in that HomeKit accessory names are set when the accessory is initially added and cannot be dynamically updated. The accessories are correctly displayed and updated in the Homebridge "Accessories" tab of the webpage interface. Occupancy sensors `trigger_value` status is correctly displayed in both HomeKit and Homebridge. -### Config Example +### Local API Config Example + +```json +{ + "name": "WeatherFlow Tempest Platform", + "local_api": true, + "units": "Standard", + "sensors": [ + { + "name": "Temperature", + "sensor_type": "Temperature Sensor", + "temperature_properties": { + "value_key": "air_temperature" + } + }, + { + "name": "Relative Humidity", + "sensor_type": "Humidity Sensor", + "humidity_properties": { + "value_key": "relative_humidity" + } + }, + { + "name": "Light Level", + "sensor_type": "Light Sensor", + "light_properties": { + "value_key": "brightness" + } + }, + { + "name": "Wind Speed", + "sensor_type": "Fan", + "fan_properties": { + "value_key": "wind_avg" + } + }, + { + "name": "Wind Gust", + "sensor_type": "Motion Sensor", + "motion_properties": { + "value_key": "wind_gust", + "trigger_value": 10 + } + }, + { + "name": "Barometer", + "sensor_type": "Occupancy Sensor", + "occupancy_properties": { + "value_key": "barometric_pressure", + "trigger_value": 30 + } + }, + { + "name": "Solar Radiation", + "sensor_type": "Occupancy Sensor", + "occupancy_properties": { + "value_key": "solar_radiation", + "trigger_value": 1000 + } + }, + { + "name": "UV", + "sensor_type": "Occupancy Sensor", + "occupancy_properties": { + "value_key": "uv", + "trigger_value": 3 + } + }, + { + "name": "Precipitation Rate", + "sensor_type": "Occupancy Sensor", + "occupancy_properties": { + "value_key": "precip", + "trigger_value": 0.25 + } + }, + { + "name": "Precipitation Today", + "sensor_type": "Occupancy Sensor", + "occupancy_properties": { + "value_key": "precip_accum_local_day", + "trigger_value": 1 + } + }, + { + "name": "Wind Direction", + "sensor_type": "Occupancy Sensor", + "occupancy_properties": { + "value_key": "wind_direction", + "trigger_value": 360 + } + } + ], + "platform": "WeatherFlowTempest" +} +``` + +### HTTP API Config Example ```json { "name": "WeatherFlow Tempest Platform", + "local_api": false, "token": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "station_id": 10000, "interval": 10, diff --git a/config.schema.json b/config.schema.json index ff4810f..f115681 100644 --- a/config.schema.json +++ b/config.schema.json @@ -1,182 +1,214 @@ { - "pluginAlias": "WeatherFlowTempest", - "pluginType": "platform", - "singular": false, - "schema": { - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string", - "required": true, - "default": "WeatherFlow Tempest Platform" - }, - "token": { - "title": "Token", - "type": "string", - "required": true - }, - "station_id": { - "title": "Station ID (Integer)", - "type": "number", - "required": true - }, - "interval": { - "title": "Interval (seconds)", - "type": "integer", - "default": 10, - "minimum": 1 - }, - "units": { - "title": "Units", - "type": "string", - "enum": [ - "Standard", - "Metric" - ], - "default": "Standard" - }, - "sensors": { - "title": "Weather Sensors", - "description": "Enable WeatherFlow Tempest Sensors.", - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string", - "required": true - }, - "sensor_type": { - "type": "string", - "enum": [ - "Temperature Sensor", - "Light Sensor", - "Humidity Sensor", - "Fan", - "Motion Sensor", - "Occupancy Sensor" - ], - "default": "Temperature Sensor" - }, - "fan_properties": { - "title": "Fan Properties", - "type": "object", - "condition": { - "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Fan') { return true; } else { return false; };" - }, - "properties": { - "value_key": { - "type": "string", - "enum": [ - "wind_avg" - ] - } - } - }, - "light_properties": { - "title": "Light Properties", - "type": "object", - "condition": { - "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Light Sensor') { return true; } else { return false; };" - }, - "properties": { - "value_key": { - "type": "string", - "enum": [ - "brightness" - ] - } - } - }, - "humidity_properties": { - "title": "Humidity Properties", - "type": "object", - "condition": { - "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Humidity Sensor') { return true; } else { return false; };" - }, - "properties": { - "value_key": { - "type": "string", - "enum": [ - "relative_humidity" - ] - } - } - }, - "temperature_properties": { - "title": "Temperature Properties", - "type": "object", - "condition": { - "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Temperature Sensor') { return true; } else { return false; };" - }, - "properties": { - "value_key": { - "type": "string", - "enum": [ - "air_temperature", - "dew_point", - "feels_like", - "wind_chill" - ] - } - } - }, - "motion_properties": { - "title": "Motion Properties", - "type": "object", - "condition": { - "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Motion Sensor') { return true; } else { return false; };" - }, - "properties": { - "value_key": { - "type": "string", - "enum": [ - "wind_gust" - ] - }, - "trigger_value": { - "type": "number", - "minimum": 1, - "description": "At what point (value) to trigger motion detected on/off (1 minimum)." - } - } - }, - "occupancy_properties": { - "title": "Occupancy Properties", - "type": "object", - "condition": { - "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Occupancy Sensor') { return true; } else { return false; };" - }, - "properties": { - "value_key": { - "type": "string", - "enum": [ - "barometric_pressure", - "precip", - "precip_accum_local_day", - "wind_direction", - "wind_gust", - "solar_radiation", - "uv" - ] - }, - "trigger_value": { - "type": "number", - "minimum": 0, - "description": "At what point (value) to trigger occupancy detected on/off (0 minimum)." - } - } - } - } - }, - "required": [ - "name", - "sensor_type", - "value_key" - ] - } - } - } + "pluginAlias": "WeatherFlowTempest", + "pluginType": "platform", + "singular": false, + "schema": { + "type": "object", + + "properties": { + "name": { + "title": "Name", + "type": "string", + "required": true, + "default": "WeatherFlow Tempest Platform" + }, + "local_api": { + "title": "Use Local API", + "type": "boolean", + "required": true, + "default": true + }, + "token": { + "title": "Token", + "type": "string", + "default": "", + "condition": { + "functionBody": "if (model.local_api != undefined && !model.local_api) { return true; } else { return false; };" + } + }, + "station_id": { + "title": "Station ID (Integer)", + "type": "number", + "default": 0, + "condition": { + "functionBody": "if (model.local_api != undefined && !model.local_api) { return true; } else { return false; };" + } + }, + "interval": { + "title": "Interval (seconds)", + "type": "integer", + "default": 10, + "minimum": 1, + "condition": { + "functionBody": "if (model.local_api != undefined && !model.local_api) { return true; } else { return false; };" + } + }, + "units": { + "title": "Units", + "type": "string", + "enum": [ + "Standard", + "Metric" + ], + "default": "Standard" + }, + "sensors": { + "title": "Weather Sensors", + "description": "Enable WeatherFlow Tempest Sensors.", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string", + "required": true + }, + "sensor_type": { + "type": "string", + "enum": [ + "Temperature Sensor", + "Light Sensor", + "Humidity Sensor", + "Fan", + "Motion Sensor", + "Occupancy Sensor" + ], + "default": "Temperature Sensor" + }, + "fan_properties": { + "title": "Fan Properties", + "type": "object", + "condition": { + "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Fan') { return true; } else { return false; };" + }, + "properties": { + "value_key": { + "type": "string", + "enum": [ + "wind_avg" + ] + } + } + }, + "light_properties": { + "title": "Light Properties", + "type": "object", + "condition": { + "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Light Sensor') { return true; } else { return false; };" + }, + "properties": { + "value_key": { + "type": "string", + "enum": [ + "brightness" + ] + } + } + }, + "humidity_properties": { + "title": "Humidity Properties", + "type": "object", + "condition": { + "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Humidity Sensor') { return true; } else { return false; };" + }, + "properties": { + "value_key": { + "type": "string", + "enum": [ + "relative_humidity" + ] + } + } + }, + "temperature_properties": { + "title": "Temperature Properties", + "type": "object", + "condition": { + "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Temperature Sensor') { return true; } else { return false; };" + }, + "properties": { + "value_key": { + "type": "string", + "enum": [ + "air_temperature", + "dew_point", + "feels_like", + "wind_chill" + ] + } + } + }, + "motion_properties": { + "title": "Motion Properties", + "type": "object", + "condition": { + "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Motion Sensor') { return true; } else { return false; };" + }, + "properties": { + "value_key": { + "type": "string", + "enum": [ + "wind_gust" + ], + "condition": { + "functionBody": "if (!model.local_api && model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Temperature Sensor') { return true; } else { return false; };" + } + }, + "trigger_value": { + "type": "number", + "minimum": 1, + "description": "At what point (value) to trigger motion detected on/off (1 minimum)." + } + } + }, + "occupancy_properties": { + "title": "Occupancy Properties", + "type": "object", + "condition": { + "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Occupancy Sensor') { return true; } else { return false; };" + }, + "properties": { + "value_key": { + "type": "string", + "enum": [ + "barometric_pressure", + "precip", + "precip_accum_local_day", + "wind_direction", + "wind_gust", + "solar_radiation", + "uv" + ], + "condition": { + "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Temperature Sensor') { return true; } else { return false; };" + } + }, + "trigger_value": { + "type": "number", + "minimum": 0, + "description": "At what point (value) to trigger occupancy detected on/off (0 minimum)." + } + } + } + } + }, + "required": [ + "name", + "sensor_type", + "value_key" + ] + } + }, + "required": ["local_api"], + "dependencies": { + "local_api": { + "not": { + "type": "boolean", + "const": true + }, + "required": ["token", "station_id"] + } + } + } } diff --git a/package-lock.json b/package-lock.json index ac6a6d7..bd5eeae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "homebridge-weatherflow-tempest", - "version": "3.0.3", + "version": "4.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "homebridge-weatherflow-tempest", - "version": "3.0.3", + "version": "4.0.0", "license": "Apache-2.0", "dependencies": { "axios": "1.5.1" diff --git a/package.json b/package.json index 134732c..37665ee 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": false, "displayName": "Homebridge WeatherFlow Tempest", "name": "homebridge-weatherflow-tempest", - "version": "3.0.3", + "version": "4.0.0", "description": "Exposes WeatherFlow Tempest Station data as Temperature Sensors, Light Sensors, Humidity Sensors and Fan Sensors (for Wind Speed).", "license": "Apache-2.0", "repository": { diff --git a/src/platform.ts b/src/platform.ts index 5880614..f5dd0a1 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -3,7 +3,7 @@ import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; import { WeatherFlowTempestPlatformAccessory } from './platformAccessory'; -import { TempestApi, Observation } from './tempestApi'; +import { TempestApi, TempestSocket, Observation } from './tempest'; interface TempestSensor { name: string; @@ -24,7 +24,8 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic; public readonly accessories: PlatformAccessory[] = []; - private tempestApi: TempestApi; + private tempestApi: TempestApi | undefined; + private tempestSocket: TempestSocket | undefined; public observation_data: Observation; // Observation data for Accessories to use. public tempest_battery_level!: number; // Tempest battery level @@ -40,10 +41,7 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { log.info('Finished initializing platform:', this.config.name); - // Initialize TempestApi - this.tempestApi = new TempestApi(this.config.token, this.config.station_id, log); - - // initialize observation_data + // Initialize observation_data this.observation_data = { air_temperature: 0, barometric_pressure: 0, @@ -64,7 +62,7 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { this.tempest_device_id = 0; // Make sure the Station ID is the integer ID - if (isNaN(this.config.station_id)) { + if (this.config.local_api === false && isNaN(this.config.station_id)) { log.warn( 'Station ID is not an Integer! Please make sure you are using the ID integer found here: ' + 'https://tempestwx.com/station//', @@ -81,56 +79,125 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { return; } - try { + // Initialize Tempest Interfaces + if (this.config.local_api === true) { + this.initializeBySocket(); + } else { + this.initializeByApi(); + } - this.tempestApi.getStationCurrentObservation(0).then( (observation_data: Observation) => { + }); - if (!observation_data) { - log.warn('Failed to fetch initial Station Current Observations after retrying. Refusing to continue.'); - return; - } + } - // Cache the observation results - this.observation_data = observation_data; + private async initializeBySocket() { + + try { + this.log.info('Using Tempest Local API.'); + this.tempestSocket = new TempestSocket(this.log); + this.tempestSocket.start(); + + // Hold thread for first message and set values + await this.socketDataRecieved(); + this.observation_data = this.tempestSocket.getStationCurrentObservation(); + this.tempest_battery_level = this.tempestSocket.getBatteryLevel(); + + // Initialize sensors after first API response. + this.discoverDevices(); + this.log.debug ('discoverDevices completed'); - // Initialize sensors after first API response. - this.discoverDevices(); + // Poll every minute for local API + this.pollLocalStationCurrentObservation(); - this.log.debug ('discoverDevices completed'); - // Remove cached sensors that are no longer required. - this.removeDevices(); + } catch(exception) { + this.log.error(exception as string); + } + } - this.log.debug ('removeDevices completed'); + private socketDataRecieved(): Promise { - // Determine Tempest device_id & initial battery level - this.tempestApi.getTempestDeviceId().then( (device_id: number) => { - this.tempest_device_id = device_id; + this.log.info('Waiting for first local broadcast. This could take up to 60 seconds...'); + return new Promise((resolve) => { + const socket_interval = setInterval(() => { + if (this.tempestSocket === undefined) { + return; + } + if (this.tempestSocket.hasData()) { + clearInterval(socket_interval); + this.log.info('Initial local broadcast recieved.'); + resolve(); + } + }, 1000); + }); - this.tempestApi.getTempestBatteryLevel(this.tempest_device_id).then( (battery_level: number) => { + } - if (battery_level === undefined) { - this.log.warn('Failed to fetch initial Tempest battery level'); - return; - } - this.tempest_battery_level = battery_level; + private initializeByApi() { - }); + try { + this.log.info('Using Tempest RESTful API.'); + this.tempestApi = new TempestApi(this.config.token, this.config.station_id, this.log); + this.tempestApi.getStationCurrentObservation(0).then( (observation_data: Observation) => { - }); + if (!observation_data) { + this.log.warn('Failed to fetch initial Station Current Observations after retrying. Refusing to continue.'); + return; + } - // Then begin to poll the station current observations data. - this.pollStationCurrentObservation(); + if (this.tempestApi === undefined) { + return; + } + + // Cache the observation results + this.observation_data = observation_data; + + // Initialize sensors after first API response. + this.discoverDevices(); + this.log.debug ('discoverDevices completed'); + // Remove cached sensors that are no longer required. + this.removeDevices(); + this.log.debug ('removeDevices completed'); + + // Determine Tempest device_id & initial battery level + this.tempestApi.getTempestDeviceId().then( (device_id: number) => { + this.tempest_device_id = device_id; + if (this.tempestApi === undefined) { + return; + } + this.tempestApi.getTempestBatteryLevel(this.tempest_device_id).then( (battery_level: number) => { + if (battery_level === undefined) { + this.log.warn('Failed to fetch initial Tempest battery level'); + return; + } + this.tempest_battery_level = battery_level; + }); }); - } catch(exception) { + // Then begin to poll the station current observations data. + this.pollStationCurrentObservation(); + }); + + } catch(exception) { + this.log.error(exception as string); + } + + } + + private pollLocalStationCurrentObservation(): void { - this.log.error(exception as string); + setInterval( async () => { + if (this.tempestSocket === undefined) { + return; } - }); + // Update values + this.observation_data = this.tempestSocket.getStationCurrentObservation(); + this.tempest_battery_level = this.tempestSocket.getBatteryLevel(); + + }, 60 * 1000); // Tempest local API broadcasts every minute. } @@ -142,6 +209,10 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { setInterval( async () => { + if (this.tempestApi === undefined) { + return; + } + // Update Observation data await this.tempestApi.getStationCurrentObservation(0).then( (observation_data: Observation) => { diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 964f0fd..57b819b 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -302,16 +302,27 @@ class Fan { try { const value_key: string = this.accessory.context.device.fan_properties.value_key; let speed: number = parseFloat(this.platform.observation_data[value_key]); - speed = Math.round(speed * 2.236936); // convert m/s to mph and round as fan % is integer value - if (speed > 100) { - this.platform.log.debug(`WeatherFlow Tempest is reporting wind speed exceeding 100mph: ${speed}mph`); - return 100; - } else if (speed < 0) { - this.platform.log.debug(`WeatherFlow Tempest is reporting wind speed less than 0mph: ${speed}mph`); - return 0; + if (this.platform.config.units === 'Metric') { + speed = Math.round(speed); // round as fan % is integer value + if (speed > 45) { + this.platform.log.debug(`WeatherFlow Tempest is reporting wind speed exceeding 45 m/s: ${speed} m/s`); + speed = 45; + } else if (speed < 0) { + this.platform.log.debug(`WeatherFlow Tempest is reporting wind speed less than 0 m/s: ${speed} m/s`); + speed = 0; + } } else { - return speed; + speed = Math.round(speed * 2.236936); // convert m/s to mph and round as fan % is integer value + if (speed > 100) { + this.platform.log.debug(`WeatherFlow Tempest is reporting wind speed exceeding 100 mph: ${speed} mph`); + speed = 100; + } else if (speed < 0) { + this.platform.log.debug(`WeatherFlow Tempest is reporting wind speed less than 0 mph: ${speed} mph`); + speed = 0; + } } + return speed; + } catch(exception) { this.platform.log.error(exception as string); return 0; diff --git a/src/tempestApi.ts b/src/tempest.ts similarity index 57% rename from src/tempestApi.ts rename to src/tempest.ts index 32606fb..5653ade 100644 --- a/src/tempestApi.ts +++ b/src/tempest.ts @@ -1,10 +1,13 @@ import { Logger } from 'homebridge'; import axios, { AxiosResponse } from 'axios'; +import * as dgram from 'dgram'; import https from 'https'; + axios.defaults.timeout = 10000; // same as default interval axios.defaults.httpsAgent = new https.Agent({ keepAlive: true }); + export interface Observation { // temperature sensors air_temperature: number; // C, displayed according to Homebridge and HomeKit C/F settings @@ -30,6 +33,125 @@ export interface Observation { brightness: number; // Lux } + +export class TempestSocket { + + private log: Logger; + private s: dgram.Socket; + private data: object | undefined; + private tempest_battery_level: number; + + constructor(log: Logger) { + + this.log = log; + this.data = undefined; + this.tempest_battery_level = 0; + this.s = dgram.createSocket('udp4'); + + this.log.info('TempestSocket initialized.'); + + } + + public start(address = '0.0.0.0', port = 50222) { + + this.setupSocket(address, port); + this.setupSignalHandlers(); + + } + + private setupSocket(address: string, port: number) { + + this.s.bind({ address: address, port: port }); + this.s.on('message', (msg) => { + try { + const message_string = msg.toString('utf-8'); + const data = JSON.parse(message_string); + this.processReceivedData(data); + } catch (error) { + this.log.warn('JSON processing of data failed'); + this.log.error(error as string); + } + }); + + this.s.on('error', (err) => { + this.log.error('Socket error:', err); + }); + + } + + private processReceivedData(data) { + + if (data.type === 'obs_st') { // for Tempest + this.setTempestData(data); + } + + } + + private setTempestData(data): void { + + const obs = data.obs[0]; + // const windLull = (obs[1] !== null) ? obs[1] : 0; + const windSpeed = (obs[2] !== null) ? obs[2] * 2.2369 : 0; // convert to mph for heatindex calculation + // const windGust = (obs[3] !== null) ? Math.round(obs[3] * 2.236936) : 0; // convert to mph + const T = (obs[7] * 9/5) + 32; // T in F for heatindex, feelsLike and windChill calculations + + // eslint-disable-next-line max-len + const heatIndex = -42.379 + 2.04901523*T + 10.14333127*obs[8] - 0.22475541*T*obs[8] - 0.00683783*(T**2) - 0.05481717*(obs[8]**2) + 0.00122874*(T**2)*obs[8] + 0.00085282*T*(obs[8]**2) - 0.00000199*(T**2)*(obs[8]**2); + + // feels like temperature on defined for temperatures between 80F and 110F + const feelsLike = ((T >= 80) && (T <= 110)) ? heatIndex : T; + + // windChill only defined for wind speeds > 3 mph and temperature < 50F + const windChill = ((windSpeed > 3) && (T < 50)) ? (35.74 + 0.6215*T - 35.75*(windSpeed**0.16) + 0.4275*T*(windSpeed**0.16)) : T; + + this.data = { + air_temperature: obs[7], + feels_like: 5/9 * (feelsLike - 32), // convert back to C + wind_chill: 5/9 * (windChill - 32), // convert back to C + dew_point: obs[7] - ((100 - obs[8]) / 5.0), // Td = T - ((100 - RH)/5) + relative_humidity: obs[8], + wind_avg: obs[2], + wind_gust: obs[3], + barometric_pressure: obs[6], + precip: obs[12], + precip_accum_local_day: obs[12], + wind_direction: obs[4], + solar_radiation: obs[11], + uv: obs[10], + brightness: obs[9], + }; + this.tempest_battery_level = Math.round((obs[16] - 1.8) * 100); // 2.80V = 100%, 1.80V = 0% + + } + + private setupSignalHandlers(): void { + + process.on('SIGTERM', () => { + this.log.info('Got SIGTERM, shutting down Tempest Homebridge...'); + this.s.close(); + }); + + process.on('SIGINT', () => { + this.log.info('Got SIGINT, shutting down Tempest Homebridge...'); + this.s.close(); + }); + + } + + public hasData(): boolean { + return this.data !== undefined; + } + + public getStationCurrentObservation(): Observation { + return this.data as Observation; + } + + public getBatteryLevel(): number { + return this.tempest_battery_level; + } + +} + export class TempestApi { private log: Logger; From 542cd9bc447e6b0b4828b2a48493eebeb9ccb2d6 Mon Sep 17 00:00:00 2001 From: Doug B Date: Mon, 6 Nov 2023 10:53:44 -0500 Subject: [PATCH 05/12] Update package-lock.json --- package-lock.json | 176 +++++++++++++++++++++++----------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/package-lock.json b/package-lock.json index bd5eeae..e57c331 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,18 +70,18 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -102,9 +102,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -345,12 +345,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz", - "integrity": "sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==", + "version": "20.8.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", + "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", "dev": true, "dependencies": { - "undici-types": "~5.25.1" + "undici-types": "~5.26.4" } }, "node_modules/@types/semver": { @@ -360,16 +360,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz", - "integrity": "sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", + "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.8.0", - "@typescript-eslint/type-utils": "6.8.0", - "@typescript-eslint/utils": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/type-utils": "6.9.1", + "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -395,15 +395,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.8.0.tgz", - "integrity": "sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", + "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.8.0", - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/typescript-estree": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4" }, "engines": { @@ -423,13 +423,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz", - "integrity": "sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", + "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0" + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -440,13 +440,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz", - "integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", + "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.8.0", - "@typescript-eslint/utils": "6.8.0", + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/utils": "6.9.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -467,9 +467,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz", - "integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", + "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -480,13 +480,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz", - "integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", + "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -507,17 +507,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz", - "integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", + "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.8.0", - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", "semver": "^7.5.4" }, "engines": { @@ -532,12 +532,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz", - "integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", + "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/types": "6.9.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -561,9 +561,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -582,9 +582,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", "dev": true, "engines": { "node": ">=0.4.0" @@ -874,12 +874,12 @@ } }, "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true, "engines": { - "node": ">= 6" + "node": ">= 10" } }, "node_modules/concat-map": { @@ -1095,15 +1095,15 @@ } }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1258,9 +1258,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -1778,24 +1778,24 @@ } }, "node_modules/homebridge": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.6.1.tgz", - "integrity": "sha512-hDhSaBDHFbB8wQQuZKbistYj1gjTIcNWmusqgEUb0Umk76Hs+G6VKRTkOEEVuxRaQWoK5hRM5rJTsCGAMCj5cA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.7.0.tgz", + "integrity": "sha512-2QikXnmpnFe2s33Q8TeYE5+sXyKHUZ+9l5WfDmpuupHdct6H/G6b6z3HCj+2rlMRKKY5ElLv5XtLoxOcafnL0g==", "dev": true, "dependencies": { "chalk": "^4.1.2", - "commander": "5.1.0", + "commander": "^7.2.0", "fs-extra": "^10.1.0", "hap-nodejs": "~0.11.1", "qrcode-terminal": "^0.12.0", - "semver": "^7.3.7", + "semver": "^7.5.4", "source-map-support": "^0.5.21" }, "bin": { "homebridge": "bin/homebridge" }, "engines": { - "node": ">=10.17.0" + "node": "^18.15.0 || ^20.7.0" } }, "node_modules/ignore": { @@ -2703,9 +2703,9 @@ "dev": true }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -3309,15 +3309,15 @@ "dev": true }, "node_modules/undici-types": { - "version": "5.25.3", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" From 54ca06a10ffc39613d7850407fc331b62908d7c9 Mon Sep 17 00:00:00 2001 From: Doug B Date: Mon, 6 Nov 2023 15:10:24 -0500 Subject: [PATCH 06/12] v4.0.0 updates - Fixed error in config.schema.json which was blocking display of occupancy sensor type. - Added note in README.md to indicate that `precip_accum_local_day` not available in local API. --- README.md | 2 +- config.schema.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 03fe8a4..2fd709e 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ sensor_type `{2}` | value_key | metric units | std units | additional_properties `Motion Sensor` | wind_gust | m/s | mi/hr | motion_trigger_value | 5 | 10 | `Occupancy Sensor {3}{4}` | barometric_pressure | mb | inHg | occupancy_trigger_value | 1000 | 30 | ` ` | precip | mm/min | in/hr | occupancy_trigger_value | 0.1 | 0.25 | -` ` | precip_accum_local_day | mm | in | occupancy_trigger_value | 25 | 1 | +` ` | precip_accum_local_day | mm | in | occupancy_trigger_value | 25 | 1 | not available with Local API ` ` | solar_radiation | W/m^2 | W/m^2 | occupancy_trigger_value | 1000| 1000 | ` ` | uv | Index | Index | occupancy_trigger_value | 3 | 3 | ` ` | wind_direction | degrees | degrees | occupancy_trigger_value | 360 | 360 | diff --git a/config.schema.json b/config.schema.json index f115681..939bb94 100644 --- a/config.schema.json +++ b/config.schema.json @@ -181,7 +181,7 @@ "uv" ], "condition": { - "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Temperature Sensor') { return true; } else { return false; };" + "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Occupancy Sensor') { return true; } else { return false; };" } }, "trigger_value": { From 4543c92f1199421f4992a35380ce086f0a0f4b8a Mon Sep 17 00:00:00 2001 From: Doug B Date: Tue, 7 Nov 2023 19:46:41 -0500 Subject: [PATCH 07/12] Update config.schema.json Add description that states that `precip_accum_local_day` is not supported using Local API. --- config.schema.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config.schema.json b/config.schema.json index 939bb94..03c0887 100644 --- a/config.schema.json +++ b/config.schema.json @@ -182,8 +182,9 @@ ], "condition": { "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Occupancy Sensor') { return true; } else { return false; };" - } - }, + }, + "description": "Note: `precip_accum_local_day` not supported when using Local API." + }, "trigger_value": { "type": "number", "minimum": 0, From acc33316be86a3a1955793b001de760cb499dab0 Mon Sep 17 00:00:00 2001 From: Doug B Date: Wed, 8 Nov 2023 09:30:00 -0500 Subject: [PATCH 08/12] Update config.schema.json --- config.schema.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/config.schema.json b/config.schema.json index 03c0887..4eac981 100644 --- a/config.schema.json +++ b/config.schema.json @@ -150,10 +150,7 @@ "type": "string", "enum": [ "wind_gust" - ], - "condition": { - "functionBody": "if (!model.local_api && model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Temperature Sensor') { return true; } else { return false; };" - } + ] }, "trigger_value": { "type": "number", @@ -180,9 +177,6 @@ "solar_radiation", "uv" ], - "condition": { - "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Occupancy Sensor') { return true; } else { return false; };" - }, "description": "Note: `precip_accum_local_day` not supported when using Local API." }, "trigger_value": { From 9544e137fb1d984b5f1711a4eaf27fceb45fea1f Mon Sep 17 00:00:00 2001 From: Doug B Date: Wed, 8 Nov 2023 16:46:41 -0500 Subject: [PATCH 09/12] Check for 'precip_accum_local_day' Disable `precip_accum_local_day` Occupancy Sensor if using Local API. --- src/index.ts | 4 ++-- src/platform.ts | 64 ++++++++++++++++++++++++++++++------------------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/index.ts b/src/index.ts index f493443..68605a7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,11 @@ import { API } from 'homebridge'; -import { PLATFORM_NAME } from './settings'; +import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; import { WeatherFlowTempestPlatform } from './platform'; /** * This method registers the platform with Homebridge */ export = (api: API) => { - api.registerPlatform(PLATFORM_NAME, WeatherFlowTempestPlatform); + api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, WeatherFlowTempestPlatform); }; diff --git a/src/platform.ts b/src/platform.ts index f5dd0a1..d1afd39 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1,4 +1,4 @@ -import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge'; +import { API, APIEvent, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge'; import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; import { WeatherFlowTempestPlatformAccessory } from './platformAccessory'; @@ -70,7 +70,7 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { return; } - this.api.on('didFinishLaunching', () => { + api.on(APIEvent.DID_FINISH_LAUNCHING, () => { log.info('Executed didFinishLaunching callback'); @@ -92,6 +92,8 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { private async initializeBySocket() { + this.log.info('Initializing by Socket'); + try { this.log.info('Using Tempest Local API.'); this.tempestSocket = new TempestSocket(this.log); @@ -104,7 +106,11 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { // Initialize sensors after first API response. this.discoverDevices(); - this.log.debug ('discoverDevices completed'); + this.log.info ('discoverDevices completed'); + + // Remove cached sensors that are no longer required. + this.removeDevices(); + this.log.info ('removeDevices completed'); // Poll every minute for local API this.pollLocalStationCurrentObservation(); @@ -135,6 +141,8 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { private initializeByApi() { + this.log.info('Initializing by API'); + try { this.log.info('Using Tempest RESTful API.'); this.tempestApi = new TempestApi(this.config.token, this.config.station_id, this.log); @@ -154,11 +162,11 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { // Initialize sensors after first API response. this.discoverDevices(); - this.log.debug ('discoverDevices completed'); + this.log.info ('discoverDevices completed'); // Remove cached sensors that are no longer required. this.removeDevices(); - this.log.debug ('removeDevices completed'); + this.log.info ('removeDevices completed'); // Determine Tempest device_id & initial battery level this.tempestApi.getTempestDeviceId().then( (device_id: number) => { @@ -309,6 +317,9 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { break; case 'Occupancy Sensor': value_key = device.occupancy_properties['value_key']; + if((this.config.local_api === true) && (value_key === 'precip_accum_local_day')) { + value_key = 'not_available'; + } break; default: this.log.warn('device.sensor_type not defined'); @@ -318,36 +329,39 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin { `${device.name}-${device.sensor_type}-${value_key}`, ); - const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); + if (value_key !== 'not_available') { - if (existingAccessory) { - this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); - // pick up any changes such as 'trigger_value' - existingAccessory.context.device = device; + if (existingAccessory) { + this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName); - // update accessory context information - this.api.updatePlatformAccessories(this.accessories); + // pick up any changes such as 'trigger_value' + existingAccessory.context.device = device; - new WeatherFlowTempestPlatformAccessory(this, existingAccessory); + // update accessory context information + this.api.updatePlatformAccessories(this.accessories); - // add to array of active accessories - this.activeAccessory.push(existingAccessory); + new WeatherFlowTempestPlatformAccessory(this, existingAccessory); - } else { - this.log.info('Adding new accessory:', device.name); - const accessory = new this.api.platformAccessory(device.name, uuid); + // add to array of active accessories + this.activeAccessory.push(existingAccessory); - // initialize context information - accessory.context.device = device; + } else { + this.log.info('Adding new accessory:', device.name); + const accessory = new this.api.platformAccessory(device.name, uuid); + + // initialize context information + accessory.context.device = device; - new WeatherFlowTempestPlatformAccessory(this, accessory); + new WeatherFlowTempestPlatformAccessory(this, accessory); - // link the accessory to the platform - this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); + // link the accessory to the platform + this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); - // add to array of active accessories - this.activeAccessory.push(accessory); + // add to array of active accessories + this.activeAccessory.push(accessory); + } } } From c42968ee2972f289e5fb85b4e7c8a6f9e846b7bc Mon Sep 17 00:00:00 2001 From: Doug B Date: Thu, 9 Nov 2023 13:15:49 -0500 Subject: [PATCH 10/12] Update tempest.ts --- src/tempest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tempest.ts b/src/tempest.ts index 5653ade..727a2ef 100644 --- a/src/tempest.ts +++ b/src/tempest.ts @@ -92,7 +92,6 @@ export class TempestSocket { const obs = data.obs[0]; // const windLull = (obs[1] !== null) ? obs[1] : 0; const windSpeed = (obs[2] !== null) ? obs[2] * 2.2369 : 0; // convert to mph for heatindex calculation - // const windGust = (obs[3] !== null) ? Math.round(obs[3] * 2.236936) : 0; // convert to mph const T = (obs[7] * 9/5) + 32; // T in F for heatindex, feelsLike and windChill calculations // eslint-disable-next-line max-len From 330f25e6b1b66383ec61d7680a01a028ff0e97e0 Mon Sep 17 00:00:00 2001 From: Doug B Date: Fri, 10 Nov 2023 16:11:43 -0500 Subject: [PATCH 11/12] Update package-lock.json --- package-lock.json | 128 ++++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/package-lock.json b/package-lock.json index e57c331..9c1fdbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -339,37 +339,37 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/node": { - "version": "20.8.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", - "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", - "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz", + "integrity": "sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/type-utils": "6.9.1", - "@typescript-eslint/utils": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/type-utils": "6.10.0", + "@typescript-eslint/utils": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -395,15 +395,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", - "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", + "integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4" }, "engines": { @@ -423,13 +423,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", - "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz", + "integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1" + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -440,13 +440,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", - "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz", + "integrity": "sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/utils": "6.10.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -467,9 +467,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", - "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz", + "integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -480,13 +480,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", - "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz", + "integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -507,17 +507,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", - "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.10.0.tgz", + "integrity": "sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", "semver": "^7.5.4" }, "engines": { @@ -532,12 +532,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", - "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz", + "integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/types": "6.10.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -926,15 +926,15 @@ } }, "node_modules/deep-equal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", - "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", + "call-bind": "^1.0.5", "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.1", + "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", @@ -944,11 +944,14 @@ "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", + "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2263,10 +2266,13 @@ "dev": true }, "node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.2.tgz", + "integrity": "sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==", "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, "engines": { "node": "14 || >=16.14" } From 4c519d657da467f306771a317da989d9e1b492d3 Mon Sep 17 00:00:00 2001 From: chasenicholl Date: Mon, 20 Nov 2023 14:45:43 -0500 Subject: [PATCH 12/12] update readme language to highlight local API support more clearly --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88bfba6..953a3e2 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ It is recommended when upgrading to v3.0 of the plugin from a prior version that ### Setup and Parameters -You will need to create an account at https://tempestwx.com/ and then generate a Personal Use Token https://tempestwx.com/settings/tokens. +Local API is now supported which requires no authentication. If you choose to use the non-local HTTP API you will need to create an account at https://tempestwx.com/ and then generate a Personal Use Token https://tempestwx.com/settings/tokens. - `name`: _(Required)_ Must always be set to `WeatherFlow Tempest Platform`. - `local_api`: _(Required)_ Use the Local API versus HTTP API.