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
JavaScript
"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