UNPKG

homebridge-weatherflow-tempest

Version:

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

307 lines 13.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WeatherFlowTempestPlatform = void 0; const settings_1 = require("./settings"); const platformAccessory_1 = require("./platformAccessory"); const tempest_1 = require("./tempest"); /** * WeatherFlowTempestPlatform */ class WeatherFlowTempestPlatform { constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.Service = this.api.hap.Service; this.Characteristic = this.api.hap.Characteristic; this.accessories = []; this.activeAccessory = []; // array of active Tempest sensors log.info('Finished initializing platform:', this.config.name); // Initialize observation_data this.observation_data = { air_temperature: 0, barometric_pressure: 0, relative_humidity: 0, precip: 0, precip_accum_local_day: 0, wind_avg: 0, wind_direction: 0, wind_gust: 0, solar_radiation: 0, uv: 0, brightness: 0, feels_like: 0, wind_chill: 0, dew_point: 0, lightning_strike_last_epoch: 0, lightning_strike_last_distance: 0, }; this.tempest_battery_level = 0; // Backwards compatible config check for new local_api variable if (!('local_api' in this.config)) { this.config['local_api'] = false; this.log.info('local_api config parameter not set defaulting to false.'); } // Backwards compatible config check for new local_api_shared variable if (!('local_api_shared' in this.config)) { this.config['local_api_shared'] = false; this.log.info('local_api_shared config parameter not set defaulting to false.'); } // For new install with local_api === true (Local API), token and station_id will be undefined and are not required // For local_api === false (HTTP API), make sure token is provided if ((this.config.local_api === false) && (!('token' in this.config) || (this.config.token === '<Your API Token>'))) { log.warn('WeatherFlow token not provided. You will need to create an account at https://tempestwx.com/ ' + 'and then generate a Personal Use Token https://tempestwx.com/settings/tokens.'); return; } // Make sure Station ID is present and is an integer and is 5 digits in length if (isNaN(this.config.station_id) || (this.config.station_id.toString().length < 5)) { log.warn('Station ID is not provided, is not an Integer or is less than 5 digits! Please make sure you are using the ID integer ' + 'found here: https://tempestwx.com/station/<STATION_ID>/'); return; } api.on("didFinishLaunching" /* DID_FINISH_LAUNCHING */, () => { log.info('Executed didFinishLaunching callback'); if (this.areSensorsSet() === false) { log.info('No Sensors configured. Refusing to continue.'); return; } // Initialize Tempest Interfaces if (this.config.local_api === true) { this.initializeBySocket(); } else { this.initializeByApi(); } }); } async initializeBySocket() { this.log.info('Initializing by Socket'); try { this.log.info('Using Tempest Local API.'); this.tempestSocket = new tempest_1.TempestSocket(this.log, this.config.local_api_shared); 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.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(); } catch (exception) { this.log.error(exception); } } socketDataRecieved() { 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); }); } initializeByApi() { this.log.info('Initializing by API'); try { this.log.info('Using Tempest RESTful API.'); this.tempestApi = new tempest_1.TempestApi(this.config.token, this.config.station_id, this.log); this.tempestApi.getStationCurrentObservation(0).then((observation_data) => { if (!observation_data) { this.log.warn('Failed to fetch initial Station Current Observations after retrying. Refusing to continue.'); return; } if (this.tempestApi === undefined) { return; } // Cache the observation results this.observation_data = observation_data; // Initialize sensors after first API response. this.discoverDevices(); this.log.info('discoverDevices completed'); // Remove cached sensors that are no longer required. this.removeDevices(); this.log.info('removeDevices completed'); // Determine Tempest device_id & initial battery level this.tempestApi.getTempestDeviceId().then((device_id) => { this.tempest_device_id = device_id; if (this.tempestApi === undefined) { return; } this.tempestApi.getTempestBatteryLevel(this.tempest_device_id).then((battery_level) => { if (battery_level === undefined) { this.log.warn('Failed to fetch initial Tempest battery level'); return; } this.tempest_battery_level = battery_level; }); }); // Then begin to poll the station current observations data. this.pollStationCurrentObservation(); }); } catch (exception) { this.log.error(exception); } } pollLocalStationCurrentObservation() { 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. } pollStationCurrentObservation() { // Poll Tempest API const interval = (this.config.interval || 10) * 1000; this.log.debug(`Tempest API Polling interval (ms) -> ${interval}`); setInterval(async () => { if (this.tempestApi === undefined) { return; } // Update Observation data await this.tempestApi.getStationCurrentObservation(0).then((observation_data) => { if (observation_data === undefined) { this.log.warn('observation_data is undefined, skipping update'); } else { this.observation_data = observation_data; } }); // Update Battery percentage await this.tempestApi.getTempestBatteryLevel(this.tempest_device_id).then((battery_level) => { if (battery_level === undefined) { this.log.warn('battery_level is undefined, skipping update'); } else { this.tempest_battery_level = battery_level; } }); }, interval); } /** * This function is invoked when homebridge restores cached accessories from disk at startup. * It should be used to setup event handlers for characteristics and update respective values. */ configureAccessory(accessory) { this.log.info('Loading accessory from cache:', accessory.displayName); // add the restored accessory to the accessories cache so we can track if it has already been registered this.accessories.push(accessory); } areSensorsSet() { this.log.debug('Confirming sensors configured.'); // Make sure config.sensors is set and iterable. try { // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const device of this.config.sensors) { continue; } } catch (exception) { if (exception instanceof TypeError) { this.log.warn('No Sensors are configured.'); } this.log.error(exception); return false; } this.log.debug('Sensors configured.'); return true; } /** * Discover Configured Sensors. */ discoverDevices() { try { for (const device of this.config.sensors) { // Create sensor accessory for tempest sensor value this.initAccessory(device); } } catch (exception) { this.log.error(exception); } } initAccessory(device) { let value_key = ''; switch (device.sensor_type) { case 'Temperature Sensor': value_key = device.temperature_properties['value_key']; break; case 'Humidity Sensor': value_key = device.humidity_properties['value_key']; break; case 'Light Sensor': value_key = device.light_properties['value_key']; break; case 'Fan': value_key = device.fan_properties['value_key']; break; case 'Motion Sensor': value_key = device.motion_properties['value_key']; 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'); } const uuid = this.api.hap.uuid.generate(`${device.name}-${device.sensor_type}-${value_key}`); if (value_key !== 'not_available') { const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName); // pick up any changes such as 'trigger_value' existingAccessory.context.device = device; // update accessory context information this.api.updatePlatformAccessories(this.accessories); new platformAccessory_1.WeatherFlowTempestPlatformAccessory(this, existingAccessory); // add to array of active accessories this.activeAccessory.push(existingAccessory); } 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 platformAccessory_1.WeatherFlowTempestPlatformAccessory(this, accessory); // link the accessory to the platform this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]); // add to array of active accessories this.activeAccessory.push(accessory); } } } /** * Remove Tempest inactive sensors that are no loger used. */ removeDevices() { this.accessories.forEach((accessory) => { if (!this.activeAccessory.includes(accessory)) { this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]); this.accessories.splice(this.accessories.indexOf(accessory), 1); // remove unused accessory from accessories array this.log.info(`Unused accessory: ${accessory.context.device.name} removed.`); } }); return; } } exports.WeatherFlowTempestPlatform = WeatherFlowTempestPlatform; //# sourceMappingURL=platform.js.map