UNPKG

homebridge-weatherflow-tempest

Version:

Exposes WeatherFlow Tempest Station data as Temperature Sensors, Light Sensors, Humidity Sensors and Fan Sensors (for Wind Speed).

232 lines 9.52 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TempestApi = exports.TempestSocket = void 0; const axios_1 = __importDefault(require("axios")); const dgram = __importStar(require("dgram")); const https_1 = __importDefault(require("https")); axios_1.default.defaults.timeout = 10000; // same as default interval axios_1.default.defaults.httpsAgent = new https_1.default.Agent({ keepAlive: true }); class TempestSocket { constructor(log, reuse_address) { this.log = log; 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 }); this.log.info('TempestSocket initialized.'); } start(address = '0.0.0.0', port = 50222) { this.setupSocket(address, port); this.setupSignalHandlers(); } setupSocket(address, port) { 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); } }); this.s.on('error', (err) => { this.log.error('Socket error:', err); }); } processReceivedData(data) { if (data.type === 'obs_st') { // Observation event this.setTempestData(data); } else if (data.type === 'evt_strike') { // Lightening strike event this.appendStrikeEvent(data); } } setTempestData(event) { 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 // 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]; 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% } appendStrikeEvent(data) { if (this.data) { this.data.lightning_strike_last_epoch = data.evt[0]; this.data.lightning_strike_last_distance = data.evt[1]; } } setupSignalHandlers() { 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(); }); } hasData() { return this.data !== undefined; } getStationCurrentObservation() { return this.data; } getBatteryLevel() { return this.tempest_battery_level; } } exports.TempestSocket = TempestSocket; class TempestApi { constructor(token, station_id, log) { this.max_retries = 30; this.log = log; this.token = token; this.station_id = station_id; this.data = undefined; // last sample of Observation data this.tempest_device_id = 0; this.tempest_battery_level = 0; this.log.info('TempestApi initialized.'); } async getStationObservation() { let observation; const url = `https://swd.weatherflow.com/swd/rest/observations/station/${this.station_id}`; const options = { headers: { 'Authorization': `Bearer ${this.token}`, }, validateStatus: (status) => status >= 200 && status < 300, // Default }; await axios_1.default.get(url, options) .then(response => { observation = response.data['obs'][0]; }) .catch(exception => { this.log.warn(`[WeatherFlow] ${exception}`); }); return observation; } async delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async getStationCurrentObservation(retry_count) { if (retry_count === this.max_retries) { this.log.error(`Reached max API retries: ${this.max_retries}. Stopping.`); return; } const observation = await this.getStationObservation(); if (observation === undefined) { this.log.warn('Response missing "obs" data.'); if (this.data !== undefined) { this.log.warn('Returning last cached response.'); return this.data; } else { this.log.warn(`Retrying ${retry_count + 1} of ${this.max_retries}. No cached "obs" data.`); retry_count += 1; await this.delay(1000 * retry_count); return this.getStationCurrentObservation(retry_count); } } else { this.data = observation; return this.data; } } async getTempestBatteryLevel(device_id) { const url = `https://swd.weatherflow.com/swd/rest/observations/device/${device_id}`; const options = { headers: { 'Authorization': `Bearer ${this.token}`, }, validateStatus: (status) => status >= 200 && status < 300, // Default }; await axios_1.default.get(url, options) // assumes single Tempest station .then(response => { const tempest_battery_voltage = response.data.obs[0][16]; this.tempest_battery_level = Math.round((tempest_battery_voltage - 1.8) * 100); // 2.80V = 100%, 1.80V = 0% }) .catch(exception => { this.log.warn(`[WeatherFlow] ${exception}`); }); return this.tempest_battery_level; } async getTempestDeviceId() { const url = `https://swd.weatherflow.com/swd/rest/stations/${this.station_id}`; const options = { headers: { 'Authorization': `Bearer ${this.token}`, }, validateStatus: (status) => status >= 200 && status < 300, // Default }; await axios_1.default.get(url, options) // assumes single hub with single Tempest station .then(response => { this.tempest_device_id = response.data.stations[0].devices[1].device_id; }) .catch(exception => { this.log.warn(`[WeatherFlow] ${exception}`); }); return this.tempest_device_id; } } exports.TempestApi = TempestApi; //# sourceMappingURL=tempest.js.map