homebridge-hilo
Version:
Plugin Homebridge (non officiel) pour la passerelle et les appareils Hilo de Hydro-Québec | Unofficial Homebridge plugin for Hydro-Québec Hilo bridge and devices
184 lines • 8.44 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = default_1;
const config_1 = require("./config");
const logger_1 = require("./logger");
const api_1 = require("./api");
const hiloApi_1 = require("./hiloApi");
const devices_1 = require("./devices");
const subscription_1 = require("./subscription");
const location_1 = require("./location");
const deviceHub_1 = require("./deviceHub");
const types_1 = require("./devices/types");
const PLUGIN_NAME = "homebridge-hilo";
const PLATFORM_NAME = "Hilo";
function default_1(api) {
api.registerPlatform(PLATFORM_NAME, Hilo);
}
class Hilo {
constructor(log, config, api, accessories = {}) {
this.log = log;
this.config = config;
this.api = api;
this.accessories = accessories;
this.pluginAccessories = {};
this.locations = [];
this.subscriptions = {};
this.staleAccessories = [];
(0, config_1.setConfig)(config);
this.config = (0, config_1.getConfig)();
if (!this.config.refreshToken) {
this.log.error("Please login with hilo in the plugin configuration page");
return;
}
(0, logger_1.setLogger)(log);
(0, api_1.setApi)(api);
log.info("Initializing Hilo platform");
api.on("didFinishLaunching" /* APIEvent.DID_FINISH_LAUNCHING */, async () => {
this.locations = await fetchLocations();
if (this.locations.length === 0) {
log.error("No locations found");
return;
}
else {
log.info(`Found ${this.locations.length} locations`);
}
const allSignalRDevices = [];
const allGraphqlDevices = [];
let signalRConnected = true;
for (const location of this.locations) {
const [hubResult, graphqlDevices] = await Promise.all([
(0, deviceHub_1.connectToDeviceHub)(location.id),
(0, location_1.fetchDevicesForLocation)(location.locationHiloId),
]);
if (!hubResult.connected) {
signalRConnected = false;
log.warn(`DeviceHub connection failed for location ${location.id}, will use cached devices`);
}
hubResult.devices.forEach((d) => (d.locationId = location.id));
allSignalRDevices.push(...hubResult.devices);
allGraphqlDevices.push(...graphqlDevices);
}
// When SignalR failed, recover cached SignalR device data from accessories
if (!signalRConnected && allSignalRDevices.length === 0) {
const cachedDevices = Object.values(this.accessories)
.map((a) => a.context.device)
.filter((d) => !!(d === null || d === void 0 ? void 0 : d.hiloId));
if (cachedDevices.length > 0) {
log.info(`Recovering ${cachedDevices.length} cached SignalR devices`);
allSignalRDevices.push(...cachedDevices);
}
}
const supportedDevices = allGraphqlDevices.filter((device) => types_1.SUPPORTED_DEVICES.includes(device.__typename));
if (supportedDevices.length === 0) {
log.error("No supported devices found");
return;
}
else {
log.info(`Found ${supportedDevices.length} supported devices`);
}
const signalRByHiloId = new Map(allSignalRDevices.map((d) => [d.hiloId, d]));
supportedDevices.forEach((device) => {
this.log.debug("Initializing device", device);
const signalRDevice = signalRByHiloId.get(device.hiloId);
if (!signalRDevice) {
if (signalRConnected) {
this.log.error(`No SignalR device found for hiloId ${device.hiloId}`);
}
else {
this.log.warn(`No SignalR device found for hiloId ${device.hiloId} (SignalR disconnected, device may appear after reconnection)`);
}
return;
}
let accessory = this.accessories[device.hiloId];
if (!accessory) {
this.log.debug(`Setting up new accessory for device ${device.hiloId}`);
accessory = this.setupNewAccessory(device, signalRDevice);
}
else {
accessory.context = {
device: signalRDevice,
graphqlDevice: device,
};
}
const pluginAccessory = devices_1.initializeHiloDevice[device.__typename](accessory, this.api);
this.pluginAccessories[device.hiloId] = pluginAccessory;
});
// Only remove stale accessories when SignalR is connected and authoritative
if (signalRConnected) {
const currentHiloIds = new Set(signalRByHiloId.keys());
this.staleAccessories = this.staleAccessories.concat(Object.values(this.accessories).filter((accessory) => {
return !currentHiloIds.has(accessory.context.device.hiloId);
}));
this.log.debug(`Found ${this.staleAccessories.length} stale accessories removing...`);
if (this.staleAccessories.length > 0) {
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, this.staleAccessories);
}
}
else {
log.warn("Skipping stale accessory removal because SignalR connection failed");
}
this.setupSubscriptions();
log.info("Hilo platform is ready");
});
}
async setupSubscriptions() {
for (const location of this.locations) {
try {
const subscription = await (0, subscription_1.setupSubscription)(location.locationHiloId, (device) => {
this.log.debug(`Device update received:`, device);
const pluginAccessory = this.pluginAccessories[device.hiloId];
if (!pluginAccessory) {
this.log.debug(`No accessory for device ${device.hiloId}`);
return;
}
pluginAccessory.updateDevice(device);
});
this.subscriptions[location.locationHiloId] = subscription;
this.log.info(`Subscribed to updates for location ${location.locationHiloId}`);
}
catch (error) {
this.log.error(`Failed to subscribe to location ${location.locationHiloId}:`, error instanceof Error ? error.message : String(error));
}
}
}
configureAccessory(accessory) {
var _a;
this.log.debug(`Configuring accessory from cache ${accessory.displayName}`);
if ((_a = accessory.context.device) === null || _a === void 0 ? void 0 : _a.hiloId) {
this.accessories[accessory.context.device.hiloId] =
accessory;
}
else {
this.log.warn(`Stale cached accessory ${accessory.displayName}, will remove`);
this.staleAccessories.push(accessory);
}
}
setupNewAccessory(device, signalRDevice) {
const uuid = this.api.hap.uuid.generate(signalRDevice.hiloId);
const accessory = new this.api.platformAccessory(signalRDevice.name.trim(), uuid);
accessory.context = {
device: signalRDevice,
graphqlDevice: device,
};
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [
accessory,
]);
this.accessories[device.hiloId] = accessory;
return accessory;
}
}
async function fetchLocations() {
(0, logger_1.getLogger)().debug("Fetching locations");
try {
const response = await hiloApi_1.hiloApi.get("/Automation/v1/api/Locations", {
params: { force: true },
});
return response.data;
}
catch (error) {
(0, logger_1.getLogger)().error("Error while fetching locations", error instanceof Error ? error.message : String(error));
return [];
}
}
//# sourceMappingURL=hilo.js.map