Skip to content

Commit

Permalink
new api - wip
Browse files Browse the repository at this point in the history
  • Loading branch information
hpruszyn committed Sep 24, 2024
1 parent aeae13b commit 7109419
Show file tree
Hide file tree
Showing 8 changed files with 3,700 additions and 2,810 deletions.
6,321 changes: 3,582 additions & 2,739 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"gulp-nodemon": "^2.5.0",
"gulp-typescript": "^6.0.0-alpha.1",
"homebridge": "^1.8.2",
"homebridge-config-ui-x": "^4.56.2",
"homebridge-config-ui-x": "^4.58.0",
"jest": "^29.7.0",
"nodemon": "^3.1.3",
"source-map": "^0.7.4",
Expand Down
31 changes: 24 additions & 7 deletions src/platform/AccessoryDomain.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Data} from './DataDomain';
import {Characteristics, Services} from './NibePlatform';
import {Logger} from './PlatformDomain';
import {Locale} from './util/Locale';

Expand All @@ -13,6 +12,22 @@ export interface ServiceInstance {
updateCharacteristic(type: any, value: any): void;
}

export interface ServiceResolver {
resolveService(type: ServiceType);
resolveCharacteristic(type: CharacteristicType);
}

export type ServiceType =
'AccessoryInformation' |
'TemperatureSensor'

export type CharacteristicType =
'Manufacturer' |
'Model' |
'SerialNumber' |
'CurrentTemperature' |
'Name'

