homebridge-nibe
Version:
Homebridge plugin for Nibe services
163 lines (141 loc) • 7.45 kB
text/typescript
import {API, APIEvent, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig} from 'homebridge';
import {MyUplinkApiFetcher} from './myuplink/MyUplinkApiFetcher';
import * as dataDomain from './DataDomain';
import {Data, DataFetcher} from './DataDomain';
import {Locale} from './util/Locale';
import {
AccessoryContext,
AccessoryDefinition,
AccessoryInstance,
ServiceResolver,
ServiceType,
} from './AccessoryDomain';
import {TemperatureSensorAccessory} from './nibeaccessory/TemperatureSensorAccessory';
import {HotWaterAccessory} from './nibeaccessory/HotWaterAccessory';
import {NewFirmwareAccessory} from './nibeaccessory/NewFirmwareAccessory';
export const PLATFORM_NAME = 'Nibe';
export const PLUGIN_NAME = 'homebridge-nibe';
/**
* 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.
*/
export class NibePlatform implements DynamicPlatformPlugin {
private readonly dataFetcher: DataFetcher;
private readonly accessories: AccessoryInstance[] = [];
private readonly accessoryDefinitions: AccessoryDefinition[];
private readonly locale: Locale;
private readonly serviceResolver: ServiceResolver;
constructor(private readonly log: Logger, private readonly config: PlatformConfig, private readonly api: API) {
this.locale = new Locale(config.language, log);
this.serviceResolver = {
resolveCharacteristic(type: any) {
return api.hap.Characteristic[type];
},
resolveService(type: ServiceType) {
return api.hap.Service[type];
},
} as ServiceResolver;
this.dataFetcher = new MyUplinkApiFetcher({
clientId: config.identifier,
clientSecret: config.secret,
interval: config.pollingPeriod || 60,
language: config.language,
showApiResponse: config.showApiResponse || false,
}, log);
this.accessoryDefinitions = [
new TemperatureSensorAccessory('40067', 'average-outdoor-temperature-40067', 1, this.locale, this.serviceResolver, this.log),
new TemperatureSensorAccessory('40004', 'outdoor-temperature-40004', 1, this.locale, this.serviceResolver, this.log),
new TemperatureSensorAccessory('44362', 'outdoor-temperature-44362', 1, this.locale, this.serviceResolver, this.log),
new TemperatureSensorAccessory('40025', 'ventilation-exhaust-air-40025', 1, this.locale, this.serviceResolver, this.log),
new TemperatureSensorAccessory('40026', 'ventilation-extract-air-40026', 1, this.locale, this.serviceResolver, this.log),
new TemperatureSensorAccessory('40075', 'ventilation-supply-air-40075', 1, this.locale, this.serviceResolver, this.log),
new TemperatureSensorAccessory('40183', 'ventilation-outdoor-40183', 1, this.locale, this.serviceResolver, this.log),
new TemperatureSensorAccessory('40013', 'hot-water-top-40013', 1, this.locale, this.serviceResolver, this.log),
new HotWaterAccessory('hot-water', 3, this.locale, this.serviceResolver, this.log,
async (deviceId: string, paramId: string, value: any) => {
return await this.dataFetcher.setValue(deviceId, paramId, value);
},
),
new NewFirmwareAccessory('new-firmware', 1, this.locale, this.serviceResolver, this.log),
];
this.log.debug('Finished initializing platform');
// 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(APIEvent.DID_FINISH_LAUNCHING, () => {
log.debug('Executed didFinishLaunching callback');
this.dataFetcher.start();
this.dataFetcher
.on<dataDomain.Data>('data', (data) => {
this.handleData(data);
}).on('error', (data) => {
this.log.error('Error:', data);
});
});
this.api.on(APIEvent.SHUTDOWN, () => {
this.dataFetcher.stop();
});
}
public handleData(data: Data): void {
const touchedAccessoriesIds = Array<string>();
this.accessoryDefinitions.forEach(accessoryDefinition => {
const isApplicable = accessoryDefinition.isApplicable(data);
const accessoryId = accessoryDefinition.buildIdentifier(data);
const isDisabled = this.isDisabled(accessoryId);
if (isApplicable && !isDisabled) {
const platformAccessory = this.accessories.find(a => a.context.accessoryId === accessoryId);
if (platformAccessory) {
this.updateAccessory(accessoryDefinition, platformAccessory, data);
} else {
this.createAccessory(accessoryId, accessoryDefinition, data);
}
touchedAccessoriesIds.push(accessoryId);
}
});
this.removeNotExistingAccessories(data.system.systemId, data.device.id, touchedAccessoriesIds);
}
private createAccessory(accessoryId: string, accessoryDefinition: AccessoryDefinition, data: Data) {
this.log.info('Adding new accessory: [%s]', accessoryId);
const platformAccessory = new this.api.platformAccessory<AccessoryContext>(
accessoryDefinition.buildName(data),
this.api.hap.uuid.generate(PLUGIN_NAME + '-' + accessoryId),
) as AccessoryInstance;
accessoryDefinition.create(platformAccessory, data);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [platformAccessory as PlatformAccessory<AccessoryContext>]);
this.accessories.push(platformAccessory);
}
private updateAccessory(accessoryDefinition: AccessoryDefinition, platformAccessory: AccessoryInstance, data: Data) {
if (!accessoryDefinition.isCurrentVersion(platformAccessory)) {
this.log.info('Old version of accessory, recreating: [%s]',
platformAccessory.context.accessoryId);
accessoryDefinition.create(platformAccessory, data);
}
this.log.debug('Updating accessory: [%s]', platformAccessory.context.accessoryId);
accessoryDefinition.update(platformAccessory, data);
}
private isDisabled(accessoryId: string) {
if (this.config.disabledAccessories &&
this.config.disabledAccessories.some(da => (da.indexOf(';') > 0 ? da.substring(0, da.indexOf(';')) : da) === accessoryId)) {
this.log.debug('Disabled accessory: [%s]', accessoryId);
return true;
}
return false;
}
private removeNotExistingAccessories(systemId: string, deviceId: string, existingAccessoriesIds: string[]) {
this.accessories
.filter(accessory => accessory.context.systemId === systemId)
.filter(accessory => accessory.context.deviceId === deviceId)
.filter(accessory => !existingAccessoriesIds.includes(accessory.context.accessoryId))
.forEach(accessory => this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory as PlatformAccessory<AccessoryContext>]));
}
/**
* 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: PlatformAccessory<AccessoryContext>) {
this.log.info( `Loading accessory from cache: [${accessory.displayName}], UUID: [${accessory.UUID}]`);
this.accessories.push(accessory as AccessoryInstance);
}
}