homebridge-homewizard-energy-socket
Version:
This Homebridge plugin exposes your HomeWizard Energy Sockets to Apple HomeKit. So you can use the Home App to switch your Energy Sockets on or off and integrate the Energy Sockets into your Home Automations.
248 lines • 13.3 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HomebridgeHomeWizardEnergySocket = void 0;
const bonjour_service_1 = require("bonjour-service");
const settings_1 = require("./settings");
const energy_socket_accessory_1 = require("./energy-socket-accessory");
const zod_1 = require("zod");
const config_schema_1 = require("./config.schema");
const homewizard_energy_api_1 = require("homewizard-energy-api");
class HomebridgeHomeWizardEnergySocket {
constructor(log, config, api) {
this.log = log;
this.api = api;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
this.cachedAccessories = [];
this.bonjour = null;
const loggerPrefix = `[Platform Setup] -> `;
this.loggerPrefix = loggerPrefix;
this.config = config;
this.log.debug(loggerPrefix, 'Finished initializing platform:', config.name);
this.api.on("didFinishLaunching", () => {
var _a;
this.log.debug(loggerPrefix, 'Executed didFinishLaunching callback');
if (!this.isValidConfigSchema(this.config)) {
this.log.error(this.loggerPrefix, `Please fix the issues in your config.json file for this plugin. Once fixed, restart Homebridge.`);
return;
}
if (!((_a = this.config.energySockets) === null || _a === void 0 ? void 0 : _a.length)) {
this.startDiscoveringDevices();
return;
}
this.handleEnergySocketsFromConfig();
});
this.api.on("shutdown", () => {
this.stopDiscoveringDevices();
});
}
configureAccessory(accessory) {
this.log.debug(this.loggerPrefix, `Loading accessory from cache: ${accessory.displayName}`);
this.cachedAccessories.push(accessory);
}
isStaleCachedAccessory(cachedAccessory, energySocketsConfig) {
if (!energySocketsConfig)
return false;
const configIps = energySocketsConfig.map(energySocket => energySocket.ip);
const accessoryIp = cachedAccessory.context.energySocket.ip;
const isIpStillInConfig = configIps.includes(accessoryIp);
return !isIpStillInConfig;
}
stopDiscoveringDevices() {
if (this.bonjour) {
this.log.info(this.loggerPrefix, 'Stopping automatic discovering Energy Sockets in your network...');
this.bonjour.destroy();
this.bonjour = null;
}
}
startDiscoveringDevices() {
this.log.info(this.loggerPrefix, 'Automatically discovering Energy Sockets in your network...');
this.bonjour = new bonjour_service_1.Bonjour();
const browser = this.bonjour.find({
protocol: homewizard_energy_api_1.MDNS_DISCOVERY_PROTOCOL,
type: homewizard_energy_api_1.MDNS_DISCOVERY_TYPE,
});
browser.on('up', (service) => {
this.handleDiscoveredService(service);
});
browser.on('down', (service) => {
this.log.info(this.loggerPrefix, `HomeWizard device appears to be down: ${service.host}`);
});
browser.on('error', (error) => {
this.log.error(this.loggerPrefix, `Error while discovering devices: ${error.message}`);
});
}
isDeviceApiEnabled(txtRecord) {
return txtRecord.api_enabled === '1';
}
isDeviceProductTypeSupported(txtRecord) {
return txtRecord.product_type === 'HWE-SKT';
}
handleDiscoveredService(service) {
return __awaiter(this, void 0, void 0, function* () {
const txtRecord = service.txt;
if (!this.isDeviceProductTypeSupported(txtRecord)) {
this.log.debug(this.loggerPrefix, `Found a device that is not an Energy Socket, skipping`, JSON.stringify(txtRecord));
return;
}
if (!this.isDeviceApiEnabled(txtRecord)) {
this.log.info(this.loggerPrefix, `Found a Energy Socket, but it has not enabled the "Local API" setting, skipping. If you want to use this device, please enable the "Local API" setting in the HomeWizard app for Energy Socket with ID: ${txtRecord.serial}. Otherwise, you can ignore this message.`, JSON.stringify(txtRecord));
return;
}
try {
const { energySocketProperties, api } = yield this.getEnergySocketPropertiesFromService(service);
this.addAccessory(energySocketProperties, api);
}
catch (error) {
this.log.error(this.loggerPrefix, `Error while handling discovered service: ${JSON.stringify(error)}`);
}
});
}
isValidConfigSchema(config) {
try {
config_schema_1.configSchema.parse(config);
return true;
}
catch (err) {
if (err instanceof zod_1.ZodError) {
const mappedErrors = err.errors.map(err => {
return `${err.message} at ${err.path.join('.')}`;
});
this.log.error(this.loggerPrefix, `There is an error in your config: ${JSON.stringify(mappedErrors)}`);
return false;
}
this.log.error(this.loggerPrefix, `A unknown error happened while validation your config: ${JSON.stringify(err)}`);
return false;
}
}
handleEnergySocketsFromConfig() {
return __awaiter(this, void 0, void 0, function* () {
const energySocketsConfig = this.config.energySockets;
if (!energySocketsConfig || !energySocketsConfig.length) {
this.log.warn(this.loggerPrefix, `No Energy Sockets are configured, we stop. You can configure them in the config.json`);
return;
}
this.log.debug(this.loggerPrefix, `Found ${energySocketsConfig === null || energySocketsConfig === void 0 ? void 0 : energySocketsConfig.length} Energy Sockets in config, skipping automatic discovery...`);
const staleCachedAccessories = this.cachedAccessories.filter(accessory => {
return this.isStaleCachedAccessory(accessory, energySocketsConfig);
});
if (staleCachedAccessories.length) {
this.log.debug(this.loggerPrefix, `Found ${staleCachedAccessories.length} stale cached accessories. We will remove them...`);
for (const staleCachedAccessory of staleCachedAccessories) {
this.log.debug(this.loggerPrefix, `Removing stale cached accessory: ${staleCachedAccessory.displayName} (${staleCachedAccessory.UUID})`);
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [staleCachedAccessory]);
const updatedCachedAccessories = this.cachedAccessories.filter(accessory => accessory.UUID !== staleCachedAccessory.UUID);
this.cachedAccessories = [...updatedCachedAccessories];
}
}
for (const energySocket of energySocketsConfig) {
try {
const { energySocketProperties, api } = yield this.getEnergySocketPropertiesFromIp(energySocket.ip, energySocket.name);
this.addAccessory(energySocketProperties, api);
}
catch (error) {
this.log.error(this.loggerPrefix, `Error while handling energy socket from config: ${JSON.stringify(error)}`);
}
}
});
}
addAccessory(energySocketProperties, api) {
try {
const existingAccessory = this.cachedAccessories.find(accessory => accessory.UUID === energySocketProperties.uuid);
if (!existingAccessory) {
this.log.info(this.loggerPrefix, 'Adding new accessory:', energySocketProperties.displayName, energySocketProperties.apiUrl, energySocketProperties.uuid);
const newAccessory = new this.api.platformAccessory(energySocketProperties.displayName, energySocketProperties.uuid);
newAccessory.context.energySocket = energySocketProperties;
this.attachAccessoryToPlatform(newAccessory, api);
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [newAccessory]);
return;
}
this.log.info(this.loggerPrefix, `Restoring existing accessory from cache: ${existingAccessory.displayName}`);
existingAccessory.context.energySocket = energySocketProperties;
this.api.updatePlatformAccessories([existingAccessory]);
this.attachAccessoryToPlatform(existingAccessory, api);
}
catch (error) {
this.log.error(this.loggerPrefix, `Error while adding the accessory: ${JSON.stringify(error)}`);
}
}
attachAccessoryToPlatform(accessory, api) {
this.log.debug(this.loggerPrefix, 'Attaching accessory to platform:', accessory.displayName);
new energy_socket_accessory_1.EnergySocketAccessory(this, accessory, api);
}
getEnergySocketPropertiesFromIp(ip, configName) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
this.log.info(this.loggerPrefix, `Using IP ${ip} to find information about the Energy Socket...`);
const energySocketConfig = (_a = this.config.energySockets) === null || _a === void 0 ? void 0 : _a.find(energySocket => energySocket.ip === ip);
const apiUrl = `http://${ip}`;
try {
const api = new homewizard_energy_api_1.EnergySocketApi(apiUrl, {
polling: {
interval: settings_1.POLLING_INTERVAL,
},
});
const [basicInformation, data] = yield Promise.all([
api.getBasicInformation(),
api.getData(),
]);
const activePower = data.active_power_w || null;
const firmwareVersion = basicInformation.firmware_version;
const productName = basicInformation.product_name;
const productType = basicInformation.product_type;
const serialNumber = basicInformation.serial;
const apiVersion = basicInformation.api_version;
const uuid = this.api.hap.uuid.generate(serialNumber);
const displayName = configName ? configName : `${productName} ${serialNumber}`;
const energySocketProperties = {
uuid,
ip,
apiVersion,
apiUrl,
serialNumber: serialNumber,
productName,
displayName,
productType,
firmwareVersion,
activePower,
config: energySocketConfig,
};
this.log.debug(this.loggerPrefix, 'Will use this info from service to setup the accessory: ', JSON.stringify(energySocketProperties));
this.log.info(this.loggerPrefix, 'Found Energy Socket: ', JSON.stringify(energySocketProperties));
return { energySocketProperties, api };
}
catch (error) {
const errorMessage = `Could not get basic information from the Energy Socket, skipping: ${JSON.stringify(error)}`;
this.log.error(this.loggerPrefix, errorMessage);
throw new Error(errorMessage);
}
});
}
getEnergySocketPropertiesFromService(service) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
this.log.debug(this.loggerPrefix, `Received data from Bonjour service: ${JSON.stringify(service)}`);
const ip = (_a = service.addresses) === null || _a === void 0 ? void 0 : _a[0];
try {
const energySocketProperties = yield this.getEnergySocketPropertiesFromIp(ip);
return energySocketProperties;
}
catch (error) {
const errorMessage = `Could not get basic information from the Energy Socket, skipping: ${JSON.stringify(error)}`;
this.log.error(this.loggerPrefix, errorMessage);
throw new Error(errorMessage);
}
});
}
}
exports.HomebridgeHomeWizardEnergySocket = HomebridgeHomeWizardEnergySocket;
//# sourceMappingURL=platform.js.map