export interface AccessoryContext {
accessoryId: string
lastUpdate: number //epoch
Expand All @@ -28,6 +43,7 @@ export abstract class AccessoryDefinition {
protected readonly name: string,
protected readonly version: number,
protected readonly locale: Locale,
protected readonly serviceResolver: ServiceResolver,
protected readonly log: Logger,
) {}

Expand Down Expand Up @@ -57,16 +73,17 @@ export abstract class AccessoryDefinition {
platformAccessory.context.deviceId = data.device.id;
platformAccessory.context.deviceName = data.device.name;

const accessoryInformationService = this.getOrCreateService(Services.AccessoryInformation, platformAccessory);
accessoryInformationService.updateCharacteristic(Characteristics.Manufacturer, 'Nibe');
accessoryInformationService.updateCharacteristic(Characteristics.Model, `${data.device.name} (${data.system.name})`);
accessoryInformationService.updateCharacteristic(Characteristics.SerialNumber, data.device.serialNumber);
const accessoryInformationService = this.getOrCreateService('AccessoryInformation', platformAccessory);
accessoryInformationService.updateCharacteristic(this.serviceResolver.resolveCharacteristic('Manufacturer'), 'Nibe');
accessoryInformationService.updateCharacteristic(this.serviceResolver.resolveCharacteristic('Model'), `${data.device.name} (${data.system.name})`);
accessoryInformationService.updateCharacteristic(this.serviceResolver.resolveCharacteristic('SerialNumber'), data.device.serialNumber);

this.update(platformAccessory, data);
}

protected getOrCreateService(type: any, platformAccessory: AccessoryInstance) {
return platformAccessory.getService(type) || platformAccessory.addService(type);
protected getOrCreateService(type: ServiceType, platformAccessory: AccessoryInstance) {
const resolvedType = this.serviceResolver.resolveService(type);
return platformAccessory.getService(resolvedType) || platformAccessory.addService(resolvedType);
}

protected findParameter(parameterId: string, data: Data) {
Expand Down
51 changes: 26 additions & 25 deletions src/platform/NibePlatform.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import {
API,
APIEvent,
Characteristic,
DynamicPlatformPlugin,
Logger,
PlatformAccessory,
PlatformConfig,
Service,
} from 'homebridge';
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} from './AccessoryDomain';
import {
AccessoryContext,
AccessoryDefinition,
AccessoryInstance,
ServiceResolver,
ServiceType,
} from './AccessoryDomain';
import {TemperatureSensorAccessory} from './nibeaccessory/TemperatureSensorAccessory';

export let Services: typeof Service;
export let Characteristics: typeof Characteristic;

export const PLATFORM_NAME = 'Nibe';
export const PLUGIN_NAME = 'homebridge-nibe';

Expand All @@ -32,13 +26,20 @@ export class NibePlatform implements DynamicPlatformPlugin {
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) {
Services = this.api.hap.Service;
Characteristics = this.api.hap.Characteristic;

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,
Expand All @@ -48,14 +49,14 @@ export class NibePlatform implements DynamicPlatformPlugin {
}, log);

this.accessoryDefinitions = [
new TemperatureSensorAccessory('40067', 'average-outdoor-temperature-40067', 1, this.locale, this.log),
new TemperatureSensorAccessory('40004', 'outdoor-temperature-40004', 1, this.locale, this.log),
new TemperatureSensorAccessory('44362', 'outdoor-temperature-44362', 1, this.locale, this.log),
new TemperatureSensorAccessory('40025', 'ventilation-exhaust-air-40025', 1, this.locale, this.log),
new TemperatureSensorAccessory('40026', 'ventilation-extract-air-40026', 1, this.locale, this.log),
new TemperatureSensorAccessory('40075', 'ventilation-supply-air-40075', 1, this.locale, this.log),
new TemperatureSensorAccessory('40183', 'ventilation-outdoor-40183', 1, this.locale, this.log),
new TemperatureSensorAccessory('40013', 'hot-water-top-40013', 1, this.locale, this.log),
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),
];


Expand Down
16 changes: 8 additions & 8 deletions src/platform/nibeaccessory/TemperatureSensorAccessory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {Data} from '../DataDomain';
import {Characteristics, Services} from '../NibePlatform';
import {AccessoryDefinition, AccessoryInstance} from '../AccessoryDomain';
import {AccessoryDefinition, AccessoryInstance, ServiceResolver} from '../AccessoryDomain';
import {Logger} from '../PlatformDomain';
import {Locale} from '../util/Locale';

Expand All @@ -11,9 +10,10 @@ export class TemperatureSensorAccessory extends AccessoryDefinition {
protected readonly name: string,
protected readonly version: number,
protected readonly locale: Locale,
protected readonly serviceResolver: ServiceResolver,
protected readonly log: Logger,
) {
super(name, version, locale, log);
super(name, version, locale, serviceResolver, log);
}

isApplicable(data: Data) {
Expand All @@ -27,9 +27,9 @@ export class TemperatureSensorAccessory extends AccessoryDefinition {

update(platformAccessory: AccessoryInstance, data: Data) {
const parameter = this.findParameter(this.parameterId, data);
const temperatureSensorService = this.getOrCreateService(Services.TemperatureSensor, platformAccessory);
const temperatureSensorService = this.getOrCreateService('TemperatureSensor', platformAccessory);
if (temperatureSensorService && parameter) {
temperatureSensorService.updateCharacteristic(Characteristics.CurrentTemperature, parameter.value);
temperatureSensorService.updateCharacteristic(this.serviceResolver.resolveCharacteristic('CurrentTemperature'), parameter.value);
super.update(platformAccessory, data);
this.log.debug(`Accessory ${platformAccessory.context.accessoryId} updated to ${parameter.value}`);
}
Expand All @@ -38,9 +38,9 @@ export class TemperatureSensorAccessory extends AccessoryDefinition {
create(platformAccessory: AccessoryInstance, data: Data): void {
super.create(platformAccessory, data);

const temperatureSensorService = this.getOrCreateService(Services.TemperatureSensor, platformAccessory);
temperatureSensorService.updateCharacteristic(Characteristics.CurrentTemperature, 0);
temperatureSensorService.updateCharacteristic(Characteristics.Name, this.getText(this.name));
const temperatureSensorService = this.getOrCreateService('TemperatureSensor', platformAccessory);
temperatureSensorService.updateCharacteristic(this.serviceResolver.resolveCharacteristic('CurrentTemperature'), 0);
temperatureSensorService.updateCharacteristic(this.serviceResolver.resolveCharacteristic('Name'), this.getText(this.name));

this.update(platformAccessory, data);
}
Expand Down
38 changes: 18 additions & 20 deletions tests/AllAccessories.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {loadData, mockAccessory, testDevice, testLogger, testSystem} from './test-utils';
import {loadData, mockAccessory, serviceResolver, testDevice, testLogger, testSystem} from './test-utils';
import {Locale} from '../src/platform/util/Locale';
import {MyUplinkApiFetcher} from '../src/platform/myuplink/MyUplinkApiFetcher';
import {AccessoryDefinition} from '../src/platform/AccessoryDomain';
import {AccessoryDefinition, AccessoryInstance} from '../src/platform/AccessoryDomain';
import {Data} from '../src/platform/DataDomain';
import {Logger} from '../src/platform/PlatformDomain';

Expand All @@ -12,7 +12,7 @@ describe('Test common accessory functionalities', () => {
protected readonly locale: Locale,
protected readonly log: Logger,
) {
super('test', 1, locale, log);
super('test', 1, locale, serviceResolver, log);
}

isApplicable(data: Data): boolean {
Expand All @@ -27,24 +27,22 @@ describe('Test common accessory functionalities', () => {

const data = MyUplinkApiFetcher.mapData(testSystem, testDevice, loadData('F1145-10-PC'));

test('TemperatureSensorAccessory: create should set parameters', () => {
test('TestAccessory: create should set context and parameters', () => {
const platformAccessory = mockAccessory();
accessoryDefinition.create(platformAccessory, data);

expect(platformAccessory.context.accessoryId).toBe(0);
expect(platformAccessory.context.version).toBe(0);
expect(platformAccessory.context.systemId).toBe(0);
expect(platformAccessory.context.systemName).toBe(0);
expect(platformAccessory.context.deviceId).toBe(0);
expect(platformAccessory.context.deviceName).toBe(0);
expect(platformAccessory.context.lastUpdate).toBe(0);


// const accessoryInformationService = this.getOrCreateService(Services.AccessoryInformation, platformAccessory);
// accessoryInformationService.updateCharacteristic(Characteristics.Manufacturer, 'Nibe');
// accessoryInformationService.updateCharacteristic(Characteristics.Model, `${data.device.name} (${data.system.name})`);
// accessoryInformationService.updateCharacteristic(Characteristics.SerialNumber, data.device.serialNumber);

const startEpoch = Date.now();
accessoryDefinition.create(platformAccessory as AccessoryInstance, data);

expect(platformAccessory.context.accessoryId).toBe('emmy-r-test::test');
expect(platformAccessory.context.version).toBe(1);
expect(platformAccessory.context.systemId).toBe('42f23d18');
expect(platformAccessory.context.systemName).toBe('Dom');
expect(platformAccessory.context.deviceId).toBe('emmy-r-test');
expect(platformAccessory.context.deviceName).toBe('F1145-10 PC');
expect(platformAccessory.context.lastUpdate).toBeGreaterThanOrEqual(startEpoch);

expect(platformAccessory.getValue('AccessoryInformation', 'Manufacturer')).toBe('Nibe');
expect(platformAccessory.getValue('AccessoryInformation', 'Model')).toBe('F1145-10 PC (Dom)');
expect(platformAccessory.getValue('AccessoryInformation', 'SerialNumber')).toBe('666');
});
});

12 changes: 6 additions & 6 deletions tests/TemperatureSensorAccessory.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {TemperatureSensorAccessory} from '../src/platform/nibeaccessory/TemperatureSensorAccessory';
import {loadData, mockAccessory, testDevice, testLogger, testSystem} from './test-utils';
import {loadData, mockAccessory, serviceResolver, testDevice, testLogger, testSystem} from './test-utils';
import {Locale} from '../src/platform/util/Locale';
import {MyUplinkApiFetcher} from '../src/platform/myuplink/MyUplinkApiFetcher';
import {AccessoryInstance} from '../src/platform/AccessoryDomain';

describe('Test TemperatureSensorAccessory', () => {

Expand All @@ -10,6 +11,7 @@ describe('Test TemperatureSensorAccessory', () => {
'average-outdoor-temperature-40067',
1,
new Locale('en', testLogger),
serviceResolver,
testLogger,
);

Expand All @@ -21,11 +23,9 @@ describe('Test TemperatureSensorAccessory', () => {

test('TemperatureSensorAccessory: create should set parameters', () => {
const platformAccessory = mockAccessory();
accessoryDefinition.create(platformAccessory, data);
accessoryDefinition.create(platformAccessory as AccessoryInstance, data);

// expect(platformAccessory).toBe(0);

// temperatureSensorService.updateCharacteristic(Characteristics.CurrentTemperature, 0);
// temperatureSensorService.updateCharacteristic(Characteristics.Name, this.getText(this.name));
expect(platformAccessory.getValue('TemperatureSensor', 'CurrentTemperature')).toBe(22.9);
expect(platformAccessory.getValue('TemperatureSensor', 'Name')).toBe('Nibe average outdoor temperature');
});
});
39 changes: 35 additions & 4 deletions tests/test-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import fs from 'fs';
import path from 'path';
import {AccessoryInstance} from '../src/platform/AccessoryDomain';
import {
AccessoryContext,
CharacteristicType,
ServiceInstance,
ServiceResolver,
ServiceType,
} from '../src/platform/AccessoryDomain';

export const testLogger = { // logger
debug: (message: string, ...parameters: any[]) => {
Expand All @@ -17,14 +23,39 @@ export const testLogger = { // logger
},
};

export const testDevice = {'id':'emmy-r-107050-20240612-06513518228002-54-10-ec-d0-9c-56','connectionState':'Connected','currentFwVersion':'9699R4','product':{'serialNumber':'06513518228002','name':'F1145-10 PC'}};
export const testDevice = {'id':'emmy-r-test','connectionState':'Connected','currentFwVersion':'9699R4','product':{'serialNumber':'666','name':'F1145-10 PC'}};

export const testSystem = {'systemId':'42f23d18-4b2d-48b3-94f7-20208b8fe43e','name':'Dom','securityLevel':'admin','hasAlarm':false,'country':'Poland','devices':[{'id':'emmy-r-107050-20240612-06513518228002-54-10-ec-d0-9c-56','connectionState':'Connected','currentFwVersion':'9699R4','product':{'serialNumber':'06513518228002','name':'F1145-10 PC'}}]};
export const testSystem = {'systemId':'42f23d18','name':'Dom','country':'Poland','devices':[testDevice]};

export const mockAccessory = function() {
return {} as AccessoryInstance;
const values = new Map();
return {
context: {} as AccessoryContext,
addService(type: any): ServiceInstance {
return this.getService(type);
},
getService(stype: any): ServiceInstance {
return {
updateCharacteristic(ctype: any, value: any) {
values.set(stype + ':' + ctype, value);
},
} as ServiceInstance;
},
getValue(stype: any, ctype: any) {
return values.get(stype + ':' + ctype);
},
};
};

export const serviceResolver = {
resolveService(type: ServiceType) {
return type;
},
resolveCharacteristic(type: CharacteristicType) {
return type;
},
} as ServiceResolver;

export const loadData = function (product) {
return JSON.parse(fs.readFileSync(path.resolve(__dirname, `./data/nibe-myuplink/${product.replace(/ /g, '-')}.json`), 'utf8'));
};

0 comments on commit 7109419

Please sign in to comment.