homebridge-smartsystem
Version:
SmartServer (Proxy Websockets to TCP sockets, Smappee MQTT, Duotecno IP Nodes, Homekit interface)
298 lines (249 loc) • 9.88 kB
text/typescript
import { PlatformConfig, Sanitizers, UnitExtendedType } from "../duotecno/types";
import { log, setLogFunction, LogLevel, logSettings } from "../duotecno/logger";
import { API, Logger } from "homebridge";
import { System } from "../duotecno/system";
import { Smappee } from "./smappee";
import { Power, SmartApp } from "./smartapp";
import { Dimmer } from "../accessories/dimmer";
import { Unit } from "../duotecno/protocol";
import { Switch } from "../accessories/switch";
import { Bulb } from "../accessories/bulb";
import { WindowCovering } from "../accessories/windowcovering";
import { GarageDoor } from "../accessories/garagedoor";
import { Mood } from "../accessories/mood";
import { Temperature } from "../accessories/temperature";
import { Base } from "./base";
import { Accessory } from "../accessories/accessory";
import { SocApp } from "./socapp";
import { Door } from "../accessories/door";
import { Lock } from "../accessories/lock";
import { P1 } from "./p1";
import { Shelly } from "./shelly";
import { kConfigFiles, setConfig } from "../duotecno/config";
import { readFileSync } from "fs";
import { cleanStart, setProxyConfig } from "./proxy";
export class Platform extends Base {
homebridgeAPI: API;
config: PlatformConfig;
system: System;
power: Power;
smartapp: SmartApp;
smartsoc: SocApp;
accessoryList: Array<Accessory> = [];
ready = false;
startWaiting = 0;
constructor(logger: Logger, pConfig: PlatformConfig, api: API) {
super("homebridge");
if (logger.info) setLogFunction(logger.info);
this.config = {... pConfig};
this.config.debug = (pConfig && ("debug" in pConfig)) ? pConfig.debug : false;
if (this.config.debug) logSettings["homebridge"] = LogLevel.debug;
if (typeof this.config.system.cmasters === "undefined") {
log("homebridge", "try doing upgrade from pre v6.2 versions");
this.doUpgradeV62();
}
log("homebridge", "running in " + ((this.config.debug) ? "debug" : "prod") + " mode in directory: " + process.cwd());
log("homebridge", "with config:" + JSON.stringify(this.config, null, 2));
// save config for other components
setConfig(this.config);
// remember api for later use
this.homebridgeAPI = api;
if (this.config.system) {
try {
this.system = new System();
this.system.openMasters(true);
this.system.emitter.on('ready', this.systemReady.bind(this));
this.system.emitter.on('update', this.updateState.bind(this));
} catch(err) {
log("homebridge", err);
if (!this.system) {
log("homebridge", "platform - Can't run without a System.");
return;
}
}
} else {
log("homebridge", "platform - No System configured -> can't run !!!");
return;
}
// start power managers
this.power = {};
this.startPower("smappee", Smappee);
this.startPower("p1", P1);
this.startPower("shelly", Shelly);
// startup a smartApp if configured
if (this.config.smartapp) {
try {
this.smartapp = new SmartApp(this.system, this.power, this);
this.smartapp.serve();
} catch(err) {
log("homebridge", err);
if (!this.smartapp) {
log("homebridge", "platform - No SmartApp started.");
}
}
} else {
log("homebridge", "platform - No SmartApp configured.");
}
// startup a SocApp if configured -> for proxy (not needed for normal operations)
if (this.config.socapp) {
try {
// reuse the config file from the smartapp
this.smartsoc = new SocApp(this.system, 'socapp');
this.smartsoc.serve();
} catch(err) {
log("homebridge", err);
if (!this.smartsoc) {
log("homebridge", "platform - No SocApp started.");
}
}
} else {
log("homebridge", "platform - No SocApp configured.");
}
if (this.config.proxy && this.config.proxy?.uniqueId) {
this.config.proxy.kind = "gw";
setProxyConfig(this.config.proxy);
cleanStart(true);
log("homebridge", "platform - Proxy configured: " + JSON.stringify(this.config.proxy, null, 2));
} else {
log("homebridge", "platform - No Proxy configured.");
}
}
startPower(type: string, PowerMgr: any) {
// startup a power handler, if configured
if (this.config[type]) {
try {
this.power[type] = new PowerMgr(this.system);
} catch(err) {
log("homebridge", err);
if (!this.power[type]) {
log("homebridge", "platform - No " + type + " started.");
}
}
} else {
log("homebridge", "platform - No " + type + " configured.");
}
}
updateState(unit: Unit) {
const accessory = this.accessoryList.find((acc: Accessory) => unit.isUnit(acc.unit));
if (accessory) {
accessory.updateState();
}
}
systemReady() {
const inConfig = this.system.activeUnitsConfig().length;
const inSystem = this.system.allActiveUnits().length;
log("homebridge", "=== SYSTEM READY received update -> addMasters: " + inSystem + " of " + inConfig + " ===");
this.ready = (inSystem == inConfig);
// trigger status request of all active units in 2 seconds.
setTimeout( async() => {
let units = this.system.allActiveUnits();
for (let u of units) {
await u.node.master.requestUnitStatus(u)
}
}, 2000);
}
accessories(callback) {
// waiting until the database is complete or give up after 5 minutes
const kMaxWaiting = 5 * 60;
const inConfig = this.system.activeUnitsConfig().length;
const inSystem = this.system.allActiveUnits().length;
this.ready = (inSystem == inConfig);
if (this.ready) {
log("homebridge", "=== running after timeout of " + this.startWaiting + " secs --> " +
inSystem + " == " + inConfig + " units -> " + (inConfig == inSystem) + ", will start in 10 seconds ===");
setTimeout( () => { this.doAccessories(callback) }, 10 * 1000);
} else if (this.startWaiting > kMaxWaiting) {
// give up, retry connection to the masters, a manager like "forever" or "pm2" will restart us.
log("homebridge", "=== giving up after " + kMaxWaiting + " secs -> auto restart system ===");
process.exit(-1)
} else {
// wait another 5 seconds.
log("homebridge", "=== waiting >> found " + inSystem + " units out of " + inConfig + " selected after " + this.startWaiting + " sec ===");
this.startWaiting += 5;
setTimeout( () => { this.accessories(callback) }, 5000);
}
}
doAccessories(callback) {
log("homebridge", "platform - accessories() called by Homebridge - System is ready");
this.addAccessories()
.then( list => {
log("homebridge", "platform - addAccessories returned: " + list.length + " accessories.")
callback(list);
})
.catch( reason => {
log("homebridge", "platform - addAccessories errored: " + reason);
callback([]);
});
}
async addAccessories() {
if (!this.system) {
log("homebridge", "platform - No System -> No accessories");
this.accessoryList = [];
return;
}
// clear our list
this.accessoryList = [];
this.system.allActiveUnits().forEach(unit => {
log("homebridge", "adding accessory: " + unit.getDescription());
switch (unit.getType()) {
case UnitExtendedType.kDimmer:
this.accessoryList.push( new Dimmer(this.homebridgeAPI, unit) );
break;
case UnitExtendedType.kSwitch:
this.accessoryList.push( new Switch(this.homebridgeAPI, unit) );
break;
case UnitExtendedType.kLightbulb:
this.accessoryList.push( new Bulb(this.homebridgeAPI, unit) );
break;
case UnitExtendedType.kSwitchingMotor:
this.accessoryList.push( new WindowCovering(this.homebridgeAPI, unit) );
break;
case UnitExtendedType.kGarageDoor:
this.accessoryList.push( new GarageDoor(this.homebridgeAPI, unit) );
break;
case UnitExtendedType.kDoor:
this.accessoryList.push( new Door(this.homebridgeAPI, unit) );
break;
case UnitExtendedType.kLock:
case UnitExtendedType.kUnlocker:
this.accessoryList.push( new Lock(this.homebridgeAPI, unit) );
break;
case UnitExtendedType.kMood:
case UnitExtendedType.kCondition:
case UnitExtendedType.kInput:
this.accessoryList.push( new Mood(this.homebridgeAPI, unit) );
break;
case UnitExtendedType.kTemperature:
this.accessoryList.push( new Temperature(this.homebridgeAPI, unit) );
break;
default:
log("homebridge", "platform - addAccessories: accessory type not yet supported: " + unit.typeName() +
" ("+ unit.getName() + ")")
break;
}
});
return this.accessoryList;
}
doUpgradeV62() {
function readFile(type: string): object | boolean {
const fn = kConfigFiles + "/config." + type + ".json";
try {
const configBuf = readFileSync(fn);
const configStr = configBuf.toString();
return (Sanitizers[type])(JSON.parse(configStr));
} catch(err) {
log("system", "Couldn't read my config file (" + fn + ") -- setting to 'false'");
return false;
}
}
this.config.system = readFile("system");
this.config.smartapp = readFile("smartapp");
this.config.scenes = readFile("scenes");
this.config.settings = readFile("settings");
this.config.smappee = readFile("smappee");
this.config.p1 = readFile("p1");
this.config.shelly = readFile("shelly");
setConfig(this.config);
log("homebridge", "Upgraded the config file -> NOW: log in and save something...");
}
}