homebridge-ecowitt-weather-sensors
Version:
Homebridge support for your Fine Offset weather station (Ecowitt, Ambient, Froggit, GoGen, and more)
773 lines • 42.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (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.EcowittPlatform = void 0;
const settings_1 = require("./settings");
const BASE_1 = require("./devices/BASE");
const WH25_1 = require("./devices/WH25");
const WH26_1 = require("./devices/WH26");
const WN30_1 = require("./devices/WN30");
const WN31_1 = require("./devices/WN31");
const WN34_1 = require("./devices/WN34");
const WN35_1 = require("./devices/WN35");
const WH40_1 = require("./devices/WH40");
const WH41_1 = require("./devices/WH41");
const WH45_1 = require("./devices/WH45");
const WH46_1 = require("./devices/WH46");
const WH51_1 = require("./devices/WH51");
const WH55_1 = require("./devices/WH55");
const WH57_1 = require("./devices/WH57");
const WH65_1 = require("./devices/WH65");
const WN67_1 = require("./devices/WN67");
const WS68_1 = require("./devices/WS68");
const WS80_1 = require("./devices/WS80");
const WS85_1 = require("./devices/WS85");
const WS90_1 = require("./devices/WS90");
const utils = __importStar(require("./Utils"));
const bodyParser = __importStar(require("body-parser"));
const crypto = __importStar(require("crypto"));
const arp_lookup_1 = __importDefault(require("@network-utils/arp-lookup"));
const express_1 = __importDefault(require("express"));
// eslint-disable-next-line @typescript-eslint/no-var-requires
const merge = require('deepmerge');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const querystring = require('querystring');
/**
* HomebridgePlatform
* This class is the main constructor for your plugin, this is where you should
* parse the user config and discover/register accessories with Homebridge.
*/
class EcowittPlatform {
constructor(log, config, api) {
var _a, _b, _c, _d, _e, _f, _g, _h;
this.log = log;
this.config = config;
this.api = api;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
// this is used to track restored cached accessories
this.accessories = [];
this.lastDataReport = null;
this.registeredProperties = [];
this.consumedReportData = [];
this.unconsumedReportData = [];
this.validatorsReportData = ['PASSKEY', 'MAC'];
this.requiredReportData = ['stationtype', 'dateutc', 'model'];
this.ignoreableReportData = ['runtime', 'heap', 'interval', 'freq'];
this.protocolCheckFieldsWU = ['ID', 'PASSWORD'];
this.baseStationInfo = {
model: utils.UNKNOWN,
deviceName: utils.UNKNOWN,
deviceIP: '',
serialNumber: '',
shortSerial: '',
protocol: utils.UNKNOWN,
hardwareRevision: '',
softwareRevision: '',
firmwareRevision: '',
frequency: '',
MAC: '',
PASSKEY: '',
sensors: [],
};
let mac = ((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.baseStation) === null || _b === void 0 ? void 0 : _b.mac) || ((_c = this.config) === null || _c === void 0 ? void 0 : _c.mac);
if (typeof mac === 'undefined' || !arp_lookup_1.default.isMAC(mac)) {
if (typeof ((_d = this.config) === null || _d === void 0 ? void 0 : _d.additional) === 'undefined') {
this.config.additional = {};
}
this.config.additional.validateMac = false;
mac = '00:00:00:00:00:00';
this.log.warn('Disabling MAC validation because MAC address was not provided or MAC address is invalid. '
+ 'Provide a valid MAC address to have MAC validation enabled');
}
this.baseStationInfo.serialNumber = mac.toUpperCase();
this.baseStationInfo.shortSerial = mac.replaceAll(':', '').slice(8).toUpperCase();
this.baseStationInfo.MAC = mac.replaceAll('-', ':').toUpperCase();
this.baseStationInfo.PASSKEY = crypto.createHash('md5').update(mac.toUpperCase()).digest('hex').toUpperCase();
if (utils.v1ConfigTest(this.config)) {
const v2Config = utils.v1ConfigRemapper(this.config);
this.log.warn('Plugin configuration currently has v1 properties and needs to be migrated to v2. '
+ 'A migrated v2-compatible version of your plugin configuration has been generated below, '
+ `see ${utils.MIGRATION_GUIDE_LINK} for the migration guide \n${JSON.stringify(v2Config, undefined, 2)}`);
this.config = v2Config;
this.log.debug(`Plugin config has been auto-migrated to v2 \n${JSON.stringify(this.config, undefined, 2)}`);
}
// structure of base station changed in v2.7.0 and prior versions need to be remapped
const updatedConfig = utils.baseStationRemapper(this.config);
if (JSON.stringify(updatedConfig) === JSON.stringify(this.config)) {
this.log.debug('Plugin configuration migration for base station not required');
}
else {
this.config = updatedConfig;
this.log.warn('Plugin config needs to be migrated, an auto-migrated version '
+ `of your plugin configuration has been generated below \n${JSON.stringify(updatedConfig, undefined, 2)}`);
}
let encodedPath = encodeURI(((_f = (_e = this.config) === null || _e === void 0 ? void 0 : _e.baseStation) === null || _f === void 0 ? void 0 : _f.path) || '/data/report/');
const port = ((_h = (_g = this.config) === null || _g === void 0 ? void 0 : _g.baseStation) === null || _h === void 0 ? void 0 : _h.port) || 8080;
// check and remove trailing slash to match parsedPath
if (encodedPath.endsWith('/')) {
encodedPath = encodedPath.slice(0, -1);
}
this.log.debug(`Creating data report service on ${port} '${encodedPath}'`);
this.dataReportServer = (0, express_1.default)();
this.dataReportServer.use(bodyParser.json());
this.dataReportServer.use(bodyParser.urlencoded({ extended: true }));
// depending on the vendor/protocol, the request can be POST or GET and data properties
// can be sent as query params or as JSON body
// additionally, users frequently forget to add the query prefix character (?) to the path option
// when configuring the custom server upload, so that case is handled here
this.dataReportServer.all('*', (req, res, next) => {
var _a, _b;
let parsedPath = req.path.split(/[?#&]/)[0];
if (parsedPath.endsWith('/')) {
parsedPath = parsedPath.slice(0, -1); // remove trailing slash
}
if (arp_lookup_1.default.isIP(req.socket.remoteAddress)) {
if (req.socket.remoteAddress.startsWith('::ffff:')) { // IPv6 prefix
this.baseStationInfo.deviceIP = req.socket.remoteAddress.substring(7);
}
else {
this.baseStationInfo.deviceIP = req.socket.remoteAddress;
}
this.log.debug(`Received request ${req.method} '${parsedPath}' from IP ${this.baseStationInfo.deviceIP}`);
}
else {
this.log.debug(`Received request ${req.method} '${parsedPath}'`);
}
if (parsedPath !== encodedPath && utils.falsy((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.additional) === null || _b === void 0 ? void 0 : _b.acceptAnyPath)) {
this.log.error(`Configure the base station to send data reports to path '${encodedPath}' `
+ `instead of path '${parsedPath}' or enable accept any path in advanced settings`);
res.send();
return;
}
let dataReport = {};
if (req.originalUrl.includes('?')) { // query params should be well-formatted and parseable
dataReport = merge(req.body, req.query);
}
else if (req.path.includes('&')) { // query params not formed correctly
const params = req.path.substring(req.path.indexOf('&') + 1);
dataReport = merge(req.body, querystring.parse(params));
}
else {
dataReport = req.body;
}
// WU sends with unique data report properties
if (utils.includesAny(Object.keys(dataReport), this.protocolCheckFieldsWU)) {
this.log.error('Data report appears to use the Wunderground protocol. This plugin ' +
'does not have support for the Wunderground protocol. Please ensure that the ' +
' Ecowitt protocol or Ambient protocol is used when sending data reports to this plugin');
this.baseStationInfo.protocol = utils.UNDERGROUND;
res.send();
return;
}
// AMB sends get request with data as query params
if (req.method === 'GET' && (Object.keys(req.query).length > 0 || req.path.length > 2)) {
if (this.lastDataReport === null) { // only show once on startup
this.log.warn('Data report appears to use the Ambient Weather protocol. This plugin ' +
`has beta support for Ambient Weather. Please file bug reports at ${utils.BUG_REPORT_LINK}` +
` and feature requests at ${utils.FEATURE_REQ_LINK} to help improve support for Ambient devices`);
}
this.baseStationInfo.protocol = utils.AMBIENT;
}
// ECO sends as post with data in request body
if (req.method === 'POST' && Object.keys(req.body).length > 0) {
this.baseStationInfo.protocol = utils.ECOWITT;
}
this.onDataReport(dataReport)
.then(() => {
res.send();
}).
catch((err) => {
next(err);
});
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
this.dataReportServer.use((err, req, res, next) => {
this.log.warn('An issue occurred while processing a data report. '
+ `Review the error message below and file a bug report at ${utils.BUG_REPORT_LINK} \n${err.stack}`);
res.status(500).send('Error processing data report');
});
// When this event is fired it means Homebridge has restored all cached accessories from disk.
// Dynamic Platform plugins should only register new accessories after this event was fired,
// in order to ensure they weren't added to homebridge already. This event can also be used
// to start discovery of new accessories.
this.api.on('didFinishLaunching', () => {
this.dataReportServer.listen(port, () => {
this.log.info(`Setup complete, plugin is listening for data reports on ${port} '${encodedPath}'`);
this.log.info(`See ${utils.GATEWAY_SETUP_LINK} for help with sending data reports to this plugin`);
}).on('error', (err) => {
if (err instanceof Error) {
if (err['code'] === 'EADDRINUSE') {
this.log.error(`Unable to start data report service on ${port} '${encodedPath}' because port ${port} is in use. `
+ `Try setting the port to ${port + 1} and restart Homebridge`);
}
else {
this.log.error(`Unable to start data report service on ${port} '${encodedPath}'. `
+ `Verify plugin configuration with docs at ${utils.GATEWAY_SETUP_LINK}, update plugin configuration, `
+ `and restart homebridge \n${err.stack}`);
}
}
});
});
}
//----------------------------------------------------------------------------
serviceUuid(name) {
return this.api.hap.uuid.generate(name);
}
//----------------------------------------------------------------------------
serviceId(name, channel) {
const serviceId = this.baseStationInfo.shortSerial;
if (typeof channel !== 'undefined') {
return `${serviceId}:${name}CH${channel}`;
}
else {
return `${serviceId}:${name}`;
}
}
//----------------------------------------------------------------------------
shortServiceId(name, channel) {
if (typeof channel !== 'undefined') {
return `${name}CH${channel}`;
}
else {
return `${name}`;
}
}
//----------------------------------------------------------------------------
isEcowitt() {
return this.baseStationInfo.protocol === utils.ECOWITT;
}
//----------------------------------------------------------------------------
isAmbient() {
return this.baseStationInfo.protocol === utils.AMBIENT;
}
//----------------------------------------------------------------------------
/**
* 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.debug(`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);
}
//----------------------------------------------------------------------------
async onDataReport(dataReport) {
var _a, _b, _c, _d;
if (typeof dataReport !== 'object') {
this.log.warn(`Received empty data report. Verify configuration with docs at ${utils.GATEWAY_SETUP_LINK}`);
return;
}
if (Object.keys(dataReport).length === 0) {
this.log.warn(`Received empty data report. Verify configuration with docs at ${utils.GATEWAY_SETUP_LINK}`);
return;
}
if (utils.truthy((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.additional) === null || _b === void 0 ? void 0 : _b.logDataReports)) {
this.log.info(`Received data report \n${JSON.stringify(dataReport, undefined, 2)}`);
}
else {
this.log.debug('Received data report (if you are submitting a bug report copy and paste the full data '
+ `report object below) \n${JSON.stringify(dataReport, undefined, 2)}`);
}
if (this.isAmbient()) {
dataReport = utils.dataReportTranslator(dataReport);
this.log.debug('Ambient data report has been translated to Ecowitt data report \n'
+ `${JSON.stringify(dataReport, undefined, 2)}`);
}
if (utils.truthy((_d = (_c = this.config) === null || _c === void 0 ? void 0 : _c.additional) === null || _d === void 0 ? void 0 : _d.validateMac)) {
if (!utils.includesAny(Object.keys(dataReport), this.validatorsReportData)) {
this.log.warn(`Data report must include one of ${this.validatorsReportData} to validate `
+ 'mac. Check your data report or disable MAC validation in advanced settings');
return;
}
const validatorKey = dataReport.PASSKEY !== undefined ? 'PASSKEY' : 'MAC';
if (dataReport[validatorKey] !== this.baseStationInfo.PASSKEY &&
dataReport[validatorKey] !== this.baseStationInfo.MAC) {
if (this.baseStationInfo.deviceIP.length > 0) {
// attempt to lookup MAC from the IP address from the request
// to provide user more information for properly setting AMC
const discoveredMac = await arp_lookup_1.default.toMAC(this.baseStationInfo.deviceIP);
if (discoveredMac !== null) {
this.log.warn(`Ignoring data report from MAC ${discoveredMac.toUpperCase()}, expected data report ` +
`from MAC ${this.baseStationInfo.serialNumber}. Verify MAC address is set properly in the plugin, ` +
'or disable MAC validation in advanced settings');
return;
}
else {
this.log.warn(`Ignoring data report from IP ${this.baseStationInfo.deviceIP} with unknown MAC, ` +
`expected data report from MAC ${this.baseStationInfo.serialNumber}. Verify MAC address is ` +
'set properly in the plugin, or disable MAC validation in advanced settings');
return;
}
}
else {
this.log.warn('Ignoring data report from unknown MAC address, expected data report ' +
`from MAC ${this.baseStationInfo.serialNumber}. Verify MAC address is set properly in ` +
'the plugin, or disable MAC validation in advanced settings');
return;
}
}
else {
this.log.debug('Data report MAC validation was successful');
}
}
else {
this.log.debug('MAC validation is disabled, not checking MAC or PASSKEY on data report');
}
if (!utils.includesAll(Object.keys(dataReport), this.requiredReportData)) {
this.log.warn(`Received incomplete data report. Missing one of ${this.requiredReportData}. `
+ `Verify plugin configuration with docs at ${utils.GATEWAY_SETUP_LINK}`);
return;
}
if (!this.lastDataReport) { // on first data report after startup
this.log.info('Registering accessories from data report');
this.lastDataReport = dataReport;
this.registeredProperties = Object.keys(dataReport);
this.registerAccessories(dataReport);
}
else {
const intersection = this.registeredProperties.filter(x => Object.keys(dataReport).includes(x));
if (intersection.length !== this.registeredProperties.length) {
this.log.warn('The weather data properties from the current data report are different ' +
'from the properties of the data report used to register your accessories. Try restarting ' +
'Homebridge so that the plugin re-registers devices from the data report');
}
this.lastDataReport = dataReport;
}
this.updateAccessories(dataReport);
}
//----------------------------------------------------------------------------
addSensorType(add, type, channel = undefined) {
if (add) {
this.baseStationInfo.sensors.push({
type: type,
channel: channel,
accessory: undefined,
id: undefined,
uuid: undefined,
});
if (typeof channel !== 'undefined') {
this.log.debug(`Adding ${type} channel ${channel}`);
}
else {
this.log.debug(`Adding ${type}`);
}
}
}
//----------------------------------------------------------------------------
registerAccessories(dataReport) {
var _a, _b, _c, _d;
let modelInfo = [];
// ecowitt gateway - gw (WIFI) series
if (dataReport.model.trim().startsWith('GW')) {
modelInfo = dataReport.model.trim().match(/(GW[123][0-9]{3})[ABC]?_?(.*)/);
}
// ecowitt console - hp (TFT) series
if (dataReport.model.trim().startsWith('HP')) {
modelInfo = dataReport.model.trim().match(/(HP[23][56][0-9]{2})[ABC]?_?(.*)/);
}
// ecowitt console - ws (LCD) series
if (dataReport.model.trim().startsWith('WS')) {
modelInfo = dataReport.model.trim().match(/(WS[23][389][0-9]{2})[ABC]?_?(.*)/);
}
// ecowitt console - wn (LCD) series
if (dataReport.model.trim().startsWith('WN')) {
modelInfo = dataReport.model.trim().match(/(WN[1][89][0-9]{2})[ABC]?_?(.*)/);
}
if (Array.isArray(modelInfo) && modelInfo.length === 3) { // 3 match groups always expected
this.baseStationInfo.deviceName = modelInfo[1];
}
// parse out software version info if possible from station type
if (!dataReport.stationtype.trim().startsWith('EasyWeather')) {
const stationTypeInfo = dataReport.stationtype.split('_');
this.baseStationInfo.softwareRevision = stationTypeInfo[stationTypeInfo.length - 1];
}
if (dataReport.model !== undefined) {
this.baseStationInfo.model = dataReport.model;
}
if (dataReport.freq !== undefined) {
this.baseStationInfo.frequency = dataReport.freq;
}
const hideConfig = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.hidden) || {};
const hideConfigCustom = ((_b = this.config) === null || _b === void 0 ? void 0 : _b.customHidden) || [];
const hidden = Object.keys(hideConfig).filter(k => !!hideConfig[k]).concat(hideConfigCustom);
const validateIndoor = dataReport.tempinf !== undefined && dataReport.humidityin !== undefined;
const validateTHP = dataReport.wh25batt !== undefined; // THP monitor may be providing indoor metrics
if (!utils.includesAll(hidden, ['BASE']) && !utils.includesAll(hidden, BASE_1.BASE.properties)) {
if (this.baseStationInfo.deviceName !== utils.UNKNOWN) {
if (!validateTHP && validateIndoor) { // indoor metrics not covered by THP monitor
this.addSensorType(true, this.baseStationInfo.deviceName);
}
else if (!validateIndoor) {
this.log.warn('Base station was detected from data report, but indoor data properties '
+ 'do not exist on data report so base station accessory will not be created');
}
else {
this.log.warn('Base station was detected from data report, but indoor data properties appear '
+ 'to come from indoor companion sensor so base station accessory will not be created');
}
}
else {
if (!validateTHP && validateIndoor) { // indoor metrics not covered by THP monitor
this.log.warn('Base station was not recognized from data report, but a base gateway accessory '
+ 'will still be created. To add official support for your base station, please file a feature '
+ `request at ${utils.FEATURE_REQ_LINK}`);
this.addSensorType(true, 'BASE');
}
else {
this.log.debug('Base station was not recognized from data report, but it appears a '
+ 'base station accessory does not need to be created. To add support for your '
+ `base station, please file a feature request at ${utils.FEATURE_REQ_LINK}`);
}
}
}
if (!utils.includesAny(hidden, ['WS90']) && !utils.includesAll(hidden, WS90_1.WS90.properties)) {
this.addSensorType(dataReport.wh90batt !== undefined, 'WS90');
}
if (!utils.includesAny(hidden, ['WS85']) && !utils.includesAll(hidden, WS85_1.WS85.properties)) {
this.addSensorType(dataReport.wh85batt !== undefined, 'WS85');
}
if (!utils.includesAny(hidden, ['WS80']) && !utils.includesAll(hidden, WS80_1.WS80.properties)) {
this.addSensorType(dataReport.wh80batt !== undefined, 'WS80');
}
if (!utils.includesAny(hidden, ['WS68']) && !utils.includesAll(hidden, WS68_1.WS68.properties)) {
this.addSensorType(dataReport.wh68batt !== undefined, 'WS68');
}
// WH65 and WN67 are the same sensor type, except WN67 does not have solarRadiation and UVIndex
if (!utils.includesAny(hidden, ['WH65']) && !utils.includesAll(hidden, WH65_1.WH65.properties)) {
this.addSensorType(dataReport.wh65batt !== undefined && dataReport.uv !== undefined, 'WH65');
}
if (!utils.includesAny(hidden, ['WN67']) && !utils.includesAll(hidden, WN67_1.WN67.properties)) {
this.addSensorType(dataReport.wh65batt !== undefined && dataReport.uv === undefined, 'WN67');
}
if (!utils.includesAny(hidden, ['WH57']) && !utils.includesAll(hidden, WH57_1.WH57.properties)) {
this.addSensorType(dataReport.wh57batt !== undefined, 'WH57');
}
if (!utils.includesAny(hidden, ['WH55']) && !utils.includesAll(hidden, WH55_1.WH55.properties)) {
for (let channel = 1; channel <= 4; channel++) {
if (!utils.includesAny(hidden, [`WH55CH${channel}`])) {
this.addSensorType(dataReport[`leakbatt${channel}`] !== undefined, 'WH55', channel);
}
}
}
if (!utils.includesAny(hidden, ['WH51']) && !utils.includesAll(hidden, WH51_1.WH51.properties)) {
for (let channel = 1; channel <= 16; channel++) {
if (!utils.includesAny(hidden, [`WH51CH${channel}`])) {
this.addSensorType(dataReport[`soilbatt${channel}`] !== undefined, 'WH51', channel);
}
}
}
// WH45 and WH46 are the same sensor type, except WH45 does not have PM1.0 and PM4.0
if (!utils.includesAny(hidden, ['WH46']) && !utils.includesAll(hidden, WH46_1.WH46.properties)) {
this.addSensorType(dataReport.co2_batt !== undefined && dataReport.pm1_co2 !== undefined, 'WH46');
}
if (!utils.includesAny(hidden, ['WH45']) && !utils.includesAll(hidden, WH45_1.WH45.properties)) {
this.addSensorType(dataReport.co2_batt !== undefined && dataReport.pm1_co2 === undefined, 'WH45');
}
if (!utils.includesAny(hidden, ['WH41']) && !utils.includesAll(hidden, WH41_1.WH41.properties)) {
for (let channel = 1; channel <= 4; channel++) {
if (!utils.includesAny(hidden, [`WH41CH${channel}`])) {
this.addSensorType(dataReport[`pm25batt${channel}`] !== undefined, 'WH41', channel);
}
}
}
if (!utils.includesAny(hidden, ['WH40']) && !utils.includesAll(hidden, WH40_1.WH40.properties)) {
this.addSensorType(dataReport.wh40batt !== undefined, 'WH40');
}
if (!utils.includesAny(hidden, ['WN35']) && !utils.includesAll(hidden, WN35_1.WN35.properties)) {
for (let channel = 1; channel <= 8; channel++) {
if (!utils.includesAny(hidden, [`WN35CH${channel}`])) {
this.addSensorType(dataReport[`leaf_batt${channel}`] !== undefined, 'WN35', channel);
}
}
}
if (!utils.includesAny(hidden, ['WN34']) && !utils.includesAll(hidden, WN34_1.WN34.properties)) {
for (let channel = 1; channel <= 8; channel++) {
if (!utils.includesAny(hidden, [`WN34CH${channel}`])) {
this.addSensorType(dataReport[`tf_batt${channel}`] !== undefined, 'WN34', channel);
}
}
}
// WN31 and WN30 are the same sensor type, except WN30 does not have humidity
if (!utils.includesAny(hidden, ['WN31']) && !utils.includesAll(hidden, WN31_1.WN31.properties)) {
for (let channel = 1; channel <= 8; channel++) {
if (!utils.includesAny(hidden, [`WN31CH${channel}`])) {
this.addSensorType(dataReport[`batt${channel}`] !== undefined && dataReport[`humidity${channel}`] !== undefined, 'WN31', channel);
}
}
}
if (!utils.includesAny(hidden, ['WN30']) && !utils.includesAll(hidden, WN30_1.WN30.properties)) {
for (let channel = 1; channel <= 8; channel++) {
if (!utils.includesAny(hidden, [`WN30CH${channel}`])) {
this.addSensorType(dataReport[`batt${channel}`] !== undefined && dataReport[`humidity${channel}`] === undefined, 'WN30', channel);
}
}
}
if (!utils.includesAny(hidden, ['WH26']) && !utils.includesAll(hidden, WH26_1.WH26.properties)) {
this.addSensorType(dataReport.wh26batt !== undefined, 'WH26');
}
if (!utils.includesAny(hidden, ['WH25']) && !utils.includesAll(hidden, WH25_1.WH25.properties)) {
this.addSensorType(dataReport.wh25batt !== undefined, 'WH25');
}
if (this.baseStationInfo.sensors.length === 0) {
this.log.warn('No devices discovered from data report. Verify plugin configuration with docs '
+ `at ${utils.GATEWAY_SETUP_LINK}, and/or file a feature request to support your weather devices at ${utils.FEATURE_REQ_LINK}`);
return;
}
this.log.debug(`StationInfo: ${JSON.stringify(this.baseStationInfo, undefined, 2)}`);
for (const sensor of this.baseStationInfo.sensors) {
const accessoryId = this.serviceId(sensor.type, sensor.channel);
const accessoryUuid = this.serviceUuid(accessoryId);
sensor.id = accessoryId;
sensor.uuid = accessoryUuid;
const existingAccessory = this.accessories.find(acc => acc.UUID === accessoryUuid);
if (existingAccessory) {
// the accessory already exists
this.log.info(`Restoring existing accessory from cache - type: ${existingAccessory.displayName}, uuid: ${existingAccessory.UUID}`);
this.createAccessory(sensor, existingAccessory);
}
else {
// create a new sensor accessory
const accessory = new this.api.platformAccessory(sensor.type, accessoryUuid);
this.createAccessory(sensor, accessory);
if (typeof sensor.channel !== 'undefined') {
this.log.info(`Adding new accessory - type: ${sensor.type}, channel: ${sensor.channel}, uuid: ${sensor.uuid}`);
}
else {
this.log.info(`Adding new accessory - type: ${sensor.type}, uuid: ${sensor.uuid}`);
}
// link the sensor accessory to the platform
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [
accessory,
]);
}
if (typeof sensor.accessory !== 'undefined' && sensor.accessory.unusedData.length > 0) {
this.log.info(`Note that accessory ${sensor.type} does not currently use and/or display the following ` +
`data: ${sensor.accessory.unusedData}`);
}
if (typeof sensor.accessory !== 'undefined') {
this.consumedReportData.push(...sensor.accessory.requiredData);
this.consumedReportData.push(...sensor.accessory.optionalData);
this.consumedReportData.push(...sensor.accessory.unusedData);
}
}
// remove any accessory that was previously cached but is no longer present in data report
const sensorUuids = this.baseStationInfo.sensors.map(s => s.uuid);
const accessoryUuids = this.accessories.map(acc => acc.UUID);
const accessoriesToRemove = accessoryUuids.filter(x => !sensorUuids.includes(x));
for (const accessoryUuid of accessoriesToRemove) {
const accessory = this.accessories.find(acc => acc.UUID === accessoryUuid);
if (typeof accessory === 'undefined') {
continue;
}
if (utils.truthy((_c = this.config.additional) === null || _c === void 0 ? void 0 : _c.removeStaleDevices)) {
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
this.log.info(`Removing existing accessory from cache - type: ${accessory.displayName}, uuid: ${accessory.UUID}`);
}
else {
this.log.warn(`Existing accessory was detected as stale but was not removed - type: ${accessory.displayName}, ` +
`uuid: ${accessory.UUID}. Remove manually through Homebridge, or enable Stale Device Removal in ` +
'advanced settings to auto-remove');
}
}
// determine which data from the data report were not used with any detected sensors
let unconsumed = Object.keys(dataReport).filter(x => !this.consumedReportData.includes(x));
unconsumed = unconsumed.filter(x => !this.requiredReportData.includes(x));
unconsumed = unconsumed.filter(x => !this.validatorsReportData.includes(x));
unconsumed = unconsumed.filter(x => !this.ignoreableReportData.includes(x));
this.unconsumedReportData = unconsumed;
if (unconsumed.length > 0 && Object.keys(((_d = this.config) === null || _d === void 0 ? void 0 : _d.hidden) || {}).length === 0) {
this.log.info(`There was unused data from data report ${unconsumed}. This may indicate that a sensor was not ` +
'successfully discovered. Try restarting Homebridge so that the plugin re-registers ' +
'devices from the data report');
}
else {
this.log.debug('All data from data report was consummable');
}
// validate data report interval
if (typeof (dataReport === null || dataReport === void 0 ? void 0 : dataReport.interval) !== 'undefined') {
if (parseInt(dataReport.interval) < 10) {
this.log.warn('The lowest recommended data report interval is 10s, please reconfigure your base ' +
'station to publish data reports no more than once every 10 seconds');
}
}
}
createAccessory(sensor, accessory) {
switch (sensor.type) {
case 'BASE': // unknown
case 'GW1000': // gateways
case 'GW1100':
case 'GW1200':
case 'GW2000':
case 'GW3000':
case 'HP2560': // HP consoles
case 'HP2561':
case 'HP2564':
case 'WS2320': // WS consoles
case 'WS2350':
case 'WS2900':
case 'WS2910':
case 'WS3800':
case 'WS3820':
case 'WS3900':
case 'WS3910':
case 'WN1820': // WN consoles
case 'WN1821':
case 'WN1900':
case 'WN1910':
case 'WN1920':
case 'WN1980':
sensor.accessory = new BASE_1.BASE(this, accessory, sensor.type);
break;
case 'WH25':
sensor.accessory = new WH25_1.WH25(this, accessory);
break;
case 'WH26':
sensor.accessory = new WH26_1.WH26(this, accessory);
break;
case 'WN30':
sensor.accessory = new WN30_1.WN30(this, accessory, sensor.channel);
break;
case 'WN31':
sensor.accessory = new WN31_1.WN31(this, accessory, sensor.channel);
break;
case 'WN34':
sensor.accessory = new WN34_1.WN34(this, accessory, sensor.channel);
break;
case 'WN35':
sensor.accessory = new WN35_1.WN35(this, accessory, sensor.channel);
break;
case 'WH40':
sensor.accessory = new WH40_1.WH40(this, accessory);
break;
case 'WH41':
sensor.accessory = new WH41_1.WH41(this, accessory, sensor.channel);
break;
case 'WH45':
sensor.accessory = new WH45_1.WH45(this, accessory);
break;
case 'WH46':
sensor.accessory = new WH46_1.WH46(this, accessory);
break;
case 'WH51':
sensor.accessory = new WH51_1.WH51(this, accessory, sensor.channel);
break;
case 'WH55':
sensor.accessory = new WH55_1.WH55(this, accessory, sensor.channel);
break;
case 'WH57':
sensor.accessory = new WH57_1.WH57(this, accessory);
break;
case 'WH65':
sensor.accessory = new WH65_1.WH65(this, accessory);
break;
case 'WN67':
sensor.accessory = new WN67_1.WN67(this, accessory);
break;
case 'WS68':
sensor.accessory = new WS68_1.WS68(this, accessory);
break;
case 'WS80':
sensor.accessory = new WS80_1.WS80(this, accessory);
break;
case 'WS85':
sensor.accessory = new WS85_1.WS85(this, accessory);
break;
case 'WS90':
sensor.accessory = new WS90_1.WS90(this, accessory);
break;
default:
this.log.error(`Unsupported device type for ${sensor.type}. Please file a feature request to support `
+ `additional devices at ${utils.FEATURE_REQ_LINK}`);
break;
}
}
//----------------------------------------------------------------------------
updateAccessories(dataReport) {
var _a;
const dataDate = new Date(dataReport.dateutc);
dataDate.setMinutes(dataDate.getMinutes() - dataDate.getTimezoneOffset()); // timezone correction
const dateDiff = Math.abs(new Date().getTime() - dataDate.getTime());
let threshold = (dataReport === null || dataReport === void 0 ? void 0 : dataReport.interval) || 30;
threshold = parseInt(threshold) * 4;
if (dateDiff >= threshold * 1000) {
if (utils.truthy((_a = this.config.additional) === null || _a === void 0 ? void 0 : _a.validateTimestamp)) {
this.log.error(`Received data report for ${dataReport.dateutc} UTC which appears to be old, `
+ 'discarding data report. To process old data reports, disable timestamp validation in advanced settings');
return;
}
else {
this.log.info(`Received data report for ${dataReport.dateutc} UTC which appears to be old, `
+ 'this could indicate an issue if it occurs frequently');
}
}
else {
this.log.debug(`Received new data report for ${dataReport.dateutc} UTC`);
}
for (const sensor of this.baseStationInfo.sensors) {
if (typeof sensor.channel !== 'undefined') {
this.log.debug(`Updating sensor - type: ${sensor.type} channel: ${sensor.channel}`);
}
else {
this.log.debug(`Updating sensor - type: ${sensor.type}`);
}
try {
if (typeof sensor.accessory !== 'undefined') {
sensor.accessory.update(dataReport);
}
else {
this.log.warn(`Skipping update on ${sensor.type}, accessory is not defined. Please file `
+ `bug reports at ${utils.BUG_REPORT_LINK}`);
}
}
catch (err) {
let stack = undefined;
let message = undefined;
if (err instanceof Error) {
stack = err.stack;
message = err.message;
}
else {
stack = String(err);
message = String(err);
}
if (message.includes('Update on') && message.includes('requires data')) {
this.log.warn(`An issue occurred while updating sensor values for ${sensor.type}. ${message}. ` +
`If you see this warning repeatedly, use the plugin UI to hide the ${sensor.type} device, ` +
`and report a bug to ${utils.BUG_REPORT_LINK}`);
}
else {
this.log.warn(`An issue occurred while updating sensor values for ${sensor.type}. Review the error message below `
+ `and file a bug report if needed at ${utils.BUG_REPORT_LINK} \n${stack}`);
}
}
}
}
}
exports.EcowittPlatform = EcowittPlatform;
//# sourceMappingURL=EcowittPlatform.js.map