@homebridge-plugins/homebridge-tado
Version:
Homebridge plugin for controlling tado° devices.
323 lines (267 loc) • 11.1 kB
JavaScript
import DeviceHandler from './helper/handler.js';
import Logger from './helper/logger.js';
import TadoConfig from './tado/tado-config.js';
import fakeGatoHistory from 'fakegato-history';
import { readFileSync } from 'fs';
import { fileURLToPath } from "url";
import { join, dirname } from "path";
// Accessories
import ContactAccessory from './accessories/contact.js';
import FaucetAccessory from './accessories/faucet.js';
import HeaterCoolerAccessory from './accessories/heatercooler.js';
import HumidityAccessory from './accessories/humidity.js';
import MotionAccessory from './accessories/motion.js';
import OccupancyAccessory from './accessories/occupancy.js';
import SecurityAccessory from './accessories/security.js';
import SolarLightbulbAccessory from './accessories/lightbulb.js';
import SolarLightsensorAccessory from './accessories/lightsensor.js';
import SwitchAccessory from './accessories/switch.js';
import TemperatureAccessory from './accessories/temperature.js';
import ThermostatAccessory from './accessories/thermostat.js';
// Custom Types
import CustomTypes from './types/custom.js';
import EveTypes from './types/eve.js';
const PLUGIN_NAME = '@homebridge-plugins/homebridge-tado';
const PLATFORM_NAME = 'TadoPlatform';
let Accessory, UUIDGen, FakeGatoHistoryService;
export default function (homebridge) {
Accessory = homebridge.platformAccessory;
UUIDGen = homebridge.hap.uuid;
return TadoPlatform;
}
class TadoPlatform {
constructor(log, config, api) {
if (!api || !config) return;
Logger.init(log, config.debug);
CustomTypes.registerWith(api.hap);
EveTypes.registerWith(api.hap);
FakeGatoHistoryService = config.disableHistoryService ? undefined : fakeGatoHistory(api);
this.api = api;
this.accessories = [];
this.config = config;
this.user = [];
this.packageJson = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8"));
const storagePath = this.api.user.storagePath();
this.setupPlugin(storagePath);
if (!this.user.length) this.setupConfig(storagePath);
this.api.on('didFinishLaunching', this.didFinishLaunching.bind(this));
}
async setupPlugin(storagePath) {
try {
if (this.config.user && this.config.user.length) {
for (const auth of this.config.user) {
let error = false;
if (!auth.username) {
Logger.warn('There is no username configured for the user. This user will be skipped.');
error = true;
} else if (auth.reconfigure === false) {
error = true;
}
if (!error) {
this.user.push({
username: auth.username,
tadoApiUrl: auth.tadoApiUrl,
skipAuth: auth.skipAuth
});
}
}
}
if (this.user.length) {
for (const auth of this.user) {
if (auth.reconfigure || auth.reconfigure === undefined) {
if (this.config.homes && this.config.homes.length) {
let foundHome = this.config.homes.filter((home) => home && home.username === auth.username);
if (foundHome.length) {
//refresh
if (foundHome[0].name && foundHome[0].username) {
Logger.info('Refreshing home...', foundHome[0].name);
this.config = await TadoConfig.refresh(foundHome[0].name, this.config, {
username: foundHome[0].username,
tadoApiUrl: foundHome[0].tadoApiUrl,
skipAuth: foundHome[0].skipAuth
}, storagePath);
}
} else {
Logger.info('Generating new home...', auth.username);
this.config = await TadoConfig.add(this.config, [auth], storagePath);
}
} else {
Logger.info('Generating new home...', auth.username);
this.config = await TadoConfig.add(this.config, [auth], storagePath);
}
}
}
//store config
Logger.info('Storing config...');
this.config.user = this.user
.map((user) => {
return {
reconfigure: false,
username: user.username
};
})
.filter((user) => user);
await TadoConfig.store(this.config, storagePath);
Logger.info('Done!');
//setup config
this.user = [];
this.setupConfig(storagePath);
//configure accessories
this.accessories.forEach((accessory) => {
this.configureAccessory(accessory, true);
});
//finish
this.didFinishLaunching();
}
} catch (err) {
Logger.error('An error occured during setting up plugin!');
Logger.error(err);
}
}
setupConfig(storagePath) {
try {
const { config, devices, deviceHandler, telegram } = TadoConfig.setup(this.config, UUIDGen, storagePath);
this.config = config;
this.devices = devices;
this.deviceHandler = deviceHandler;
this.telegram = telegram;
} catch (err) {
Logger.error('An error occured during setting up plugin!');
Logger.error(err);
}
}
didFinishLaunching() {
if (this.user.length) return;
for (const entry of this.devices.entries()) {
let uuid = entry[0];
let device = entry[1];
const cachedAccessory = this.accessories.find((curAcc) => curAcc.UUID === uuid);
if (!cachedAccessory) {
const accessory = new Accessory(device.name, uuid);
Logger.info('Configuring new accessory...', accessory.displayName);
this.setupAccessory(accessory, device);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
this.accessories.push(accessory);
}
}
this.accessories.forEach((accessory) => {
const device = this.devices.get(accessory.UUID);
try {
if (!device) this.removeAccessory(accessory);
} catch (err) {
Logger.info('It looks like the device has already been removed. Skip removing.', device.name);
Logger.debug(err);
}
});
for (const entry of this.deviceHandler.entries()) {
const name = entry[0];
const config = entry[1];
const tado = config.tado;
delete config.tado;
let accessories = this.accessories.filter((acc) => acc && acc.context.config.homeName === name);
const deviceHandler = DeviceHandler(this.api, accessories, config, tado, this.telegram);
deviceHandler.initTasks();
}
}
setupAccessory(accessory, device) {
accessory.on('identify', () => {
Logger.info('Identify requested.', accessory.displayName);
});
const manufacturer = 'tado';
const model = device.model ? device.model : device.subtype;
const serialNumber = device.serialNumber ? device.serialNumber : '123456789';
const AccessoryInformation = accessory.getService(this.api.hap.Service.AccessoryInformation);
AccessoryInformation.setCharacteristic(this.api.hap.Characteristic.Manufacturer, manufacturer)
.setCharacteristic(this.api.hap.Characteristic.Model, model)
.setCharacteristic(this.api.hap.Characteristic.SerialNumber, serialNumber)
.setCharacteristic(this.api.hap.Characteristic.FirmwareRevision, this.packageJson.version);
const tado = device.tado;
delete device.tado;
delete device.geolocation;
delete device.zones;
delete device.presence;
delete device.anyone;
delete device.weather;
delete device.extras;
delete device.childLock;
accessory.context.config = device;
const configHandler = this.deviceHandler.get(accessory.context.config.homeName);
const deviceHandler = DeviceHandler(this.api, false, configHandler, tado, this.telegram);
switch (device.subtype) {
case 'zone-thermostat':
new ThermostatAccessory(this.api, accessory, this.accessories, tado, deviceHandler, this.config.preferSiriTemperature, FakeGatoHistoryService);
break;
case 'zone-heatercooler':
case 'zone-heatercooler-boiler':
case 'zone-heatercooler-ac':
new HeaterCoolerAccessory(this.api, accessory, this.accessories, tado, deviceHandler, this.config.preferSiriTemperature, FakeGatoHistoryService);
break;
case 'zone-switch':
case 'zone-window-switch':
case 'extra-childswitch':
case 'extra-cntrlswitch':
case 'extra-boost':
case 'extra-shedule':
case 'extra-turnoff':
case 'extra-plockswitch':
new SwitchAccessory(this.api, accessory, this.accessories, tado, deviceHandler);
break;
case 'zone-faucet':
new FaucetAccessory(this.api, accessory, this.accessories, tado, deviceHandler);
break;
case 'zone-window-contact':
new ContactAccessory(this.api, accessory, this.accessories, tado, deviceHandler, FakeGatoHistoryService);
break;
case 'zone-temperature':
new TemperatureAccessory(this.api, accessory, this.accessories, tado, deviceHandler, FakeGatoHistoryService);
break;
case 'zone-humidity':
new HumidityAccessory(this.api, accessory, this.accessories, tado, deviceHandler, FakeGatoHistoryService);
break;
case 'presence-motion':
new MotionAccessory(this.api, accessory, this.accessories, tado, deviceHandler, FakeGatoHistoryService);
break;
case 'presence-occupancy':
new OccupancyAccessory(this.api, accessory, this.accessories, tado, deviceHandler);
break;
case 'weather-temperature':
new TemperatureAccessory(this.api, accessory, this.accessories, tado, deviceHandler, FakeGatoHistoryService);
break;
case 'weather-lightbulb':
new SolarLightbulbAccessory(this.api, accessory, this.accessories, tado);
break;
case 'weather-lightsensor':
new SolarLightsensorAccessory(this.api, accessory, this.accessories, tado);
break;
case 'extra-plock':
new SecurityAccessory(this.api, accessory, this.accessories, tado, deviceHandler);
break;
default:
Logger.warn('Unknown accessory type: ' + device.subtype, accessory.displayName);
break;
}
return;
}
configureAccessory(accessory, refresh) {
if (!this.user.length) {
const device = this.devices.get(accessory.UUID);
if (device) {
Logger.info('Configuring accessory...', accessory.displayName);
this.setupAccessory(accessory, device);
}
}
if (!refresh) this.accessories.push(accessory);
}
removeAccessory(accessory) {
Logger.info('Removing accessory...', accessory.displayName);
let accessories = this.accessories.map((cachedAccessory) => {
if (cachedAccessory.displayName !== accessory.displayName) {
return cachedAccessory;
}
});
this.accessories = accessories.filter(function (el) {
return el != null;
});
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
}
}