diff --git a/CHANGELOG.md b/CHANGELOG.md
index d65c18a..b4f9281 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.2.0
+* Added the Lightning Strike Contact Sensor, allowing configuration of both the minimum distance and time thresholds for triggering CONTACT_NOT_DETECTED.
+
## v4.1.1
* Update README.md to correctly display "Tempest" logo.
* Update README.md to include `station_id` in "Local API Config Example".
diff --git a/README.md b/README.md
index 6eb4748..0a70be6 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,14 @@
[![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins) ![npm-version](https://badgen.net/npm/v/homebridge-weatherflow-tempest?icon=npm&label) ![npm-downloads](https://badgen.net/npm/dt/homebridge-weatherflow-tempest?icon=npm&label) [![donate](https://badgen.net/badge/donate/paypal/yellow)](https://paypal.me/chasenicholl)
*New* in v4.0.0 Local API Support!
@@ -47,6 +51,7 @@ Local API is now supported which requires no authentication. If you choose to us
- `sensors[].{1}_properties.value_key`: _(Required)_ Which REST API response body key to target for its value. You can find the available value_keys in the table below.
- `sensors[].motion_properties.trigger_value`: _(Required with Motion Sensor)_ At what point (value) to trigger motion detected on/off. Minimum 1.
- `sensors[].occupancy_properties.trigger_value`: _(Required with Occupancy Sensor)_ At what point (value) to trigger occupancy detected on/off. Minimum 0.
+- `sensors[].contact_properties.trigger_distance`: _(Required with Contact Sensor)_ The minimum distance (in kilometers) at which the strike was detected to activate the contact sensor.
`{1}` Replace with Sensor: temperature, humidity, light, fan
@@ -166,6 +171,13 @@ sensor_type `{2}` | value_key | metric units | std units | additional_properties
"value_key": "wind_direction",
"trigger_value": 360
}
+ },
+ {
+ "name": "Lightening Detector",
+ "sensor_type": "Contact Sensor",
+ "contact_properties": {
+ "trigger_distance": 10
+ }
}
],
"platform": "WeatherFlowTempest"
@@ -281,6 +293,13 @@ sensor_type `{2}` | value_key | metric units | std units | additional_properties
"value_key": "wind_direction",
"trigger_value": 360
}
+ },
+ {
+ "name": "Lightening Detector",
+ "sensor_type": "Contact Sensor",
+ "contact_properties": {
+ "trigger_distance": 10
+ }
}
],
"platform": "WeatherFlowTempest"
diff --git a/config.schema.json b/config.schema.json
index 22a3d15..067052c 100644
--- a/config.schema.json
+++ b/config.schema.json
@@ -3,211 +3,231 @@
"pluginType": "platform",
"singular": false,
"schema": {
- "type": "object",
-
- "properties": {
- "name": {
+ "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
+ },
+ "local_api_shared": {
+ "title": "Share the Local API UDP port with other processes",
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "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 of 5 digits)",
+ "type": "number",
+ "default": 0
+ },
+ "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,
- "default": "WeatherFlow Tempest Platform"
- },
- "local_api": {
- "title": "Use Local API",
- "type": "boolean",
- "required": true,
- "default": true
- },
- "local_api_shared": {
- "title": "Share the Local API UDP port with other processes",
- "type": "boolean",
- "required": false,
- "default": false
- },
- "token": {
- "title": "Token",
+ "required": true
+ },
+ "sensor_type": {
"type": "string",
- "default": "",
+ "enum": [
+ "Temperature Sensor",
+ "Light Sensor",
+ "Humidity Sensor",
+ "Fan",
+ "Motion Sensor",
+ "Occupancy Sensor",
+ "Contact Sensor"
+ ],
+ "default": "Temperature Sensor"
+ },
+ "fan_properties": {
+ "title": "Fan Properties",
+ "type": "object",
"condition": {
- "functionBody": "if (model.local_api != undefined && !model.local_api) { return true; } else { return false; };"
+ "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"
+ ]
+ }
}
- },
- "station_id": {
- "title": "Station ID (Integer of 5 digits)",
- "type": "number",
- "default": 0
- },
- "interval": {
- "title": "Interval (seconds)",
- "type": "integer",
- "default": 10,
- "minimum": 1,
+ },
+ "light_properties": {
+ "title": "Light Properties",
+ "type": "object",
"condition": {
- "functionBody": "if (model.local_api != undefined && !model.local_api) { return true; } else { return false; };"
+ "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"
+ ]
+ }
}
- },
- "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"
- ],
- "description": "Note: `precip_accum_local_day` not supported when using Local API."
- },
- "trigger_value": {
- "type": "number",
- "minimum": 0,
- "description": "At what point (value) to trigger occupancy detected on/off (0 minimum)."
- }
- }
- }
- }
+ },
+ "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; };"
},
- "required": [
- "name",
- "sensor_type",
- "value_key"
- ]
- }
- },
- "required": ["local_api"],
- "dependencies": {
- "local_api": {
- "not": {
- "type": "boolean",
- "const": true
+ "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; };"
},
- "required": ["token", "station_id"]
+ "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"
+ ],
+ "description": "Note: `precip_accum_local_day` not supported when using Local API."
+ },
+ "trigger_value": {
+ "type": "number",
+ "minimum": 0,
+ "description": "At what point (value) to trigger occupancy detected on/off (0 minimum)."
+ }
+ }
+ },
+ "contact_properties": {
+ "title": "Contact Properties",
+ "type": "object",
+ "condition": {
+ "functionBody": "if (model.sensors[arrayIndices] && model.sensors[arrayIndices].sensor_type && model.sensors[arrayIndices].sensor_type === 'Contact Sensor') { return true; } else { return false; };"
+ },
+ "description": "The Contact Sensor is utilized to trigger automations in response to lightning strikes.",
+ "properties": {
+ "trigger_distance": {
+ "type": "number",
+ "minimum": 0,
+ "default": 0,
+ "description": "The minimum distance (in kilometers) at which the strike was detected to activate the contact sensor."
+ }
+ }
+ }
}
+ },
+ "required": [
+ "name",
+ "sensor_type",
+ "value_key"
+ ]
+ }
+ },
+ "required": [
+ "local_api"
+ ],
+ "dependencies": {
+ "local_api": {
+ "not": {
+ "type": "boolean",
+ "const": true
+ },
+ "required": [
+ "token",
+ "station_id"
+ ]
}
+ }
}
-}
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 7fa6049..404c171 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "homebridge-weatherflow-tempest",
- "version": "4.1.0",
+ "version": "4.2.3-beta.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "homebridge-weatherflow-tempest",
- "version": "4.1.0",
+ "version": "4.2.3-beta.1.0",
"license": "Apache-2.0",
"dependencies": {
"axios": "1.7.7"
diff --git a/package.json b/package.json
index 91bb6ab..31da732 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"private": false,
"displayName": "Homebridge WeatherFlow Tempest",
"name": "homebridge-weatherflow-tempest",
- "version": "4.1.1",
+ "version": "4.2.3-beta.1.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 11f9e2b..13d1731 100644
--- a/src/platform.ts
+++ b/src/platform.ts
@@ -57,6 +57,8 @@ export class WeatherFlowTempestPlatform implements DynamicPlatformPlugin {
feels_like: 0,
wind_chill: 0,
dew_point: 0,
+ lightning_strike_last_epoch: 0,
+ lightning_strike_last_distance: 0,
};
this.tempest_battery_level = 0;
diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts
index 57b819b..67a6414 100644
--- a/src/platformAccessory.ts
+++ b/src/platformAccessory.ts
@@ -603,6 +603,77 @@ class BatterySensor {
}
+class ContactSensor {
+ private service: Service;
+ private state: number;
+ constructor(
+ private readonly platform: WeatherFlowTempestPlatform,
+ private readonly accessory: PlatformAccessory,
+ ) {
+
+ this.state = 0;
+ this.service = this.accessory.getService(this.platform.Service.ContactSensor) ||
+ this.accessory.addService(this.platform.Service.ContactSensor);
+
+ // Create handlers for required characteristics
+ this.service.getCharacteristic(this.platform.Characteristic.ContactSensorState)
+ .onGet(this.handleCurrentStateGet.bind(this));
+
+ // Set initial value
+ this.setCharacteristicState(0); // CONTACT_DETECTED
+
+ // Update value based on a 1 second check interval
+ let tick = 0;
+ setInterval( () => {
+ tick++;
+ if (tick === 5) { // Reset Contact sensor every 5 seconds if CONTACT_NOT_DETECTED
+ tick = 0;
+ if (this.state === 1) {
+ this.setCharacteristicState(0);
+ }
+ }
+ this.setCharacteristicState(this.getState());
+ }, 1000);
+
+ }
+
+ private getState(): number {
+
+ try {
+ const lightning_strike_last_epoch: number = this.platform.observation_data.lightning_strike_last_epoch;
+ const lightning_strike_last_distance: number = this.platform.observation_data.lightning_strike_last_distance;
+ const trigger_distance: number = this.accessory.context.device.contact_properties.trigger_distance;
+ const current_epoch_now = Math.floor(Date.now() / 1000);
+ if (lightning_strike_last_epoch > 0
+ && lightning_strike_last_distance > 0
+ && lightning_strike_last_distance <= trigger_distance
+ && (current_epoch_now - lightning_strike_last_epoch) <= 5) {
+ return 1; // trigger CONTACT_NOT_DETECTED.
+ }
+ return 0;
+ } catch(exception) {
+ this.platform.log.error(exception as string);
+ return 0;
+ }
+
+ }
+
+ private setCharacteristicState(state: number): void {
+
+ this.state = state;
+ this.service.getCharacteristic(this.platform.Characteristic.ContactSensorState).updateValue(state);
+
+ }
+
+ private handleCurrentStateGet(): number {
+
+ this.platform.log.debug('Triggered GET handleCurrentStateGet for Contact Sensor state');
+ return this.getState();
+
+ }
+
+}
+
/**
* Initialize Tempest Platform (only need to do once)
*/
@@ -644,7 +715,6 @@ export class WeatherFlowTempestPlatformAccessory {
switch (this.accessory.context.device.sensor_type) {
case 'Temperature Sensor':
new TemperatureSensor(this.platform, this.accessory);
-
// Add Battery to default Temperature air_temperature sensor
if (this.accessory.context.device.temperature_properties.value_key === 'air_temperature') {
new BatterySensor(this.platform, this.accessory);
@@ -665,6 +735,9 @@ export class WeatherFlowTempestPlatformAccessory {
case 'Occupancy Sensor':
new OccupancySensor(this.platform, this.accessory);
break;
+ case 'Contact Sensor':
+ new ContactSensor(this.platform, this.accessory);
+ break;
}
}
diff --git a/src/tempest.ts b/src/tempest.ts
index 15ba9c6..3006139 100644
--- a/src/tempest.ts
+++ b/src/tempest.ts
@@ -10,27 +10,30 @@ 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
+ air_temperature: number; // C, displayed according to Homebridge and HomeKit C/F settings
feels_like: number;
wind_chill: number;
dew_point: number;
// humidity sensor
- relative_humidity: number; // %
+ relative_humidity: number; // %
// fan and motion sensor
- wind_avg: number; // m/s, used for Fan speed %
- wind_gust: number; // m/s, used for motion sensor
+ wind_avg: number; // m/s, used for Fan speed %
+ wind_gust: number; // m/s, used for motion sensor
// occupancy sensors
- barometric_pressure: number; // mbar
- precip: number; // mm/min (minute sampling)
- precip_accum_local_day: number; // mm
- wind_direction: number; // degrees
- solar_radiation: number; // W/m^2
- uv: number; // Index
-
- brightness: number; // Lux
+ barometric_pressure: number; // mbar
+ precip: number; // mm/min (minute sampling)
+ precip_accum_local_day: number; // mm
+ wind_direction: number; // degrees
+ solar_radiation: number; // W/m^2
+ uv: number; // Index
+
+ brightness: number; // Lux
+
+ lightning_strike_last_epoch: number; // timestamp in seconds
+ lightning_strike_last_distance: number; // km
}
@@ -38,13 +41,30 @@ export class TempestSocket {
private log: Logger;
private s: dgram.Socket;
- private data: object | undefined;
+ private data: Observation;
private tempest_battery_level: number;
constructor(log: Logger, reuse_address: boolean) {
this.log = log;
- this.data = undefined;
+ this.data = {
+ air_temperature: 0,
+ feels_like: 0,
+ wind_chill: 0,
+ dew_point: 0,
+ relative_humidity: 0,
+ wind_avg: 0,
+ wind_gust: 0,
+ barometric_pressure: 0,
+ precip: 0,
+ precip_accum_local_day: 0,
+ wind_direction: 0,
+ solar_radiation: 0,
+ uv: 0,
+ brightness: 0,
+ lightning_strike_last_epoch: 0,
+ lightning_strike_last_distance: 0,
+ };
this.tempest_battery_level = 0;
this.s = dgram.createSocket({ type: 'udp4', reuseAddr: reuse_address });
@@ -81,15 +101,17 @@ export class TempestSocket {
private processReceivedData(data) {
- if (data.type === 'obs_st') { // for Tempest
+ if (data.type === 'obs_st') { // Observation event
this.setTempestData(data);
+ } else if (data.type === 'evt_strike') { // Lightening strike event
+ this.appendStrikeEvent(data);
}
}
- private setTempestData(data): void {
+ private setTempestData(event): void {
- const obs = data.obs[0];
+ const obs = event.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 T = (obs[7] * 9/5) + 32; // T in F for heatindex, feelsLike and windChill calculations
@@ -103,26 +125,33 @@ export class TempestSocket {
// 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.data.air_temperature = obs[7];
+ this.data.feels_like = 5/9 * (feelsLike - 32); // convert back to C
+ this.data.wind_chill = 5/9 * (windChill - 32); // convert back to C
+ this.data.dew_point = obs[7] - ((100 - obs[8]) / 5.0); // Td = T - ((100 - RH)/5)
+ this.data.relative_humidity = obs[8];
+ this.data.wind_avg = obs[2];
+ this.data.wind_gust = obs[3];
+ this.data.barometric_pressure = obs[6];
+ this.data.precip = obs[12];
+ this.data.precip_accum_local_day = obs[12];
+ this.data.wind_direction = obs[4];
+ this.data.solar_radiation = obs[11];
+ this.data.uv = obs[10];
+ this.data.brightness = obs[9];
this.tempest_battery_level = Math.round((obs[16] - 1.8) * 100); // 2.80V = 100%, 1.80V = 0%
}
+ private appendStrikeEvent(data): void {
+
+ if (this.data) {
+ this.data.lightning_strike_last_epoch = data.evt[0];
+ this.data.lightning_strike_last_distance = data.evt[1];
+ }
+
+ }
+
private setupSignalHandlers(): void {
process.on('SIGTERM', () => {