Add native local energy device integrations
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { CpuspeedClient, CpuspeedConfigFlow, CpuspeedIntegration, CpuspeedMapper, HomeAssistantCpuspeedIntegration, cpuspeedProfile, createCpuspeedDiscoveryDescriptor, type ICpuspeedSnapshot } from '../../ts/integrations/cpuspeed/index.js';
|
||||
|
||||
const rawData = {
|
||||
actual_ghz: 3.18,
|
||||
advertised_ghz: 3.4,
|
||||
arch: 'x86_64',
|
||||
brand: 'Example CPU',
|
||||
};
|
||||
|
||||
tap.test('matches manual CPU Speed candidates and creates config flow output', async () => {
|
||||
const descriptor = createCpuspeedDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'cpuspeed-manual-match');
|
||||
const result = await matcher!.matches({ name: 'CPU Speed', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('cpuspeed');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new CpuspeedConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
});
|
||||
|
||||
tap.test('maps CPU Speed raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new CpuspeedClient({ name: 'CPU Speed Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = CpuspeedMapper.toDevices(snapshot);
|
||||
const entities = CpuspeedMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('cpuspeed');
|
||||
expect(devices[0].name).toEqual('CPU Speed Test');
|
||||
expect(entities.length > 0).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes CPU Speed read-only runtime without control services', async () => {
|
||||
expect(new HomeAssistantCpuspeedIntegration().domain).toEqual('cpuspeed');
|
||||
expect(cpuspeedProfile.status).toEqual('read-only-runtime');
|
||||
expect(cpuspeedProfile.metadata.configFlow).toBeTrue();
|
||||
expect(cpuspeedProfile.metadata.requirements).toEqual(['py-cpuinfo==9.0.0']);
|
||||
expect(Object.prototype.hasOwnProperty.call(cpuspeedProfile.metadata, 'qualityScale')).toBeTrue();
|
||||
expect(cpuspeedProfile.metadata.qualityScale).toBeUndefined();
|
||||
|
||||
const runtime = await new CpuspeedIntegration().setup({ name: 'CPU Speed Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'cpuspeed', service: 'status', target: {} });
|
||||
const snapshot = status.data as ICpuspeedSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('CPU Speed Runtime');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'cpuspeed', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(Boolean(controlCommand.error?.includes('requires an injected'))).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,71 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DanfossAirClient, DanfossAirConfigFlow, DanfossAirIntegration, DanfossAirMapper, HomeAssistantDanfossAirIntegration, danfossAirProfile, createDanfossAirDiscoveryDescriptor, type IDanfossAirSnapshot } from '../../ts/integrations/danfoss_air/index.js';
|
||||
|
||||
const rawData = {
|
||||
exhaustTemperature: 21.4,
|
||||
outdoorTemperature: 7.8,
|
||||
supplyTemperature: 20.1,
|
||||
extractTemperature: 22,
|
||||
humidity: 44.5,
|
||||
filterPercent: 82,
|
||||
bypass: false,
|
||||
fan_step: 40,
|
||||
supply_fan_speed: 1600,
|
||||
exhaust_fan_speed: 1580,
|
||||
away_mode: false,
|
||||
boost: true,
|
||||
automatic_bypass: false,
|
||||
battery_percent: 91,
|
||||
};
|
||||
|
||||
tap.test('matches manual Danfoss Air candidates and creates config flow output', async () => {
|
||||
const descriptor = createDanfossAirDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'danfoss_air-manual-match');
|
||||
const result = await matcher!.matches({ host: 'danfoss-air.local', name: 'Danfoss Air CCM', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('danfoss_air');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DanfossAirConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('danfoss-air.local');
|
||||
});
|
||||
|
||||
tap.test('maps Danfoss Air raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DanfossAirClient({ name: 'Danfoss Air Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DanfossAirMapper.toDevices(snapshot);
|
||||
const entities = DanfossAirMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('danfoss_air');
|
||||
expect(devices[0].manufacturer).toEqual('Danfoss');
|
||||
expect(entities.length > 0).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes Danfoss Air delegated control runtime without fake live control', async () => {
|
||||
expect(new HomeAssistantDanfossAirIntegration().domain).toEqual('danfoss_air');
|
||||
expect(danfossAirProfile.status).toEqual('control-runtime');
|
||||
expect(danfossAirProfile.metadata.configFlow).toBeFalse();
|
||||
expect(danfossAirProfile.metadata.qualityScale).toEqual('legacy');
|
||||
expect(danfossAirProfile.metadata.requirements).toEqual(['pydanfossair==0.1.0']);
|
||||
|
||||
const runtime = await new DanfossAirIntegration().setup({ name: 'Danfoss Air Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'switch', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDanfossAirSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Danfoss Air Runtime');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'switch', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(Boolean(controlCommand.error?.includes('requires an injected'))).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,76 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeakoClient, DeakoConfigFlow, DeakoIntegration, DeakoMapper, HomeAssistantDeakoIntegration, createDeakoDiscoveryDescriptor, deakoProfile, type IDeakoSnapshot } from '../../ts/integrations/deako/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'deako-kitchen',
|
||||
name: 'Kitchen Deako Dimmer',
|
||||
manufacturer: 'Deako',
|
||||
model: 'dimmer',
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
id: 'kitchen_dimmer',
|
||||
name: 'Kitchen Dimmer',
|
||||
platform: 'light',
|
||||
state: true,
|
||||
attributes: {
|
||||
brightness: 64,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual Deako candidates and creates config flow output', async () => {
|
||||
const descriptor = createDeakoDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'deako-manual-match');
|
||||
const result = await matcher!.matches({ host: 'deako.local', name: 'Deako', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('deako');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DeakoConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('deako.local');
|
||||
});
|
||||
|
||||
tap.test('maps Deako raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DeakoClient({ rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DeakoMapper.toDevices(snapshot);
|
||||
const entities = DeakoMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('deako');
|
||||
expect(devices[0].manufacturer).toEqual('Deako');
|
||||
expect(entities[0].platform).toEqual('light');
|
||||
});
|
||||
|
||||
tap.test('exposes Deako delegated control runtime without fake live control', async () => {
|
||||
expect(new HomeAssistantDeakoIntegration().domain).toEqual('deako');
|
||||
expect(deakoProfile.status).toEqual('control-runtime');
|
||||
expect(deakoProfile.metadata.configFlow).toBeTrue();
|
||||
expect(deakoProfile.metadata.requirements).toEqual(['pydeako==0.6.0']);
|
||||
expect(deakoProfile.metadata.dependencies).toEqual(['zeroconf']);
|
||||
expect(Object.prototype.hasOwnProperty.call(deakoProfile.metadata, 'qualityScale')).toBeTrue();
|
||||
expect(deakoProfile.metadata.qualityScale).toBeUndefined();
|
||||
|
||||
const runtime = await new DeakoIntegration().setup({ rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'light', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDeakoSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Kitchen Deako Dimmer');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'light', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(Boolean(controlCommand.error?.includes('requires an injected'))).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,77 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DenonRs232Client, DenonRs232ConfigFlow, DenonRs232Integration, DenonRs232Mapper, HomeAssistantDenonRs232Integration, createDenonRs232DiscoveryDescriptor, denonRs232Profile, type IDenonRs232Snapshot } from '../../ts/integrations/denon_rs232/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
name: 'Denon AVR-3803',
|
||||
manufacturer: 'Denon',
|
||||
model: 'AVR-3803',
|
||||
serialNumber: 'denon-rs232-1',
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
id: 'main_receiver',
|
||||
name: 'Main receiver',
|
||||
platform: 'media_player' as const,
|
||||
state: 'on',
|
||||
attributes: {
|
||||
source: 'cd',
|
||||
volumeLevel: 0.42,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual Denon RS232 candidates and creates config flow output', async () => {
|
||||
const descriptor = createDenonRs232DiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'denon_rs232-manual-match');
|
||||
const result = await matcher!.matches({ name: 'Denon RS232 receiver', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('denon_rs232');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DenonRs232ConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
});
|
||||
|
||||
tap.test('maps Denon RS232 raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DenonRs232Client({ name: 'Denon RS232 Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DenonRs232Mapper.toDevices(snapshot);
|
||||
const entities = DenonRs232Mapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('denon_rs232');
|
||||
expect(devices[0].manufacturer).toEqual('Denon');
|
||||
expect(entities[0].platform).toEqual('media_player');
|
||||
});
|
||||
|
||||
tap.test('exposes Denon RS232 runtime status and explicit unsupported control without executor', async () => {
|
||||
const alias = new HomeAssistantDenonRs232Integration();
|
||||
expect(alias instanceof DenonRs232Integration).toBeTrue();
|
||||
expect(alias.domain).toEqual('denon_rs232');
|
||||
expect(denonRs232Profile.status).toEqual('read-only-runtime');
|
||||
expect(denonRs232Profile.metadata.qualityScale).toEqual('bronze');
|
||||
expect(denonRs232Profile.metadata.requirements).toEqual(['denon-rs232==4.1.0']);
|
||||
|
||||
const runtime = await new DenonRs232Integration().setup({ name: 'Denon RS232 Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'denon_rs232', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDenonRs232Snapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.callService!({ domain: 'denon_rs232', service: 'refresh', target: {} })).success).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Denon AVR-3803');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'media_player', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(controlCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,79 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DevialetClient, DevialetConfigFlow, DevialetIntegration, DevialetMapper, HomeAssistantDevialetIntegration, createDevialetDiscoveryDescriptor, devialetProfile, type IDevialetSnapshot } from '../../ts/integrations/devialet/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
name: 'Living Room Phantom',
|
||||
manufacturer: 'Devialet',
|
||||
model: 'Phantom I',
|
||||
serialNumber: 'devialet-phantom-1',
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
id: 'speaker',
|
||||
name: 'Speaker',
|
||||
platform: 'media_player' as const,
|
||||
state: 'playing',
|
||||
attributes: {
|
||||
source: 'AirPlay',
|
||||
soundMode: 'Flat',
|
||||
volumeLevel: 0.35,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual Devialet candidates and creates config flow output', async () => {
|
||||
const descriptor = createDevialetDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'devialet-manual-match');
|
||||
const result = await matcher!.matches({ host: 'phantom.local', name: 'Devialet Phantom', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('devialet');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DevialetConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('phantom.local');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
});
|
||||
|
||||
tap.test('maps Devialet raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DevialetClient({ name: 'Devialet Runtime Speaker', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DevialetMapper.toDevices(snapshot);
|
||||
const entities = DevialetMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('devialet');
|
||||
expect(devices[0].manufacturer).toEqual('Devialet');
|
||||
expect(entities[0].platform).toEqual('media_player');
|
||||
});
|
||||
|
||||
tap.test('exposes Devialet runtime status and explicit unsupported control without executor', async () => {
|
||||
const alias = new HomeAssistantDevialetIntegration();
|
||||
expect(alias instanceof DevialetIntegration).toBeTrue();
|
||||
expect(alias.domain).toEqual('devialet');
|
||||
expect(devialetProfile.status).toEqual('read-only-runtime');
|
||||
expect(devialetProfile.metadata.requirements).toEqual(['devialet==1.5.7']);
|
||||
expect(devialetProfile.metadata.afterDependencies).toEqual(['zeroconf']);
|
||||
|
||||
const runtime = await new DevialetIntegration().setup({ name: 'Devialet Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'devialet', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDevialetSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.callService!({ domain: 'devialet', service: 'refresh', target: {} })).success).toBeTrue();
|
||||
expect((await runtime.entities())[0].name).toEqual('Speaker');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'media_player', service: 'turn_off', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(controlCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,109 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DevoloHomeControlClient, DevoloHomeControlConfigFlow, DevoloHomeControlIntegration, DevoloHomeControlMapper, HomeAssistantDevoloHomeControlIntegration, createDevoloHomeControlDiscoveryDescriptor, devoloHomeControlProfile, type IDevoloHomeControlSnapshot } from '../../ts/integrations/devolo_home_control/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
name: 'devolo Home Control Gateway',
|
||||
manufacturer: 'devolo',
|
||||
model: 'Central Unit 2600',
|
||||
serialNumber: 'devolo-hc-1',
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
id: 'front_door',
|
||||
name: 'Front door',
|
||||
platform: 'binary_sensor' as const,
|
||||
state: false,
|
||||
deviceClass: 'door',
|
||||
},
|
||||
{
|
||||
id: 'hall_thermostat',
|
||||
name: 'Hall thermostat',
|
||||
platform: 'climate' as const,
|
||||
state: 21.5,
|
||||
unit: 'C',
|
||||
},
|
||||
{
|
||||
id: 'living_room_blind',
|
||||
name: 'Living room blind',
|
||||
platform: 'cover' as const,
|
||||
state: 75,
|
||||
},
|
||||
{
|
||||
id: 'dimmer',
|
||||
name: 'Dimmer',
|
||||
platform: 'light' as const,
|
||||
state: true,
|
||||
},
|
||||
{
|
||||
id: 'battery',
|
||||
name: 'Battery',
|
||||
platform: 'sensor' as const,
|
||||
state: 87,
|
||||
unit: '%',
|
||||
deviceClass: 'battery',
|
||||
},
|
||||
{
|
||||
id: 'outlet',
|
||||
name: 'Outlet',
|
||||
platform: 'switch' as const,
|
||||
state: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual devolo Home Control candidates and creates config flow output', async () => {
|
||||
const descriptor = createDevoloHomeControlDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'devolo_home_control-manual-match');
|
||||
const result = await matcher!.matches({ name: 'devolo Home Control gateway 2600', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('devolo_home_control');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DevoloHomeControlConfigFlow().start(result.candidate!, {})).submit!({ username: 'user@example.com', password: 'secret' });
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.username).toEqual('user@example.com');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
});
|
||||
|
||||
tap.test('maps devolo Home Control raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DevoloHomeControlClient({ name: 'devolo Runtime Gateway', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DevoloHomeControlMapper.toDevices(snapshot);
|
||||
const entities = DevoloHomeControlMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('devolo_home_control');
|
||||
expect(devices[0].manufacturer).toEqual('devolo');
|
||||
expect(entities.some((entityArg) => entityArg.platform === 'climate')).toBeTrue();
|
||||
expect(entities.some((entityArg) => entityArg.platform === 'light')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes devolo Home Control runtime status and explicit unsupported control without executor', async () => {
|
||||
const alias = new HomeAssistantDevoloHomeControlIntegration();
|
||||
expect(alias instanceof DevoloHomeControlIntegration).toBeTrue();
|
||||
expect(alias.domain).toEqual('devolo_home_control');
|
||||
expect(devoloHomeControlProfile.status).toEqual('read-only-runtime');
|
||||
expect(devoloHomeControlProfile.metadata.qualityScale).toEqual('silver');
|
||||
expect(devoloHomeControlProfile.metadata.requirements).toEqual(['devolo-home-control-api==0.19.0']);
|
||||
|
||||
const runtime = await new DevoloHomeControlIntegration().setup({ name: 'devolo Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'devolo_home_control', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDevoloHomeControlSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.callService!({ domain: 'devolo_home_control', service: 'refresh', target: {} })).success).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('devolo Home Control Gateway');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'switch', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(controlCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,61 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DoodsClient, DoodsConfigFlow, DoodsIntegration, DoodsMapper, HomeAssistantDoodsIntegration, createDoodsDiscoveryDescriptor, doodsProfile, type IDoodsSnapshot } from '../../ts/integrations/doods/index.js';
|
||||
|
||||
const rawData = {
|
||||
detector: 'default',
|
||||
total_matches: 2,
|
||||
process_time: 0.13,
|
||||
online: true,
|
||||
};
|
||||
|
||||
tap.test('matches manual DOODS candidates and creates config flow output', async () => {
|
||||
const descriptor = createDoodsDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'doods-manual-match');
|
||||
const result = await matcher!.matches({ host: 'doods.local', name: 'DOODS - Dedicated Open Object Detection Service', metadata: { rawData, path: '/detect' } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('doods');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DoodsConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('doods.local');
|
||||
expect(done.config?.path).toEqual('/detect');
|
||||
});
|
||||
|
||||
tap.test('maps DOODS raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DoodsClient({ name: 'DOODS Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DoodsMapper.toDevices(snapshot);
|
||||
const entities = DoodsMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('doods');
|
||||
expect(devices[0].manufacturer).toEqual('DOODS');
|
||||
expect(entities.length > 0).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes DOODS read-only runtime without fake control', async () => {
|
||||
expect(new HomeAssistantDoodsIntegration().domain).toEqual('doods');
|
||||
expect(doodsProfile.status).toEqual('read-only-runtime');
|
||||
expect(doodsProfile.metadata.qualityScale).toEqual('legacy');
|
||||
expect(doodsProfile.metadata.requirements).toEqual(['pydoods==1.0.2', 'Pillow==12.2.0']);
|
||||
expect(doodsProfile.metadata.configFlow).toBeFalse();
|
||||
|
||||
const runtime = await new DoodsIntegration().setup({ name: 'DOODS Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'doods', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDoodsSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('DOODS Runtime');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'doods', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,61 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DormakabaDkeyClient, DormakabaDkeyConfigFlow, DormakabaDkeyIntegration, DormakabaDkeyMapper, HomeAssistantDormakabaDkeyIntegration, createDormakabaDkeyDiscoveryDescriptor, dormakabaDkeyProfile, type IDormakabaDkeySnapshot } from '../../ts/integrations/dormakaba_dkey/index.js';
|
||||
|
||||
const rawData = {
|
||||
battery_level: 88,
|
||||
door_position_open: false,
|
||||
security_locked: true,
|
||||
locked: true,
|
||||
};
|
||||
|
||||
tap.test('matches manual Dormakaba dKey candidates and creates config flow output', async () => {
|
||||
const descriptor = createDormakabaDkeyDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'dormakaba_dkey-manual-match');
|
||||
const result = await matcher!.matches({ id: 'AA:BB:CC:DD:EE:FF', name: 'Dormakaba dKey', metadata: { rawData, address: 'AA:BB:CC:DD:EE:FF' } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('dormakaba_dkey');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DormakabaDkeyConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.name).toEqual('Dormakaba dKey');
|
||||
expect(done.config?.uniqueId).toEqual('AA:BB:CC:DD:EE:FF');
|
||||
});
|
||||
|
||||
tap.test('maps Dormakaba dKey raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DormakabaDkeyClient({ name: 'Dormakaba dKey Test', uniqueId: 'AA_BB_CC_DD_EE_FF', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DormakabaDkeyMapper.toDevices(snapshot);
|
||||
const entities = DormakabaDkeyMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('dormakaba_dkey');
|
||||
expect(devices[0].manufacturer).toEqual('Dormakaba');
|
||||
expect(entities.length > 0).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes Dormakaba dKey runtime services without fake live control', async () => {
|
||||
expect(new HomeAssistantDormakabaDkeyIntegration().domain).toEqual('dormakaba_dkey');
|
||||
expect(dormakabaDkeyProfile.status).toEqual('control-runtime');
|
||||
expect(dormakabaDkeyProfile.metadata.configFlow).toBeTrue();
|
||||
expect(dormakabaDkeyProfile.metadata.dependencies).toEqual(['bluetooth_adapters']);
|
||||
expect(dormakabaDkeyProfile.metadata.requirements).toEqual(['py-dormakaba-dkey==1.0.6']);
|
||||
|
||||
const runtime = await new DormakabaDkeyIntegration().setup({ name: 'Dormakaba dKey Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'dormakaba_dkey', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDormakabaDkeySnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Dormakaba dKey Runtime');
|
||||
|
||||
const liveCommand = await runtime.callService!({ domain: 'lock', service: 'unlock', target: {} });
|
||||
expect(liveCommand.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,63 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DovadoClient, DovadoConfigFlow, DovadoIntegration, DovadoMapper, HomeAssistantDovadoIntegration, createDovadoDiscoveryDescriptor, dovadoProfile, type IDovadoSnapshot } from '../../ts/integrations/dovado/index.js';
|
||||
|
||||
const rawData = {
|
||||
'product name': 'Dovado Pro',
|
||||
'modem status': 'CONNECTED',
|
||||
'signal strength': '76% (LTE)',
|
||||
'sms unread': '2',
|
||||
'traffic modem tx': 2300000,
|
||||
'traffic modem rx': 5700000,
|
||||
connected: true,
|
||||
};
|
||||
|
||||
tap.test('matches manual Dovado candidates and creates config flow output', async () => {
|
||||
const descriptor = createDovadoDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'dovado-manual-match');
|
||||
const result = await matcher!.matches({ host: 'dovado.local', name: 'Dovado Router', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('dovado');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DovadoConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('dovado.local');
|
||||
});
|
||||
|
||||
tap.test('maps Dovado raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DovadoClient({ name: 'Dovado Router Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DovadoMapper.toDevices(snapshot);
|
||||
const entities = DovadoMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('dovado');
|
||||
expect(devices[0].manufacturer).toEqual('Dovado');
|
||||
expect(entities.length > 0).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes Dovado runtime services without fake live SMS control', async () => {
|
||||
expect(new HomeAssistantDovadoIntegration().domain).toEqual('dovado');
|
||||
expect(dovadoProfile.status).toEqual('control-runtime');
|
||||
expect(dovadoProfile.metadata.qualityScale).toEqual('legacy');
|
||||
expect(dovadoProfile.metadata.requirements).toEqual(['dovado==0.4.1']);
|
||||
expect(dovadoProfile.metadata.configFlow).toBeFalse();
|
||||
|
||||
const runtime = await new DovadoIntegration().setup({ name: 'Dovado Router Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'dovado', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDovadoSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Dovado Router Runtime');
|
||||
|
||||
const liveCommand = await runtime.callService!({ domain: 'notify', service: 'send_message', target: {}, data: { message: 'hello', target: ['+15550101'] } });
|
||||
expect(liveCommand.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,59 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { Dremel3dPrinterClient, Dremel3dPrinterConfigFlow, Dremel3dPrinterIntegration, Dremel3dPrinterMapper, HomeAssistantDremel3dPrinterIntegration, createDremel3dPrinterDiscoveryDescriptor, dremel3dPrinterProfile, type IDremel3dPrinterSnapshot } from '../../ts/integrations/dremel_3d_printer/index.js';
|
||||
|
||||
const rawData = {
|
||||
job_phase: 'building',
|
||||
progress: 42,
|
||||
door: false,
|
||||
running: true,
|
||||
};
|
||||
|
||||
tap.test('matches manual Dremel candidates and creates config flow output', async () => {
|
||||
const descriptor = createDremel3dPrinterDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'dremel_3d_printer-manual-match');
|
||||
const result = await matcher!.matches({ host: 'dremel.local', name: 'Dremel 3D45', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('dremel_3d_printer');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new Dremel3dPrinterConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('dremel.local');
|
||||
});
|
||||
|
||||
tap.test('maps Dremel raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new Dremel3dPrinterClient({ name: 'Dremel Lab Printer', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = Dremel3dPrinterMapper.toDevices(snapshot);
|
||||
const entities = Dremel3dPrinterMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('dremel_3d_printer');
|
||||
expect(devices[0].manufacturer).toEqual('Dremel');
|
||||
expect(entities.some((entityArg) => entityArg.name === 'Progress' && entityArg.state === 42)).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes Dremel runtime services, alias, and explicit gated control', async () => {
|
||||
expect(new HomeAssistantDremel3dPrinterIntegration().domain).toEqual('dremel_3d_printer');
|
||||
expect(dremel3dPrinterProfile.status).toEqual('control-runtime');
|
||||
expect(dremel3dPrinterProfile.metadata.qualityScale).toEqual('legacy');
|
||||
expect(dremel3dPrinterProfile.metadata.requirements).toEqual(['dremel3dpy==2.1.1']);
|
||||
|
||||
const runtime = await new Dremel3dPrinterIntegration().setup({ name: 'Dremel Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'dremel_3d_printer', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDremel3dPrinterSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Dremel Runtime');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'button', service: 'press', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,60 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DropConnectClient, DropConnectConfigFlow, DropConnectIntegration, DropConnectMapper, HomeAssistantDropConnectIntegration, createDropConnectDiscoveryDescriptor, dropConnectProfile, type IDropConnectSnapshot } from '../../ts/integrations/drop_connect/index.js';
|
||||
|
||||
const rawData = {
|
||||
current_flow_rate: 2.1,
|
||||
water_used_today: 18,
|
||||
leak: false,
|
||||
water: true,
|
||||
protect_mode: 'home',
|
||||
};
|
||||
|
||||
tap.test('matches manual DROP candidates and creates config flow output', async () => {
|
||||
const descriptor = createDropConnectDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'drop_connect-manual-match');
|
||||
const result = await matcher!.matches({ name: 'DROP Hub', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('drop_connect');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DropConnectConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
});
|
||||
|
||||
tap.test('maps DROP raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DropConnectClient({ name: 'DROP Runtime Hub', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DropConnectMapper.toDevices(snapshot);
|
||||
const entities = DropConnectMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('drop_connect');
|
||||
expect(devices[0].manufacturer).toEqual('Chandler Systems, Inc.');
|
||||
expect(entities.some((entityArg) => entityArg.name === 'Current Flow Rate' && entityArg.state === 2.1)).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes DROP runtime services, alias, and explicit gated control', async () => {
|
||||
expect(new HomeAssistantDropConnectIntegration().domain).toEqual('drop_connect');
|
||||
expect(dropConnectProfile.status).toEqual('control-runtime');
|
||||
expect(dropConnectProfile.metadata.dependencies).toEqual(['mqtt']);
|
||||
expect(dropConnectProfile.metadata.requirements).toEqual(['dropmqttapi==1.0.3']);
|
||||
|
||||
const runtime = await new DropConnectIntegration().setup({ name: 'DROP Runtime Hub', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'drop_connect', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDropConnectSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('DROP Runtime Hub');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'select', service: 'select_option', target: {}, data: { option: 'away' } });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,60 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DropletClient, DropletConfigFlow, DropletIntegration, DropletMapper, HomeAssistantDropletIntegration, createDropletDiscoveryDescriptor, dropletProfile, type IDropletSnapshot } from '../../ts/integrations/droplet/index.js';
|
||||
|
||||
const rawData = {
|
||||
current_flow_rate: 1.25,
|
||||
volume: 30.5,
|
||||
server_connectivity: 'connected',
|
||||
signal_quality: 'strong_signal',
|
||||
};
|
||||
|
||||
tap.test('matches manual Droplet candidates and creates config flow output', async () => {
|
||||
const descriptor = createDropletDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'droplet-manual-match');
|
||||
const result = await matcher!.matches({ host: 'droplet.local', name: 'Droplet Kitchen', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('droplet');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DropletConfigFlow().start(result.candidate!, {})).submit!({ token: 'PAIR1234' });
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('droplet.local');
|
||||
expect(done.config?.token).toEqual('PAIR1234');
|
||||
});
|
||||
|
||||
tap.test('maps Droplet raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DropletClient({ name: 'Droplet Kitchen', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DropletMapper.toDevices(snapshot);
|
||||
const entities = DropletMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('droplet');
|
||||
expect(entities.length).toEqual(4);
|
||||
expect(entities.some((entityArg) => entityArg.name === 'Signal Quality' && entityArg.state === 'strong_signal')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes Droplet read-only runtime, alias, and explicit unsupported control', async () => {
|
||||
expect(new HomeAssistantDropletIntegration().domain).toEqual('droplet');
|
||||
expect(dropletProfile.status).toEqual('read-only-runtime');
|
||||
expect(dropletProfile.metadata.qualityScale).toEqual('bronze');
|
||||
expect(dropletProfile.metadata.requirements).toEqual(['pydroplet==2.3.4']);
|
||||
|
||||
const runtime = await new DropletIntegration().setup({ name: 'Droplet Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'droplet', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDropletSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Droplet Runtime');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'droplet', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,60 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DsmrReaderClient, DsmrReaderConfigFlow, DsmrReaderIntegration, DsmrReaderMapper, HomeAssistantDsmrReaderIntegration, createDsmrReaderDiscoveryDescriptor, dsmrReaderProfile, type IDsmrReaderSnapshot } from '../../ts/integrations/dsmr_reader/index.js';
|
||||
|
||||
const rawData = {
|
||||
electricity_currently_delivered: 1.24,
|
||||
electricity_tariff: 'low',
|
||||
gas_usage: 12.8,
|
||||
};
|
||||
|
||||
tap.test('matches manual DSMR Reader candidates and creates config flow output', async () => {
|
||||
const descriptor = createDsmrReaderDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'dsmr_reader-manual-match');
|
||||
const result = await matcher!.matches({ host: 'mqtt.local', name: 'DSMR Reader', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('dsmr_reader');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DsmrReaderConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('mqtt.local');
|
||||
});
|
||||
|
||||
tap.test('maps DSMR Reader raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DsmrReaderClient({ name: 'DSMR Reader Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DsmrReaderMapper.toDevices(snapshot);
|
||||
const entities = DsmrReaderMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('dsmr_reader');
|
||||
expect(devices[0].protocol).toEqual('mqtt');
|
||||
expect(entities.length).toEqual(3);
|
||||
});
|
||||
|
||||
tap.test('exposes DSMR Reader read-only runtime without fake live control', async () => {
|
||||
const integration = new HomeAssistantDsmrReaderIntegration();
|
||||
expect(integration.domain).toEqual('dsmr_reader');
|
||||
expect(dsmrReaderProfile.status).toEqual('read-only-runtime');
|
||||
expect(dsmrReaderProfile.metadata.dependencies).toEqual(['mqtt']);
|
||||
expect(dsmrReaderProfile.metadata.configFlow).toEqual(true);
|
||||
|
||||
const runtime = await new DsmrReaderIntegration().setup({ name: 'DSMR Reader Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'dsmr_reader', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDsmrReaderSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('DSMR Reader Runtime');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'dsmr_reader', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(Boolean(controlCommand.error)).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,72 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DucoClient, DucoConfigFlow, DucoIntegration, DucoMapper, HomeAssistantDucoIntegration, createDucoDiscoveryDescriptor, ducoProfile, type IDucoSnapshot } from '../../ts/integrations/duco/index.js';
|
||||
|
||||
const rawData = {
|
||||
ventilation_state: 'cnt2',
|
||||
target_flow_level: 66,
|
||||
rssi_wifi: -48,
|
||||
};
|
||||
|
||||
tap.test('matches manual Duco candidates and creates config flow output', async () => {
|
||||
const descriptor = createDucoDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'duco-manual-match');
|
||||
const result = await matcher!.matches({ host: 'duco.local', name: 'Duco Ventilation', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('duco');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DucoConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('duco.local');
|
||||
});
|
||||
|
||||
tap.test('maps Duco raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DucoClient({ name: 'Duco Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DucoMapper.toDevices(snapshot);
|
||||
const entities = DucoMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('duco');
|
||||
expect(devices[0].manufacturer).toEqual('Duco');
|
||||
expect(entities.length).toEqual(3);
|
||||
});
|
||||
|
||||
tap.test('exposes Duco runtime services without fake live control', async () => {
|
||||
const integration = new HomeAssistantDucoIntegration();
|
||||
expect(integration.domain).toEqual('duco');
|
||||
expect(integration.status).toEqual('control-runtime');
|
||||
expect(ducoProfile.metadata.qualityScale).toEqual('platinum');
|
||||
expect(ducoProfile.metadata.requirements).toEqual(['python-duco-client==0.4.0']);
|
||||
|
||||
const runtime = await new DucoIntegration().setup({ name: 'Duco Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'duco', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDucoSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Duco Runtime');
|
||||
|
||||
const liveCommand = await runtime.callService!({ domain: 'fan', service: 'set_percentage', target: {}, data: { percentage: 66 } });
|
||||
expect(liveCommand.success).toBeFalse();
|
||||
expect(Boolean(liveCommand.error)).toBeTrue();
|
||||
await runtime.destroy();
|
||||
|
||||
const executorRuntime = await new DucoIntegration().setup({
|
||||
name: 'Duco Executor',
|
||||
rawData,
|
||||
commandExecutor: {
|
||||
execute: async (requestArg) => ({ success: true, data: { service: requestArg.service } }),
|
||||
},
|
||||
}, {});
|
||||
const executed = await executorRuntime.callService!({ domain: 'fan', service: 'set_preset_mode', target: {}, data: { preset_mode: 'auto' } });
|
||||
expect(executed.success).toBeTrue();
|
||||
expect((executed.data as { service: string }).service).toEqual('set_preset_mode');
|
||||
await executorRuntime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,81 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DuotecnoClient, DuotecnoConfigFlow, DuotecnoIntegration, DuotecnoMapper, HomeAssistantDuotecnoIntegration, createDuotecnoDiscoveryDescriptor, duotecnoProfile, type IDuotecnoSnapshot } from '../../ts/integrations/duotecno/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'duotecno-controller-1',
|
||||
name: 'Duotecno Controller',
|
||||
manufacturer: 'Duotecno',
|
||||
},
|
||||
entities: [
|
||||
{ id: 'input_1', name: 'Input 1', platform: 'binary_sensor', state: true },
|
||||
{ id: 'living_room_temperature', name: 'Living Room Temperature', platform: 'climate', state: 21.5, writable: true, attributes: { targetTemperature: 22 } },
|
||||
{ id: 'shutter', name: 'Shutter', platform: 'cover', state: 'open', writable: true },
|
||||
{ id: 'entry_light', name: 'Entry Light', platform: 'light', state: true, writable: true },
|
||||
{ id: 'pump', name: 'Pump', platform: 'switch', state: false, writable: true },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual Duotecno candidates and creates config flow output', async () => {
|
||||
const descriptor = createDuotecnoDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'duotecno-manual-match');
|
||||
const result = await matcher!.matches({ host: 'duotecno.local', name: 'Duotecno Controller', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('duotecno');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DuotecnoConfigFlow().start(result.candidate!, {})).submit!({ password: 'secret', port: 1234 });
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('duotecno.local');
|
||||
});
|
||||
|
||||
tap.test('maps Duotecno raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new DuotecnoClient({ name: 'Duotecno Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = DuotecnoMapper.toDevices(snapshot);
|
||||
const entities = DuotecnoMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('duotecno');
|
||||
expect(devices[0].manufacturer).toEqual('Duotecno');
|
||||
expect(entities.length).toEqual(5);
|
||||
});
|
||||
|
||||
tap.test('exposes Duotecno runtime services without fake live control', async () => {
|
||||
const integration = new HomeAssistantDuotecnoIntegration();
|
||||
expect(integration.domain).toEqual('duotecno');
|
||||
expect(integration.status).toEqual('control-runtime');
|
||||
expect(duotecnoProfile.metadata.requirements).toEqual(['pyDuotecno==2024.10.1']);
|
||||
expect(duotecnoProfile.metadata.configFlow).toEqual(true);
|
||||
|
||||
const runtime = await new DuotecnoIntegration().setup({ name: 'Duotecno Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'duotecno', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDuotecnoSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Duotecno Controller');
|
||||
|
||||
const liveCommand = await runtime.callService!({ domain: 'cover', service: 'open_cover', target: {} });
|
||||
expect(liveCommand.success).toBeFalse();
|
||||
expect(Boolean(liveCommand.error)).toBeTrue();
|
||||
await runtime.destroy();
|
||||
|
||||
const executorRuntime = await new DuotecnoIntegration().setup({
|
||||
name: 'Duotecno Executor',
|
||||
rawData,
|
||||
commandExecutor: {
|
||||
execute: async (requestArg) => ({ success: true, data: { service: requestArg.service } }),
|
||||
},
|
||||
}, {});
|
||||
const executed = await executorRuntime.callService!({ domain: 'light', service: 'turn_on', target: {}, data: { brightness: 128 } });
|
||||
expect(executed.success).toBeTrue();
|
||||
expect((executed.data as { service: string }).service).toEqual('turn_on');
|
||||
await executorRuntime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,72 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DynaliteClient, DynaliteConfigFlow, DynaliteIntegration, DynaliteMapper, HomeAssistantDynaliteIntegration, dynaliteProfile, createDynaliteDiscoveryDescriptor, type IDynaliteSnapshot } from '../../ts/integrations/dynalite/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'dynalite-gateway-1',
|
||||
name: 'Dynalite Gateway',
|
||||
manufacturer: 'Dynalite',
|
||||
model: 'Dynalite gateway',
|
||||
host: '192.0.2.10',
|
||||
port: 12345,
|
||||
},
|
||||
entities: [
|
||||
{ id: 'area_1_channel_1', name: 'Lobby Channel 1', platform: 'light', state: 180, unit: 'level', writable: true },
|
||||
{ id: 'area_1_cover', name: 'Lobby Blind', platform: 'cover', state: 75, unit: '%', writable: true },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual Dynalite candidates and creates config flow output', async () => {
|
||||
const descriptor = createDynaliteDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'dynalite-manual-match');
|
||||
const result = await matcher!.matches({ host: '192.0.2.10', name: 'Philips Dynalite Gateway', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('dynalite');
|
||||
expect(result.candidate?.port).toEqual(12345);
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new DynaliteConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('192.0.2.10');
|
||||
expect(done.config?.port).toEqual(12345);
|
||||
});
|
||||
|
||||
tap.test('maps Dynalite raw snapshots to runtime devices and entities', async () => {
|
||||
const snapshot = DynaliteMapper.toSnapshotFromRaw({ name: 'Dynalite Test', rawData }, rawData);
|
||||
const devices = DynaliteMapper.toDevices(snapshot);
|
||||
const entities = DynaliteMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(devices[0].integrationDomain).toEqual('dynalite');
|
||||
expect(devices[0].manufacturer).toEqual('Dynalite');
|
||||
expect(entities.some((entityArg) => entityArg.platform === 'cover')).toBeTrue();
|
||||
expect(entities.some((entityArg) => entityArg.platform === 'light')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes Dynalite runtime services and executor-only controls', async () => {
|
||||
expect(new HomeAssistantDynaliteIntegration().domain).toEqual('dynalite');
|
||||
expect(dynaliteProfile.status).toEqual('control-runtime');
|
||||
expect(dynaliteProfile.metadata.configFlow).toBeTrue();
|
||||
expect(dynaliteProfile.metadata.requirements).toEqual(['dynalite-devices==0.1.47', 'dynalite-panel==0.0.4']);
|
||||
|
||||
const client = new DynaliteClient({ name: 'Dynalite Client', rawData });
|
||||
expect((await client.getSnapshot()).source).toEqual('manual');
|
||||
|
||||
const runtime = await new DynaliteIntegration().setup({ name: 'Dynalite Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'dynalite', service: 'status', target: {} });
|
||||
const snapshot = status.data as IDynaliteSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Dynalite Gateway');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'dynalite', service: 'request_area_preset', target: {}, data: { area: 1 } });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(controlCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,71 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EarnEP1Client, EarnEP1ConfigFlow, EarnEP1Integration, EarnEP1Mapper, HomeAssistantEarnEP1Integration, earnEP1Profile, createEarnEP1DiscoveryDescriptor, type IEarnEP1Snapshot } from '../../ts/integrations/earn_e_p1/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'earn-e-serial-1',
|
||||
name: 'EARN-E P1 Meter',
|
||||
manufacturer: 'EARN-E',
|
||||
model: 'P1 Meter',
|
||||
serialNumber: 'EARN123456',
|
||||
host: '192.0.2.20',
|
||||
},
|
||||
entities: [
|
||||
{ id: 'power_delivered', name: 'Power imported', platform: 'sensor', state: 1.23, unit: 'kW', deviceClass: 'power', stateClass: 'measurement' },
|
||||
{ id: 'energy_delivered_tariff1', name: 'Energy imported tariff 1', platform: 'sensor', state: 456.78, unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' },
|
||||
{ id: 'wifiRSSI', name: 'Wi-Fi RSSI', platform: 'sensor', state: -61, unit: 'dBm', deviceClass: 'signal_strength' },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual EARN-E P1 candidates and creates config flow output', async () => {
|
||||
const descriptor = createEarnEP1DiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'earn_e_p1-manual-match');
|
||||
const result = await matcher!.matches({ host: '192.0.2.20', name: 'EARN-E P1 Meter', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('earn_e_p1');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EarnEP1ConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('192.0.2.20');
|
||||
});
|
||||
|
||||
tap.test('maps EARN-E P1 raw snapshots to runtime devices and entities', async () => {
|
||||
const snapshot = EarnEP1Mapper.toSnapshotFromRaw({ name: 'EARN-E Test', rawData }, rawData);
|
||||
const devices = EarnEP1Mapper.toDevices(snapshot);
|
||||
const entities = EarnEP1Mapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(devices[0].integrationDomain).toEqual('earn_e_p1');
|
||||
expect(devices[0].manufacturer).toEqual('EARN-E');
|
||||
expect(entities.length).toEqual(3);
|
||||
expect(entities.some((entityArg) => entityArg.attributes?.deviceClass === 'energy')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes EARN-E P1 read-only runtime and unsupported controls', async () => {
|
||||
expect(new HomeAssistantEarnEP1Integration().domain).toEqual('earn_e_p1');
|
||||
expect(earnEP1Profile.status).toEqual('read-only-runtime');
|
||||
expect(earnEP1Profile.metadata.qualityScale).toEqual('bronze');
|
||||
expect(earnEP1Profile.metadata.requirements).toEqual(['earn-e-p1==0.1.0']);
|
||||
|
||||
const client = new EarnEP1Client({ name: 'EARN-E Client', rawData });
|
||||
expect((await client.getSnapshot()).source).toEqual('manual');
|
||||
|
||||
const runtime = await new EarnEP1Integration().setup({ name: 'EARN-E Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'earn_e_p1', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEarnEP1Snapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('EARN-E P1 Meter');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'earn_e_p1', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(controlCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,72 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EbusdClient, EbusdConfigFlow, EbusdIntegration, EbusdMapper, HomeAssistantEbusdIntegration, ebusdProfile, createEbusdDiscoveryDescriptor, type IEbusdSnapshot } from '../../ts/integrations/ebusd/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'ebusd-daemon-1',
|
||||
name: 'ebusd Boiler',
|
||||
manufacturer: 'ebusd',
|
||||
model: 'eBUS daemon',
|
||||
host: '192.0.2.30',
|
||||
port: 8888,
|
||||
},
|
||||
entities: [
|
||||
{ id: 'Hc1FlowTemp', name: 'Hc1FlowTemp', platform: 'sensor', state: 41.5, unit: '°C', deviceClass: 'temperature' },
|
||||
{ id: 'HwcOpMode', name: 'HwcOpMode', platform: 'sensor', state: 'auto' },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual ebusd candidates and creates config flow output', async () => {
|
||||
const descriptor = createEbusdDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'ebusd-manual-match');
|
||||
const result = await matcher!.matches({ host: '192.0.2.30', name: 'ebusd daemon', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('ebusd');
|
||||
expect(result.candidate?.port).toEqual(8888);
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EbusdConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('192.0.2.30');
|
||||
expect(done.config?.port).toEqual(8888);
|
||||
});
|
||||
|
||||
tap.test('maps ebusd raw snapshots to runtime devices and entities', async () => {
|
||||
const snapshot = EbusdMapper.toSnapshotFromRaw({ name: 'ebusd Test', rawData }, rawData);
|
||||
const devices = EbusdMapper.toDevices(snapshot);
|
||||
const entities = EbusdMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(devices[0].integrationDomain).toEqual('ebusd');
|
||||
expect(devices[0].manufacturer).toEqual('ebusd');
|
||||
expect(entities.length).toEqual(2);
|
||||
expect(entities.some((entityArg) => entityArg.attributes?.deviceClass === 'temperature')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes ebusd runtime services and executor-only writes', async () => {
|
||||
expect(new HomeAssistantEbusdIntegration().domain).toEqual('ebusd');
|
||||
expect(ebusdProfile.status).toEqual('control-runtime');
|
||||
expect(ebusdProfile.metadata.configFlow).toBeFalse();
|
||||
expect(ebusdProfile.metadata.requirements).toEqual(['ebusdpy==0.0.17']);
|
||||
|
||||
const client = new EbusdClient({ name: 'ebusd Client', rawData });
|
||||
expect((await client.getSnapshot()).source).toEqual('manual');
|
||||
|
||||
const runtime = await new EbusdIntegration().setup({ name: 'ebusd Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'ebusd', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEbusdSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('ebusd Boiler');
|
||||
|
||||
const writeCommand = await runtime.callService!({ domain: 'ebusd', service: 'write', target: {}, data: { name: 'Hc1MaxFlowTempDesired', value: 21 } });
|
||||
expect(writeCommand.success).toBeFalse();
|
||||
expect(writeCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,107 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EcoalBoilerClient, EcoalBoilerConfigFlow, EcoalBoilerIntegration, EcoalBoilerMapper, HomeAssistantEcoalBoilerIntegration, ecoalBoilerProfile, createEcoalBoilerDiscoveryDescriptor, type IEcoalBoilerSnapshot } from '../../ts/integrations/ecoal_boiler/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'ecoal-1234',
|
||||
name: 'Boiler room eCoal',
|
||||
serialNumber: 'ecoal-1234',
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
id: 'outdoor_temp',
|
||||
name: 'Outdoor temperature',
|
||||
platform: 'sensor',
|
||||
state: 8.5,
|
||||
unit: 'C',
|
||||
deviceClass: 'temperature',
|
||||
},
|
||||
{
|
||||
id: 'domestic_hot_water_temp',
|
||||
name: 'Domestic hot water temperature',
|
||||
platform: 'sensor',
|
||||
state: 47,
|
||||
unit: 'C',
|
||||
deviceClass: 'temperature',
|
||||
},
|
||||
{
|
||||
id: 'central_heating_pump',
|
||||
name: 'Central heating pump',
|
||||
platform: 'switch',
|
||||
state: true,
|
||||
writable: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('defines the eCoal Boiler simple-local profile and HA alias', async () => {
|
||||
const integration = new HomeAssistantEcoalBoilerIntegration();
|
||||
|
||||
expect(integration).toBeInstanceOf(EcoalBoilerIntegration);
|
||||
expect(integration.domain).toEqual('ecoal_boiler');
|
||||
expect(integration.status).toEqual('control-runtime');
|
||||
expect(ecoalBoilerProfile.metadata.upstreamPath).toEqual('homeassistant/components/ecoal_boiler');
|
||||
expect(ecoalBoilerProfile.metadata.qualityScale).toEqual('legacy');
|
||||
expect(ecoalBoilerProfile.metadata.requirements).toEqual(['ecoaliface==0.4.0']);
|
||||
expect(ecoalBoilerProfile.metadata.configFlow).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('matches manual eCoal Boiler candidates and creates config flow output', async () => {
|
||||
const descriptor = createEcoalBoilerDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'ecoal_boiler-manual-match');
|
||||
const result = await matcher!.matches({ host: 'ecoal.local', name: 'eSterownik eCoal.pl Boiler', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('ecoal_boiler');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EcoalBoilerConfigFlow().start(result.candidate!, {})).submit!({ username: 'admin' });
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('ecoal.local');
|
||||
expect(done.config?.username).toEqual('admin');
|
||||
});
|
||||
|
||||
tap.test('maps eCoal Boiler raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new EcoalBoilerClient({ name: 'eCoal Boiler Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = EcoalBoilerMapper.toDevices(snapshot);
|
||||
const entities = EcoalBoilerMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('ecoal_boiler');
|
||||
expect(devices[0].manufacturer).toEqual('eSterownik');
|
||||
expect(entities.some((entityArg) => entityArg.id === 'sensor.boiler_room_ecoal_outdoor_temp')).toBeTrue();
|
||||
expect(entities.some((entityArg) => entityArg.id === 'switch.boiler_room_ecoal_central_heating_pump')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes eCoal Boiler runtime services without fake live control', async () => {
|
||||
const runtime = await new EcoalBoilerIntegration().setup({ name: 'eCoal Boiler Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'ecoal_boiler', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEcoalBoilerSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Boiler room eCoal');
|
||||
|
||||
const liveCommand = await runtime.callService!({ domain: 'switch', service: 'turn_on', target: {} });
|
||||
expect(liveCommand.success).toBeFalse();
|
||||
expect(liveCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
|
||||
const executorRuntime = await new EcoalBoilerIntegration().setup({
|
||||
name: 'eCoal Boiler Executor',
|
||||
rawData,
|
||||
commandExecutor: {
|
||||
execute: async (requestArg) => ({ success: true, data: { service: requestArg.service } }),
|
||||
},
|
||||
}, {});
|
||||
const executed = await executorRuntime.callService!({ domain: 'switch', service: 'turn_off', target: {} });
|
||||
expect(executed.success).toBeTrue();
|
||||
expect((executed.data as { service: string }).service).toEqual('turn_off');
|
||||
await executorRuntime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,115 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EcoforestClient, EcoforestConfigFlow, EcoforestIntegration, EcoforestMapper, HomeAssistantEcoforestIntegration, ecoforestProfile, createEcoforestDiscoveryDescriptor, type IEcoforestSnapshot } from '../../ts/integrations/ecoforest/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'eco-forest-5678',
|
||||
name: 'Ecoforest stove',
|
||||
model: 'Vigo III',
|
||||
serialNumber: 'eco-forest-5678',
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
id: 'temperature',
|
||||
name: 'Temperature',
|
||||
platform: 'sensor',
|
||||
state: 22.4,
|
||||
unit: 'C',
|
||||
deviceClass: 'temperature',
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
name: 'Status',
|
||||
platform: 'sensor',
|
||||
state: 'on',
|
||||
},
|
||||
{
|
||||
id: 'power_level',
|
||||
name: 'Power level',
|
||||
platform: 'number',
|
||||
state: 5,
|
||||
writable: true,
|
||||
},
|
||||
{
|
||||
id: 'status_switch',
|
||||
name: 'Status switch',
|
||||
platform: 'switch',
|
||||
state: true,
|
||||
writable: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('defines the Ecoforest simple-local profile and HA alias', async () => {
|
||||
const integration = new HomeAssistantEcoforestIntegration();
|
||||
|
||||
expect(integration).toBeInstanceOf(EcoforestIntegration);
|
||||
expect(integration.domain).toEqual('ecoforest');
|
||||
expect(integration.status).toEqual('control-runtime');
|
||||
expect(ecoforestProfile.metadata.upstreamPath).toEqual('homeassistant/components/ecoforest');
|
||||
expect(ecoforestProfile.metadata.qualityScale).toEqual(undefined);
|
||||
expect(ecoforestProfile.metadata.requirements).toEqual(['pyecoforest==0.4.0']);
|
||||
expect(ecoforestProfile.metadata.configFlow).toBeTrue();
|
||||
expect(ecoforestProfile.serviceDomains).toEqual(['number', 'switch']);
|
||||
});
|
||||
|
||||
tap.test('matches manual Ecoforest candidates and creates config flow output', async () => {
|
||||
const descriptor = createEcoforestDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'ecoforest-manual-match');
|
||||
const result = await matcher!.matches({ host: 'ecoforest.local', name: 'Ecoforest stove', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('ecoforest');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EcoforestConfigFlow().start(result.candidate!, {})).submit!({ username: 'user', password: 'pass' });
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('ecoforest.local');
|
||||
expect(done.config?.username).toEqual('user');
|
||||
});
|
||||
|
||||
tap.test('maps Ecoforest raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new EcoforestClient({ name: 'Ecoforest Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = EcoforestMapper.toDevices(snapshot);
|
||||
const entities = EcoforestMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('ecoforest');
|
||||
expect(devices[0].manufacturer).toEqual('Ecoforest');
|
||||
expect(entities.some((entityArg) => entityArg.id === 'sensor.ecoforest_stove_temperature')).toBeTrue();
|
||||
expect(entities.some((entityArg) => entityArg.id === 'number.ecoforest_stove_power_level')).toBeTrue();
|
||||
expect(entities.some((entityArg) => entityArg.id === 'switch.ecoforest_stove_status_switch')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes Ecoforest runtime services without fake live control', async () => {
|
||||
const runtime = await new EcoforestIntegration().setup({ name: 'Ecoforest Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'ecoforest', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEcoforestSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.entities()).some((entityArg) => entityArg.platform === 'number')).toBeTrue();
|
||||
|
||||
const liveCommand = await runtime.callService!({ domain: 'number', service: 'set_value', target: {}, data: { value: 6 } });
|
||||
expect(liveCommand.success).toBeFalse();
|
||||
expect(liveCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
|
||||
const executorRuntime = await new EcoforestIntegration().setup({
|
||||
name: 'Ecoforest Executor',
|
||||
rawData,
|
||||
commandExecutor: {
|
||||
execute: async (requestArg) => ({ success: true, data: { service: requestArg.service } }),
|
||||
},
|
||||
}, {});
|
||||
const executed = await executorRuntime.callService!({ domain: 'switch', service: 'turn_on', target: {} });
|
||||
expect(executed.success).toBeTrue();
|
||||
expect((executed.data as { service: string }).service).toEqual('turn_on');
|
||||
await executorRuntime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,97 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EcowittClient, EcowittConfigFlow, EcowittIntegration, EcowittMapper, HomeAssistantEcowittIntegration, ecowittProfile, createEcowittDiscoveryDescriptor, type IEcowittSnapshot } from '../../ts/integrations/ecowitt/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'gw1000-90ab',
|
||||
name: 'Ecowitt GW1000',
|
||||
model: 'GW1000',
|
||||
serialNumber: 'gw1000-90ab',
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
id: 'tempinf',
|
||||
name: 'Indoor temperature',
|
||||
platform: 'sensor',
|
||||
state: 21.7,
|
||||
unit: 'C',
|
||||
deviceClass: 'temperature',
|
||||
},
|
||||
{
|
||||
id: 'humidityin',
|
||||
name: 'Indoor humidity',
|
||||
platform: 'sensor',
|
||||
state: 48,
|
||||
unit: '%',
|
||||
deviceClass: 'humidity',
|
||||
},
|
||||
{
|
||||
id: 'leak_ch1',
|
||||
name: 'Leak channel 1',
|
||||
platform: 'binary_sensor',
|
||||
state: false,
|
||||
deviceClass: 'moisture',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('defines the Ecowitt simple-local profile and HA alias', async () => {
|
||||
const integration = new HomeAssistantEcowittIntegration();
|
||||
|
||||
expect(integration).toBeInstanceOf(EcowittIntegration);
|
||||
expect(integration.domain).toEqual('ecowitt');
|
||||
expect(integration.status).toEqual('read-only-runtime');
|
||||
expect(ecowittProfile.metadata.upstreamPath).toEqual('homeassistant/components/ecowitt');
|
||||
expect(ecowittProfile.metadata.qualityScale).toEqual(undefined);
|
||||
expect(ecowittProfile.metadata.requirements).toEqual(['aioecowitt==2025.9.2']);
|
||||
expect(ecowittProfile.metadata.dependencies).toEqual(['webhook']);
|
||||
expect(ecowittProfile.metadata.configFlow).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('matches manual Ecowitt candidates and creates config flow output', async () => {
|
||||
const descriptor = createEcowittDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'ecowitt-manual-match');
|
||||
const result = await matcher!.matches({ name: 'Ecowitt GW1000', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('ecowitt');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EcowittConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.transport).toEqual('snapshot');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
});
|
||||
|
||||
tap.test('maps Ecowitt raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new EcowittClient({ name: 'Ecowitt Test', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = EcowittMapper.toDevices(snapshot);
|
||||
const entities = EcowittMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('ecowitt');
|
||||
expect(devices[0].manufacturer).toEqual('Ecowitt');
|
||||
expect(entities.some((entityArg) => entityArg.id === 'sensor.ecowitt_gw1000_tempinf')).toBeTrue();
|
||||
expect(entities.some((entityArg) => entityArg.id === 'binary_sensor.ecowitt_gw1000_leak_ch1')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes Ecowitt read-only runtime and rejects unsupported control', async () => {
|
||||
const runtime = await new EcowittIntegration().setup({ name: 'Ecowitt Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'ecowitt', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEcowittSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Ecowitt GW1000');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'ecowitt', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(controlCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,61 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EdimaxClient, EdimaxConfigFlow, EdimaxIntegration, EdimaxMapper, HomeAssistantEdimaxIntegration, createEdimaxDiscoveryDescriptor, edimaxProfile, type IEdimaxSnapshot } from '../../ts/integrations/edimax/index.js';
|
||||
|
||||
const rawData = {
|
||||
info: {
|
||||
mac: 'AA:BB:CC:DD:EE:FF',
|
||||
},
|
||||
state: 'ON',
|
||||
};
|
||||
|
||||
tap.test('matches manual Edimax candidates and creates config flow output', async () => {
|
||||
const descriptor = createEdimaxDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'edimax-manual-match');
|
||||
const result = await matcher!.matches({ host: 'edimax.local', name: 'Edimax Smart Plug', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('edimax');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EdimaxConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('edimax.local');
|
||||
});
|
||||
|
||||
tap.test('maps Edimax raw snapshots to switch devices and entities', async () => {
|
||||
const client = new EdimaxClient({ name: 'Kitchen Plug', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = EdimaxMapper.toDevices(snapshot);
|
||||
const entities = EdimaxMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(devices[0].integrationDomain).toEqual('edimax');
|
||||
expect(devices[0].manufacturer).toEqual('Edimax');
|
||||
expect(devices[0].features[0].writable).toBeTrue();
|
||||
expect(entities[0].platform).toEqual('switch');
|
||||
expect(entities[0].state).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes Edimax runtime, HA alias, and explicit unsupported control without executor', async () => {
|
||||
expect(new HomeAssistantEdimaxIntegration().domain).toEqual('edimax');
|
||||
expect(edimaxProfile.status).toEqual('control-runtime');
|
||||
expect(edimaxProfile.metadata.configFlow).toEqual(false);
|
||||
expect(edimaxProfile.metadata.requirements).toEqual(['pyedimax==0.2.1']);
|
||||
|
||||
const runtime = await new EdimaxIntegration().setup({ name: 'Kitchen Plug', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'edimax', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEdimaxSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Kitchen Plug');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'switch', service: 'turn_off', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(controlCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,63 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { Edl21Client, Edl21ConfigFlow, Edl21Integration, Edl21Mapper, HomeAssistantEdl21Integration, createEdl21DiscoveryDescriptor, edl21Profile, type IEdl21Snapshot } from '../../ts/integrations/edl21/index.js';
|
||||
|
||||
const rawData = {
|
||||
serverId: '01 23 45 67 89 AB',
|
||||
valList: [
|
||||
{ objName: '1-0:1.7.0*255', value: 512, unit: 'W' },
|
||||
{ objName: '1-0:1.8.0*255', value: 12345, unit: 'Wh' },
|
||||
{ objName: '1-0:96.50.1*1', value: 'ignored' },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual EDL21 candidates and creates config flow output', async () => {
|
||||
const descriptor = createEdl21DiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'edl21-manual-match');
|
||||
const result = await matcher!.matches({ name: 'EDL21 Smart Meter', metadata: { rawData, serial_port: '/dev/ttyUSB0' } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('edl21');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new Edl21ConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
});
|
||||
|
||||
tap.test('maps EDL21 raw telegram snapshots to smart meter devices and entities', async () => {
|
||||
const client = new Edl21Client({ name: 'Utility Meter', serialPort: '/dev/ttyUSB0', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = Edl21Mapper.toDevices(snapshot);
|
||||
const entities = Edl21Mapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(devices[0].integrationDomain).toEqual('edl21');
|
||||
expect(devices[0].model).toEqual('Smart Meter');
|
||||
expect(entities.length).toEqual(2);
|
||||
expect(entities[0].attributes?.unit).toEqual('W');
|
||||
expect(entities[1].attributes?.deviceClass).toEqual('energy');
|
||||
});
|
||||
|
||||
tap.test('exposes EDL21 read-only runtime, HA alias, and explicit unsupported control without executor', async () => {
|
||||
expect(new HomeAssistantEdl21Integration().domain).toEqual('edl21');
|
||||
expect(edl21Profile.status).toEqual('read-only-runtime');
|
||||
expect(edl21Profile.metadata.configFlow).toEqual(true);
|
||||
expect(edl21Profile.metadata.requirements).toEqual(['pysml==0.1.5']);
|
||||
|
||||
const runtime = await new Edl21Integration().setup({ name: 'Utility Meter', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'edl21', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEdl21Snapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Utility Meter');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'edl21', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(controlCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,64 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EgardiaClient, EgardiaConfigFlow, EgardiaIntegration, EgardiaMapper, HomeAssistantEgardiaIntegration, createEgardiaDiscoveryDescriptor, egardiaProfile, type IEgardiaSnapshot } from '../../ts/integrations/egardia/index.js';
|
||||
|
||||
const rawData = {
|
||||
state: 'ARM',
|
||||
version: 'GATE-01',
|
||||
sensors: {
|
||||
front: { id: 'front', name: 'Front Door', type: 'Door Contact', state: true },
|
||||
hall: { id: 'hall', name: 'Hall', type: 'IR Sensor', state: false },
|
||||
},
|
||||
};
|
||||
|
||||
tap.test('matches manual Egardia candidates and creates config flow output', async () => {
|
||||
const descriptor = createEgardiaDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'egardia-manual-match');
|
||||
const result = await matcher!.matches({ host: 'egardia.local', name: 'Egardia', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('egardia');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EgardiaConfigFlow().start(result.candidate!, {})).submit!({ username: 'user', password: 'pass' });
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('egardia.local');
|
||||
});
|
||||
|
||||
tap.test('maps Egardia raw snapshots to alarm devices and binary sensor entities', async () => {
|
||||
const client = new EgardiaClient({ host: 'egardia.local', name: 'House Alarm', username: 'user', password: 'pass', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = EgardiaMapper.toDevices(snapshot);
|
||||
const entities = EgardiaMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(devices[0].integrationDomain).toEqual('egardia');
|
||||
expect(devices[0].manufacturer).toEqual('Egardia');
|
||||
expect(entities.length).toEqual(3);
|
||||
expect(entities[0].state).toEqual('armed_away');
|
||||
expect(entities[1].platform).toEqual('binary_sensor');
|
||||
expect(entities[1].attributes?.deviceClass).toEqual('opening');
|
||||
});
|
||||
|
||||
tap.test('exposes Egardia runtime, HA alias, and explicit unsupported control without executor', async () => {
|
||||
expect(new HomeAssistantEgardiaIntegration().domain).toEqual('egardia');
|
||||
expect(egardiaProfile.status).toEqual('control-runtime');
|
||||
expect(egardiaProfile.metadata.configFlow).toEqual(false);
|
||||
expect(egardiaProfile.metadata.requirements).toEqual(['pythonegardia==1.0.52']);
|
||||
|
||||
const runtime = await new EgardiaIntegration().setup({ name: 'House Alarm', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'egardia', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEgardiaSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('House Alarm');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'alarm_control_panel', service: 'alarm_arm_away', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
expect(controlCommand.error?.includes('requires an injected client.execute() or commandExecutor')).toBeTrue();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,71 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EgaugeClient, EgaugeConfigFlow, EgaugeIntegration, EgaugeMapper, HomeAssistantEgaugeIntegration, createEgaugeDiscoveryDescriptor, egaugeProfile, type IEgaugeSnapshot } from '../../ts/integrations/egauge/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'egauge-12345',
|
||||
name: 'eGauge Main Panel',
|
||||
manufacturer: 'eGauge Systems',
|
||||
model: 'eGauge Energy Monitor',
|
||||
serialNumber: '12345',
|
||||
host: 'egauge.local',
|
||||
},
|
||||
entities: [
|
||||
{ id: 'main_power_power', name: 'Main Power', platform: 'sensor', state: 1234, unit: 'W', deviceClass: 'power', stateClass: 'measurement' },
|
||||
{ id: 'main_power_energy', name: 'Main Energy', platform: 'sensor', state: 987654, unit: 'J', deviceClass: 'energy', stateClass: 'total_increasing' },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual eGauge candidates and creates config flow output', async () => {
|
||||
const descriptor = createEgaugeDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'egauge-manual-match');
|
||||
const result = await matcher!.matches({ host: 'egauge.local', name: 'eGauge Main Panel', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('egauge');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EgaugeConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('egauge.local');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
});
|
||||
|
||||
tap.test('maps eGauge raw snapshots to devices and sensor entities', async () => {
|
||||
const client = new EgaugeClient({ name: 'eGauge Runtime', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = EgaugeMapper.toDevices(snapshot);
|
||||
const entities = EgaugeMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('egauge');
|
||||
expect(devices[0].manufacturer).toEqual('eGauge Systems');
|
||||
expect(entities.length).toEqual(2);
|
||||
expect(entities[0].platform).toEqual('sensor');
|
||||
});
|
||||
|
||||
tap.test('exposes eGauge read-only runtime, HA alias, and unsupported control without executor', async () => {
|
||||
expect(new HomeAssistantEgaugeIntegration().domain).toEqual('egauge');
|
||||
expect(new HomeAssistantEgaugeIntegration().status).toEqual('read-only-runtime');
|
||||
expect(egaugeProfile.metadata.qualityScale).toEqual('bronze');
|
||||
expect(egaugeProfile.metadata.requirements).toEqual(['egauge-async==0.4.0']);
|
||||
|
||||
const runtime = await new EgaugeIntegration().setup({ name: 'eGauge Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'egauge', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEgaugeSnapshot;
|
||||
const refresh = await runtime.callService!({ domain: 'egauge', service: 'refresh', target: {} });
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(refresh.success).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('eGauge Main Panel');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'egauge', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,76 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EheimdigitalClient, EheimdigitalConfigFlow, EheimdigitalIntegration, EheimdigitalMapper, HomeAssistantEheimdigitalIntegration, createEheimdigitalDiscoveryDescriptor, eheimdigitalProfile, type IEheimdigitalSnapshot } from '../../ts/integrations/eheimdigital/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'eheim-hub-aa-bb-cc',
|
||||
name: 'EHEIM Aquarium',
|
||||
manufacturer: 'EHEIM',
|
||||
model: 'EHEIM Digital Hub',
|
||||
serialNumber: 'AA:BB:CC:DD:EE:FF',
|
||||
host: 'eheimdigital.local',
|
||||
},
|
||||
entities: [
|
||||
{ id: 'filter_active', name: 'Filter Active', platform: 'switch', state: true, writable: true },
|
||||
{ id: 'current_speed', name: 'Current Speed', platform: 'sensor', state: 42, unit: 'Hz', deviceClass: 'frequency' },
|
||||
{ id: 'manual_speed', name: 'Manual Speed', platform: 'select', state: '50', writable: true },
|
||||
{ id: 'system_led', name: 'System LED', platform: 'number', state: 75, unit: '%', writable: true },
|
||||
{ id: 'heater', name: 'Heater', platform: 'climate', state: { currentTemperature: 24.7, targetTemperature: 25, hvacMode: 'auto' }, writable: true },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual EHEIM Digital candidates and creates config flow output', async () => {
|
||||
const descriptor = createEheimdigitalDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'eheimdigital-manual-match');
|
||||
const result = await matcher!.matches({ host: 'eheimdigital.local', name: 'EHEIM Aquarium', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('eheimdigital');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EheimdigitalConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('eheimdigital.local');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
});
|
||||
|
||||
tap.test('maps EHEIM Digital raw snapshots to devices and mixed entities', async () => {
|
||||
const client = new EheimdigitalClient({ name: 'EHEIM Runtime', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = EheimdigitalMapper.toDevices(snapshot);
|
||||
const entities = EheimdigitalMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('eheimdigital');
|
||||
expect(devices[0].manufacturer).toEqual('EHEIM');
|
||||
expect(entities.length).toEqual(5);
|
||||
expect(entities.some((entityArg) => entityArg.platform === 'switch')).toBeTrue();
|
||||
expect(entities.some((entityArg) => entityArg.platform === 'climate')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('exposes EHEIM Digital runtime services, HA alias, and unsupported control without executor', async () => {
|
||||
expect(new HomeAssistantEheimdigitalIntegration().domain).toEqual('eheimdigital');
|
||||
expect(new HomeAssistantEheimdigitalIntegration().status).toEqual('control-runtime');
|
||||
expect(eheimdigitalProfile.metadata.qualityScale).toEqual('platinum');
|
||||
expect(eheimdigitalProfile.metadata.requirements).toEqual(['eheimdigital==1.6.0']);
|
||||
expect(eheimdigitalProfile.metadata.configFlow).toBeTrue();
|
||||
|
||||
const runtime = await new EheimdigitalIntegration().setup({ name: 'EHEIM Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'eheimdigital', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEheimdigitalSnapshot;
|
||||
const snapshotService = await runtime.callService!({ domain: 'eheimdigital', service: 'snapshot', target: {} });
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshotService.success).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('EHEIM Aquarium');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'switch', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,70 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EkeybionyxClient, EkeybionyxConfigFlow, EkeybionyxIntegration, EkeybionyxMapper, HomeAssistantEkeybionyxIntegration, createEkeybionyxDiscoveryDescriptor, ekeybionyxProfile, type IEkeybionyxSnapshot } from '../../ts/integrations/ekeybionyx/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'ekey-system-front-door',
|
||||
name: 'Front Door ekey bionyx',
|
||||
manufacturer: 'ekey',
|
||||
model: 'bionyx',
|
||||
},
|
||||
entities: [
|
||||
{ id: 'front_door_event', name: 'Front Door Event', platform: 'sensor', state: 'event happened', attributes: { webhookId: 'webhook-front-door', ekeyId: 'ekey-front-door' } },
|
||||
{ id: 'configured_webhooks', name: 'Configured Webhooks', platform: 'sensor', state: 1 },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual ekey bionyx candidates and creates config flow output', async () => {
|
||||
const descriptor = createEkeybionyxDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'ekeybionyx-manual-match');
|
||||
const result = await matcher!.matches({ name: 'ekey bionyx', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('ekeybionyx');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EkeybionyxConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
expect(done.config?.transport).toEqual('snapshot');
|
||||
});
|
||||
|
||||
tap.test('maps ekey bionyx local push snapshots to devices and entities', async () => {
|
||||
const client = new EkeybionyxClient({ name: 'ekey Runtime', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = EkeybionyxMapper.toDevices(snapshot);
|
||||
const entities = EkeybionyxMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('ekeybionyx');
|
||||
expect(devices[0].manufacturer).toEqual('ekey');
|
||||
expect(entities.length).toEqual(2);
|
||||
expect(entities[0].state).toEqual('event happened');
|
||||
});
|
||||
|
||||
tap.test('exposes ekey bionyx read-only runtime, HA alias, and unsupported control without executor', async () => {
|
||||
expect(new HomeAssistantEkeybionyxIntegration().domain).toEqual('ekeybionyx');
|
||||
expect(new HomeAssistantEkeybionyxIntegration().status).toEqual('read-only-runtime');
|
||||
expect(ekeybionyxProfile.metadata.qualityScale).toEqual('bronze');
|
||||
expect(ekeybionyxProfile.metadata.requirements).toEqual(['ekey-bionyxpy==1.0.1']);
|
||||
expect(ekeybionyxProfile.metadata.dependencies).toEqual(['application_credentials', 'http']);
|
||||
|
||||
const runtime = await new EkeybionyxIntegration().setup({ name: 'ekey Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'ekeybionyx', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEkeybionyxSnapshot;
|
||||
const refresh = await runtime.callService!({ domain: 'ekeybionyx', service: 'refresh', target: {} });
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(refresh.success).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Front Door ekey bionyx');
|
||||
|
||||
const controlCommand = await runtime.callService!({ domain: 'ekeybionyx', service: 'turn_on', target: {} });
|
||||
expect(controlCommand.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,68 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { Elkm1Client, Elkm1ConfigFlow, Elkm1Integration, Elkm1Mapper, HomeAssistantElkm1Integration, createElkm1DiscoveryDescriptor, elkm1Profile, type IElkm1Snapshot } from '../../ts/integrations/elkm1/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'elk-panel-1',
|
||||
name: 'ElkM1 Test Panel',
|
||||
serialNumber: '00409D123456',
|
||||
},
|
||||
entities: [
|
||||
{ id: 'area_1', name: 'Area 1 Armed', platform: 'binary_sensor', state: false },
|
||||
{ id: 'output_1', name: 'Output 1', platform: 'switch', state: true },
|
||||
{ id: 'thermostat_temperature', name: 'Thermostat Temperature', platform: 'sensor', state: 21.5, unit: 'C' },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual Elk-M1 candidates and creates config flow output', async () => {
|
||||
const descriptor = createElkm1DiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'elkm1-manual-match');
|
||||
const result = await matcher!.matches({ host: 'elk-panel.local', port: 2601, name: 'Elk-M1 Control', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('elkm1');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new Elkm1ConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('elk-panel.local');
|
||||
expect(done.config?.port).toEqual(2601);
|
||||
});
|
||||
|
||||
tap.test('maps Elk-M1 raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new Elkm1Client({ name: 'ElkM1 Runtime', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = Elkm1Mapper.toDevices(snapshot);
|
||||
const entities = Elkm1Mapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('elkm1');
|
||||
expect(devices[0].manufacturer).toEqual('ELK Products, Inc.');
|
||||
expect(entities.length).toEqual(3);
|
||||
});
|
||||
|
||||
tap.test('exposes Elk-M1 runtime services, HA alias, and executor-gated controls', async () => {
|
||||
const integration = new Elkm1Integration();
|
||||
const alias = new HomeAssistantElkm1Integration();
|
||||
expect(alias.domain).toEqual('elkm1');
|
||||
expect(integration.status).toEqual('control-runtime');
|
||||
expect(elkm1Profile.metadata.requirements).toEqual(['elkm1-lib==2.2.13']);
|
||||
expect(elkm1Profile.metadata.dependencies).toEqual(['network']);
|
||||
|
||||
const runtime = await integration.setup({ name: 'ElkM1 Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'elkm1', service: 'status', target: {} });
|
||||
const snapshot = status.data as IElkm1Snapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('ElkM1 Test Panel');
|
||||
|
||||
const command = await runtime.callService!({ domain: 'alarm_control_panel', service: 'alarm_arm_away', target: {}, data: { code: '1234' } });
|
||||
expect(command.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,64 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { ElvClient, ElvConfigFlow, ElvIntegration, ElvMapper, HomeAssistantElvIntegration, createElvDiscoveryDescriptor, elvProfile, type IElvSnapshot } from '../../ts/integrations/elv/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'pca-301-1',
|
||||
name: 'PCA 301 Test Plug',
|
||||
},
|
||||
entities: [
|
||||
{ id: 'pca_301_1', name: 'PCA 301 1', platform: 'switch', state: true },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual ELV PCA candidates and creates config flow output', async () => {
|
||||
const descriptor = createElvDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'elv-manual-match');
|
||||
const result = await matcher!.matches({ name: 'ELV PCA 301', metadata: { rawData, device: '/dev/ttyUSB0' } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('elv');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new ElvConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.rawData).toEqual(rawData);
|
||||
});
|
||||
|
||||
tap.test('maps ELV PCA raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new ElvClient({ name: 'ELV Runtime', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = ElvMapper.toDevices(snapshot);
|
||||
const entities = ElvMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('elv');
|
||||
expect(devices[0].manufacturer).toEqual('ELV');
|
||||
expect(entities[0].platform).toEqual('switch');
|
||||
});
|
||||
|
||||
tap.test('exposes ELV runtime services, HA alias, and executor-gated controls', async () => {
|
||||
const integration = new ElvIntegration();
|
||||
const alias = new HomeAssistantElvIntegration();
|
||||
expect(alias.domain).toEqual('elv');
|
||||
expect(integration.status).toEqual('control-runtime');
|
||||
expect(elvProfile.metadata.qualityScale).toEqual('legacy');
|
||||
expect(elvProfile.metadata.requirements).toEqual(['pypca==0.0.7']);
|
||||
|
||||
const runtime = await integration.setup({ name: 'ELV Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'elv', service: 'status', target: {} });
|
||||
const snapshot = status.data as IElvSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('PCA 301 Test Plug');
|
||||
|
||||
const command = await runtime.callService!({ domain: 'switch', service: 'turn_on', target: {} });
|
||||
expect(command.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,66 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EmoncmsClient, EmoncmsConfigFlow, EmoncmsIntegration, EmoncmsMapper, HomeAssistantEmoncmsIntegration, createEmoncmsDiscoveryDescriptor, emoncmsProfile, type IEmoncmsSnapshot } from '../../ts/integrations/emoncms/index.js';
|
||||
|
||||
const rawData = {
|
||||
device: {
|
||||
id: 'emoncms-local',
|
||||
name: 'Emoncms Local',
|
||||
},
|
||||
entities: [
|
||||
{ id: 'feed_1_power', name: 'Feed 1 Power', platform: 'sensor', state: 421.2, unit: 'W' },
|
||||
{ id: 'feed_2_energy', name: 'Feed 2 Energy', platform: 'sensor', state: 18.4, unit: 'kWh' },
|
||||
],
|
||||
};
|
||||
|
||||
tap.test('matches manual Emoncms candidates and creates config flow output', async () => {
|
||||
const descriptor = createEmoncmsDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'emoncms-manual-match');
|
||||
const result = await matcher!.matches({ host: 'emoncms.local', name: 'Emoncms', metadata: { rawData } }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('emoncms');
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
|
||||
const done = await (await new EmoncmsConfigFlow().start(result.candidate!, {})).submit!({});
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('emoncms.local');
|
||||
expect(done.config?.path).toEqual('/feed/list.json');
|
||||
});
|
||||
|
||||
tap.test('maps Emoncms raw snapshots to runtime devices and entities', async () => {
|
||||
const client = new EmoncmsClient({ name: 'Emoncms Runtime', rawData });
|
||||
const snapshot = await client.getSnapshot();
|
||||
const devices = EmoncmsMapper.toDevices(snapshot);
|
||||
const entities = EmoncmsMapper.toEntities(snapshot);
|
||||
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.source).toEqual('manual');
|
||||
expect(devices[0].integrationDomain).toEqual('emoncms');
|
||||
expect(devices[0].manufacturer).toEqual('OpenEnergyMonitor');
|
||||
expect(entities.length).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('exposes Emoncms read-only runtime, HA alias, and unsupported control', async () => {
|
||||
const integration = new EmoncmsIntegration();
|
||||
const alias = new HomeAssistantEmoncmsIntegration();
|
||||
expect(alias.domain).toEqual('emoncms');
|
||||
expect(integration.status).toEqual('read-only-runtime');
|
||||
expect(emoncmsProfile.metadata.configFlow).toEqual(true);
|
||||
expect(emoncmsProfile.metadata.requirements).toEqual(['pyemoncms==0.1.3']);
|
||||
|
||||
const runtime = await integration.setup({ name: 'Emoncms Runtime', rawData }, {});
|
||||
const status = await runtime.callService!({ domain: 'emoncms', service: 'status', target: {} });
|
||||
const snapshot = status.data as IEmoncmsSnapshot;
|
||||
|
||||
expect(status.success).toBeTrue();
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect((await runtime.devices())[0].name).toEqual('Emoncms Local');
|
||||
|
||||
const command = await runtime.callService!({ domain: 'emoncms', service: 'turn_on', target: {} });
|
||||
expect(command.success).toBeFalse();
|
||||
await runtime.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
+60
@@ -85,22 +85,52 @@ import { Concord232Integration } from './integrations/concord232/index.js';
|
||||
import { Control4Integration } from './integrations/control4/index.js';
|
||||
import { CoolmasterIntegration } from './integrations/coolmaster/index.js';
|
||||
import { CppmTrackerIntegration } from './integrations/cppm_tracker/index.js';
|
||||
import { CpuspeedIntegration } from './integrations/cpuspeed/index.js';
|
||||
import { DanfossAirIntegration } from './integrations/danfoss_air/index.js';
|
||||
import { DaikinIntegration } from './integrations/daikin/index.js';
|
||||
import { DdwrtIntegration } from './integrations/ddwrt/index.js';
|
||||
import { DeakoIntegration } from './integrations/deako/index.js';
|
||||
import { DeconzIntegration } from './integrations/deconz/index.js';
|
||||
import { DelugeIntegration } from './integrations/deluge/index.js';
|
||||
import { DenonIntegration } from './integrations/denon/index.js';
|
||||
import { DenonRs232Integration } from './integrations/denon_rs232/index.js';
|
||||
import { DenonavrIntegration } from './integrations/denonavr/index.js';
|
||||
import { DevialetIntegration } from './integrations/devialet/index.js';
|
||||
import { DevoloHomeControlIntegration } from './integrations/devolo_home_control/index.js';
|
||||
import { DevoloHomeNetworkIntegration } from './integrations/devolo_home_network/index.js';
|
||||
import { DirectvIntegration } from './integrations/directv/index.js';
|
||||
import { DlinkIntegration } from './integrations/dlink/index.js';
|
||||
import { DlnaDmrIntegration } from './integrations/dlna_dmr/index.js';
|
||||
import { DlnaDmsIntegration } from './integrations/dlna_dms/index.js';
|
||||
import { DoodsIntegration } from './integrations/doods/index.js';
|
||||
import { DoorbirdIntegration } from './integrations/doorbird/index.js';
|
||||
import { DormakabaDkeyIntegration } from './integrations/dormakaba_dkey/index.js';
|
||||
import { DovadoIntegration } from './integrations/dovado/index.js';
|
||||
import { Dremel3dPrinterIntegration } from './integrations/dremel_3d_printer/index.js';
|
||||
import { DropConnectIntegration } from './integrations/drop_connect/index.js';
|
||||
import { DropletIntegration } from './integrations/droplet/index.js';
|
||||
import { DsmrIntegration } from './integrations/dsmr/index.js';
|
||||
import { DsmrReaderIntegration } from './integrations/dsmr_reader/index.js';
|
||||
import { DucoIntegration } from './integrations/duco/index.js';
|
||||
import { DuotecnoIntegration } from './integrations/duotecno/index.js';
|
||||
import { DunehdIntegration } from './integrations/dunehd/index.js';
|
||||
import { DynaliteIntegration } from './integrations/dynalite/index.js';
|
||||
import { EarnEP1Integration } from './integrations/earn_e_p1/index.js';
|
||||
import { EbusdIntegration } from './integrations/ebusd/index.js';
|
||||
import { EcoalBoilerIntegration } from './integrations/ecoal_boiler/index.js';
|
||||
import { EcoforestIntegration } from './integrations/ecoforest/index.js';
|
||||
import { EcowittIntegration } from './integrations/ecowitt/index.js';
|
||||
import { EdimaxIntegration } from './integrations/edimax/index.js';
|
||||
import { Edl21Integration } from './integrations/edl21/index.js';
|
||||
import { EgardiaIntegration } from './integrations/egardia/index.js';
|
||||
import { EgaugeIntegration } from './integrations/egauge/index.js';
|
||||
import { EheimdigitalIntegration } from './integrations/eheimdigital/index.js';
|
||||
import { EkeybionyxIntegration } from './integrations/ekeybionyx/index.js';
|
||||
import { ElgatoIntegration } from './integrations/elgato/index.js';
|
||||
import { Elkm1Integration } from './integrations/elkm1/index.js';
|
||||
import { ElvIntegration } from './integrations/elv/index.js';
|
||||
import { EmbyIntegration } from './integrations/emby/index.js';
|
||||
import { EmoncmsIntegration } from './integrations/emoncms/index.js';
|
||||
import { EsphomeIntegration } from './integrations/esphome/index.js';
|
||||
import { ForkedDaapdIntegration } from './integrations/forked_daapd/index.js';
|
||||
import { FoscamIntegration } from './integrations/foscam/index.js';
|
||||
@@ -286,22 +316,52 @@ export const integrations = [
|
||||
new Control4Integration(),
|
||||
new CoolmasterIntegration(),
|
||||
new CppmTrackerIntegration(),
|
||||
new CpuspeedIntegration(),
|
||||
new DanfossAirIntegration(),
|
||||
new DaikinIntegration(),
|
||||
new DdwrtIntegration(),
|
||||
new DeakoIntegration(),
|
||||
new DeconzIntegration(),
|
||||
new DelugeIntegration(),
|
||||
new DenonIntegration(),
|
||||
new DenonRs232Integration(),
|
||||
new DenonavrIntegration(),
|
||||
new DevialetIntegration(),
|
||||
new DevoloHomeControlIntegration(),
|
||||
new DevoloHomeNetworkIntegration(),
|
||||
new DirectvIntegration(),
|
||||
new DlinkIntegration(),
|
||||
new DlnaDmrIntegration(),
|
||||
new DlnaDmsIntegration(),
|
||||
new DoodsIntegration(),
|
||||
new DoorbirdIntegration(),
|
||||
new DormakabaDkeyIntegration(),
|
||||
new DovadoIntegration(),
|
||||
new Dremel3dPrinterIntegration(),
|
||||
new DropConnectIntegration(),
|
||||
new DropletIntegration(),
|
||||
new DsmrIntegration(),
|
||||
new DsmrReaderIntegration(),
|
||||
new DucoIntegration(),
|
||||
new DuotecnoIntegration(),
|
||||
new DunehdIntegration(),
|
||||
new DynaliteIntegration(),
|
||||
new EarnEP1Integration(),
|
||||
new EbusdIntegration(),
|
||||
new EcoalBoilerIntegration(),
|
||||
new EcoforestIntegration(),
|
||||
new EcowittIntegration(),
|
||||
new EdimaxIntegration(),
|
||||
new Edl21Integration(),
|
||||
new EgardiaIntegration(),
|
||||
new EgaugeIntegration(),
|
||||
new EheimdigitalIntegration(),
|
||||
new EkeybionyxIntegration(),
|
||||
new ElgatoIntegration(),
|
||||
new Elkm1Integration(),
|
||||
new ElvIntegration(),
|
||||
new EmbyIntegration(),
|
||||
new EmoncmsIntegration(),
|
||||
new EsphomeIntegration(),
|
||||
new ForkedDaapdIntegration(),
|
||||
new FoscamIntegration(),
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalClient } from '../../core/index.js';
|
||||
import type { ICpuspeedConfig } from './cpuspeed.types.js';
|
||||
import { cpuspeedProfile } from './cpuspeed.types.js';
|
||||
|
||||
export class CpuspeedClient extends SimpleLocalClient<ICpuspeedConfig> {
|
||||
constructor(configArg: ICpuspeedConfig) {
|
||||
super(cpuspeedProfile, configArg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalConfigFlow } from '../../core/index.js';
|
||||
import type { ICpuspeedConfig } from './cpuspeed.types.js';
|
||||
import { cpuspeedProfile } from './cpuspeed.types.js';
|
||||
|
||||
export class CpuspeedConfigFlow extends SimpleLocalConfigFlow<ICpuspeedConfig> {
|
||||
constructor() {
|
||||
super(cpuspeedProfile);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,17 @@
|
||||
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
|
||||
import { SimpleLocalIntegration } from '../../core/index.js';
|
||||
import { CpuspeedConfigFlow } from './cpuspeed.classes.configflow.js';
|
||||
import { createCpuspeedDiscoveryDescriptor } from './cpuspeed.discovery.js';
|
||||
import type { ICpuspeedConfig } from './cpuspeed.types.js';
|
||||
import { cpuspeedDomain, cpuspeedProfile } from './cpuspeed.types.js';
|
||||
|
||||
export class CpuspeedIntegration extends SimpleLocalIntegration<ICpuspeedConfig> {
|
||||
public readonly domain = cpuspeedDomain;
|
||||
public readonly discoveryDescriptor = createCpuspeedDiscoveryDescriptor();
|
||||
public readonly configFlow = new CpuspeedConfigFlow();
|
||||
|
||||
export class HomeAssistantCpuspeedIntegration extends DescriptorOnlyIntegration {
|
||||
constructor() {
|
||||
super({
|
||||
domain: "cpuspeed",
|
||||
displayName: "CPU Speed",
|
||||
status: 'descriptor-only',
|
||||
metadata: {
|
||||
"source": "home-assistant/core",
|
||||
"upstreamPath": "homeassistant/components/cpuspeed",
|
||||
"upstreamDomain": "cpuspeed",
|
||||
"integrationType": "device",
|
||||
"iotClass": "local_push",
|
||||
"requirements": [
|
||||
"py-cpuinfo==9.0.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"afterDependencies": [],
|
||||
"codeowners": [
|
||||
"@fabaff"
|
||||
]
|
||||
},
|
||||
});
|
||||
super(cpuspeedProfile);
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeAssistantCpuspeedIntegration extends CpuspeedIntegration {}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createSimpleLocalDiscoveryDescriptor } from '../../core/index.js';
|
||||
import { cpuspeedProfile } from './cpuspeed.types.js';
|
||||
|
||||
export const createCpuspeedDiscoveryDescriptor = () => createSimpleLocalDiscoveryDescriptor(cpuspeedProfile);
|
||||
@@ -0,0 +1,26 @@
|
||||
import type * as shxInterfaces from '@smarthome.exchange/interfaces';
|
||||
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
|
||||
import type { ICpuspeedConfig } from './cpuspeed.types.js';
|
||||
import { cpuspeedProfile } from './cpuspeed.types.js';
|
||||
|
||||
export class CpuspeedMapper {
|
||||
public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<ICpuspeedConfig>, 'profile'>): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: cpuspeedProfile });
|
||||
}
|
||||
|
||||
public static toSnapshotFromRaw(configArg: ICpuspeedConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ profile: cpuspeedProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
|
||||
}
|
||||
|
||||
public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
|
||||
return SimpleLocalMapper.toDevices(cpuspeedProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
|
||||
return SimpleLocalMapper.toEntities(cpuspeedProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static slug(valueArg: unknown): string {
|
||||
return SimpleLocalMapper.slug(valueArg);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,72 @@
|
||||
export interface IHomeAssistantCpuspeedConfig {
|
||||
// TODO: replace with the TypeScript-native config for cpuspeed.
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { ISimpleLocalConfig, ISimpleLocalIntegrationProfile, ISimpleLocalSnapshot, TSimpleLocalRawData } from '../../core/index.js';
|
||||
|
||||
export const cpuspeedDomain = 'cpuspeed';
|
||||
export const cpuspeedDefaultName = 'CPU Speed';
|
||||
|
||||
export type TCpuspeedRawData = TSimpleLocalRawData;
|
||||
export interface ICpuspeedSnapshot extends ISimpleLocalSnapshot {}
|
||||
export interface ICpuspeedConfig extends ISimpleLocalConfig {}
|
||||
export interface IHomeAssistantCpuspeedConfig extends ICpuspeedConfig {}
|
||||
|
||||
export const cpuspeedProfile: ISimpleLocalIntegrationProfile = {
|
||||
domain: 'cpuspeed',
|
||||
displayName: 'CPU Speed',
|
||||
defaultName: 'CPU Speed',
|
||||
defaultProtocol: 'local',
|
||||
status: 'read-only-runtime',
|
||||
platforms: [
|
||||
'sensor',
|
||||
],
|
||||
serviceDomains: [],
|
||||
controlServices: [],
|
||||
discoverySources: [
|
||||
'manual',
|
||||
'custom',
|
||||
],
|
||||
discoveryKeywords: [
|
||||
'cpu',
|
||||
'cpu speed',
|
||||
'cpuspeed',
|
||||
'processor',
|
||||
],
|
||||
metadata: {
|
||||
source: 'home-assistant/core',
|
||||
upstreamPath: 'homeassistant/components/cpuspeed',
|
||||
upstreamDomain: 'cpuspeed',
|
||||
integrationType: 'device',
|
||||
iotClass: 'local_push',
|
||||
qualityScale: undefined,
|
||||
requirements: [
|
||||
'py-cpuinfo==9.0.0',
|
||||
],
|
||||
dependencies: [],
|
||||
afterDependencies: [],
|
||||
codeowners: [
|
||||
'@fabaff',
|
||||
],
|
||||
configFlow: true,
|
||||
runtime: {
|
||||
type: 'read-only-runtime',
|
||||
services: [
|
||||
'snapshot',
|
||||
'status',
|
||||
'refresh',
|
||||
],
|
||||
platforms: [
|
||||
'sensor',
|
||||
],
|
||||
controls: false,
|
||||
},
|
||||
localApi: {
|
||||
implemented: [
|
||||
'manual local CPU information setup',
|
||||
'snapshot, raw data, snapshotProvider, and injected native client operation',
|
||||
],
|
||||
explicitUnsupported: [
|
||||
'claiming live command success without injected client.execute or commandExecutor',
|
||||
'remote API polling',
|
||||
'device-specific protocol features not represented by snapshot/rawData/client inputs',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
export * from './cpuspeed.classes.client.js';
|
||||
export * from './cpuspeed.classes.configflow.js';
|
||||
export * from './cpuspeed.classes.integration.js';
|
||||
export * from './cpuspeed.discovery.js';
|
||||
export * from './cpuspeed.mapper.js';
|
||||
export * from './cpuspeed.types.js';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalClient } from '../../core/index.js';
|
||||
import type { IDanfossAirConfig } from './danfoss_air.types.js';
|
||||
import { danfossAirProfile } from './danfoss_air.types.js';
|
||||
|
||||
export class DanfossAirClient extends SimpleLocalClient<IDanfossAirConfig> {
|
||||
constructor(configArg: IDanfossAirConfig) {
|
||||
super(danfossAirProfile, configArg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalConfigFlow } from '../../core/index.js';
|
||||
import type { IDanfossAirConfig } from './danfoss_air.types.js';
|
||||
import { danfossAirProfile } from './danfoss_air.types.js';
|
||||
|
||||
export class DanfossAirConfigFlow extends SimpleLocalConfigFlow<IDanfossAirConfig> {
|
||||
constructor() {
|
||||
super(danfossAirProfile);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,17 @@
|
||||
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
|
||||
import { SimpleLocalIntegration } from '../../core/index.js';
|
||||
import { DanfossAirConfigFlow } from './danfoss_air.classes.configflow.js';
|
||||
import { createDanfossAirDiscoveryDescriptor } from './danfoss_air.discovery.js';
|
||||
import type { IDanfossAirConfig } from './danfoss_air.types.js';
|
||||
import { danfossAirDomain, danfossAirProfile } from './danfoss_air.types.js';
|
||||
|
||||
export class DanfossAirIntegration extends SimpleLocalIntegration<IDanfossAirConfig> {
|
||||
public readonly domain = danfossAirDomain;
|
||||
public readonly discoveryDescriptor = createDanfossAirDiscoveryDescriptor();
|
||||
public readonly configFlow = new DanfossAirConfigFlow();
|
||||
|
||||
export class HomeAssistantDanfossAirIntegration extends DescriptorOnlyIntegration {
|
||||
constructor() {
|
||||
super({
|
||||
domain: "danfoss_air",
|
||||
displayName: "Danfoss Air",
|
||||
status: 'descriptor-only',
|
||||
metadata: {
|
||||
"source": "home-assistant/core",
|
||||
"upstreamPath": "homeassistant/components/danfoss_air",
|
||||
"upstreamDomain": "danfoss_air",
|
||||
"iotClass": "local_polling",
|
||||
"qualityScale": "legacy",
|
||||
"requirements": [
|
||||
"pydanfossair==0.1.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"afterDependencies": [],
|
||||
"codeowners": []
|
||||
},
|
||||
});
|
||||
super(danfossAirProfile);
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeAssistantDanfossAirIntegration extends DanfossAirIntegration {}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createSimpleLocalDiscoveryDescriptor } from '../../core/index.js';
|
||||
import { danfossAirProfile } from './danfoss_air.types.js';
|
||||
|
||||
export const createDanfossAirDiscoveryDescriptor = () => createSimpleLocalDiscoveryDescriptor(danfossAirProfile);
|
||||
@@ -0,0 +1,26 @@
|
||||
import type * as shxInterfaces from '@smarthome.exchange/interfaces';
|
||||
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
|
||||
import type { IDanfossAirConfig } from './danfoss_air.types.js';
|
||||
import { danfossAirProfile } from './danfoss_air.types.js';
|
||||
|
||||
export class DanfossAirMapper {
|
||||
public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IDanfossAirConfig>, 'profile'>): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: danfossAirProfile });
|
||||
}
|
||||
|
||||
public static toSnapshotFromRaw(configArg: IDanfossAirConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ profile: danfossAirProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
|
||||
}
|
||||
|
||||
public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
|
||||
return SimpleLocalMapper.toDevices(danfossAirProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
|
||||
return SimpleLocalMapper.toEntities(danfossAirProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static slug(valueArg: unknown): string {
|
||||
return SimpleLocalMapper.slug(valueArg);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,86 @@
|
||||
export interface IHomeAssistantDanfossAirConfig {
|
||||
// TODO: replace with the TypeScript-native config for danfoss_air.
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { ISimpleLocalConfig, ISimpleLocalIntegrationProfile, ISimpleLocalSnapshot, TSimpleLocalRawData } from '../../core/index.js';
|
||||
|
||||
export const danfossAirDomain = 'danfoss_air';
|
||||
export const danfossAirDefaultName = 'Danfoss Air';
|
||||
|
||||
export type TDanfossAirRawData = TSimpleLocalRawData;
|
||||
export interface IDanfossAirSnapshot extends ISimpleLocalSnapshot {}
|
||||
export interface IDanfossAirConfig extends ISimpleLocalConfig {}
|
||||
export interface IHomeAssistantDanfossAirConfig extends IDanfossAirConfig {}
|
||||
|
||||
export const danfossAirProfile: ISimpleLocalIntegrationProfile = {
|
||||
domain: 'danfoss_air',
|
||||
displayName: 'Danfoss Air',
|
||||
manufacturer: 'Danfoss',
|
||||
model: 'Air CCM',
|
||||
defaultName: 'Danfoss Air',
|
||||
defaultProtocol: 'local',
|
||||
status: 'control-runtime',
|
||||
platforms: [
|
||||
'binary_sensor',
|
||||
'sensor',
|
||||
'switch',
|
||||
],
|
||||
serviceDomains: [
|
||||
'switch',
|
||||
],
|
||||
controlServices: [
|
||||
'turn_on',
|
||||
'turn_off',
|
||||
'toggle',
|
||||
],
|
||||
discoverySources: [
|
||||
'manual',
|
||||
'custom',
|
||||
],
|
||||
discoveryKeywords: [
|
||||
'danfoss',
|
||||
'danfoss air',
|
||||
'air ccm',
|
||||
'hrv',
|
||||
'ventilation',
|
||||
],
|
||||
metadata: {
|
||||
source: 'home-assistant/core',
|
||||
upstreamPath: 'homeassistant/components/danfoss_air',
|
||||
upstreamDomain: 'danfoss_air',
|
||||
iotClass: 'local_polling',
|
||||
qualityScale: 'legacy',
|
||||
requirements: [
|
||||
'pydanfossair==0.1.0',
|
||||
],
|
||||
dependencies: [],
|
||||
afterDependencies: [],
|
||||
codeowners: [],
|
||||
configFlow: false,
|
||||
runtime: {
|
||||
type: 'control-runtime',
|
||||
services: [
|
||||
'snapshot',
|
||||
'status',
|
||||
'refresh',
|
||||
'turn_on',
|
||||
'turn_off',
|
||||
'toggle',
|
||||
],
|
||||
platforms: [
|
||||
'binary_sensor',
|
||||
'sensor',
|
||||
'switch',
|
||||
],
|
||||
controls: true,
|
||||
},
|
||||
localApi: {
|
||||
implemented: [
|
||||
'manual local host setup for Danfoss Air CCM units',
|
||||
'snapshot, raw data, snapshotProvider, and injected native client operation',
|
||||
'delegated switch control through injected client.execute or commandExecutor',
|
||||
],
|
||||
explicitUnsupported: [
|
||||
'claiming live switch command success without injected client.execute or commandExecutor',
|
||||
'cloud account flows and remote API polling',
|
||||
'device-specific Danfoss protocol commands not represented by snapshot/rawData/client/executor inputs',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
export * from './danfoss_air.classes.client.js';
|
||||
export * from './danfoss_air.classes.configflow.js';
|
||||
export * from './danfoss_air.classes.integration.js';
|
||||
export * from './danfoss_air.discovery.js';
|
||||
export * from './danfoss_air.mapper.js';
|
||||
export * from './danfoss_air.types.js';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalClient } from '../../core/index.js';
|
||||
import type { IDeakoConfig } from './deako.types.js';
|
||||
import { deakoProfile } from './deako.types.js';
|
||||
|
||||
export class DeakoClient extends SimpleLocalClient<IDeakoConfig> {
|
||||
constructor(configArg: IDeakoConfig) {
|
||||
super(deakoProfile, configArg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalConfigFlow } from '../../core/index.js';
|
||||
import type { IDeakoConfig } from './deako.types.js';
|
||||
import { deakoProfile } from './deako.types.js';
|
||||
|
||||
export class DeakoConfigFlow extends SimpleLocalConfigFlow<IDeakoConfig> {
|
||||
constructor() {
|
||||
super(deakoProfile);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,17 @@
|
||||
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
|
||||
import { SimpleLocalIntegration } from '../../core/index.js';
|
||||
import { DeakoConfigFlow } from './deako.classes.configflow.js';
|
||||
import { createDeakoDiscoveryDescriptor } from './deako.discovery.js';
|
||||
import type { IDeakoConfig } from './deako.types.js';
|
||||
import { deakoDomain, deakoProfile } from './deako.types.js';
|
||||
|
||||
export class DeakoIntegration extends SimpleLocalIntegration<IDeakoConfig> {
|
||||
public readonly domain = deakoDomain;
|
||||
public readonly discoveryDescriptor = createDeakoDiscoveryDescriptor();
|
||||
public readonly configFlow = new DeakoConfigFlow();
|
||||
|
||||
export class HomeAssistantDeakoIntegration extends DescriptorOnlyIntegration {
|
||||
constructor() {
|
||||
super({
|
||||
domain: "deako",
|
||||
displayName: "Deako",
|
||||
status: 'descriptor-only',
|
||||
metadata: {
|
||||
"source": "home-assistant/core",
|
||||
"upstreamPath": "homeassistant/components/deako",
|
||||
"upstreamDomain": "deako",
|
||||
"iotClass": "local_polling",
|
||||
"requirements": [
|
||||
"pydeako==0.6.0"
|
||||
],
|
||||
"dependencies": [
|
||||
"zeroconf"
|
||||
],
|
||||
"afterDependencies": [],
|
||||
"codeowners": [
|
||||
"@sebirdman",
|
||||
"@balake",
|
||||
"@deakolights"
|
||||
]
|
||||
},
|
||||
});
|
||||
super(deakoProfile);
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeAssistantDeakoIntegration extends DeakoIntegration {}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createSimpleLocalDiscoveryDescriptor } from '../../core/index.js';
|
||||
import { deakoProfile } from './deako.types.js';
|
||||
|
||||
export const createDeakoDiscoveryDescriptor = () => createSimpleLocalDiscoveryDescriptor(deakoProfile);
|
||||
@@ -0,0 +1,26 @@
|
||||
import type * as shxInterfaces from '@smarthome.exchange/interfaces';
|
||||
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
|
||||
import type { IDeakoConfig } from './deako.types.js';
|
||||
import { deakoProfile } from './deako.types.js';
|
||||
|
||||
export class DeakoMapper {
|
||||
public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IDeakoConfig>, 'profile'>): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: deakoProfile });
|
||||
}
|
||||
|
||||
public static toSnapshotFromRaw(configArg: IDeakoConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ profile: deakoProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
|
||||
}
|
||||
|
||||
public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
|
||||
return SimpleLocalMapper.toDevices(deakoProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
|
||||
return SimpleLocalMapper.toEntities(deakoProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static slug(valueArg: unknown): string {
|
||||
return SimpleLocalMapper.slug(valueArg);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,94 @@
|
||||
export interface IHomeAssistantDeakoConfig {
|
||||
// TODO: replace with the TypeScript-native config for deako.
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { ISimpleLocalConfig, ISimpleLocalIntegrationProfile, ISimpleLocalSnapshot, TSimpleLocalRawData } from '../../core/index.js';
|
||||
|
||||
export const deakoDomain = 'deako';
|
||||
export const deakoDefaultName = 'Deako';
|
||||
|
||||
export type TDeakoRawData = TSimpleLocalRawData;
|
||||
export interface IDeakoSnapshot extends ISimpleLocalSnapshot {}
|
||||
export interface IDeakoConfig extends ISimpleLocalConfig {}
|
||||
export interface IHomeAssistantDeakoConfig extends IDeakoConfig {}
|
||||
|
||||
export const deakoProfile: ISimpleLocalIntegrationProfile = {
|
||||
domain: 'deako',
|
||||
displayName: 'Deako',
|
||||
manufacturer: 'Deako',
|
||||
model: 'Smart Lighting',
|
||||
defaultName: 'Deako',
|
||||
defaultProtocol: 'local',
|
||||
status: 'control-runtime',
|
||||
platforms: [
|
||||
'light',
|
||||
],
|
||||
serviceDomains: [
|
||||
'light',
|
||||
],
|
||||
controlServices: [
|
||||
'turn_on',
|
||||
'turn_off',
|
||||
'toggle',
|
||||
'set_level',
|
||||
],
|
||||
discoverySources: [
|
||||
'manual',
|
||||
'mdns',
|
||||
'custom',
|
||||
],
|
||||
discoveryKeywords: [
|
||||
'deako',
|
||||
'_deako._tcp.local',
|
||||
'dimmer',
|
||||
'light',
|
||||
'smart lighting',
|
||||
],
|
||||
metadata: {
|
||||
source: 'home-assistant/core',
|
||||
upstreamPath: 'homeassistant/components/deako',
|
||||
upstreamDomain: 'deako',
|
||||
iotClass: 'local_polling',
|
||||
qualityScale: undefined,
|
||||
requirements: [
|
||||
'pydeako==0.6.0',
|
||||
],
|
||||
dependencies: [
|
||||
'zeroconf',
|
||||
],
|
||||
afterDependencies: [],
|
||||
codeowners: [
|
||||
'@sebirdman',
|
||||
'@balake',
|
||||
'@deakolights',
|
||||
],
|
||||
configFlow: true,
|
||||
zeroconf: [
|
||||
'_deako._tcp.local.',
|
||||
],
|
||||
runtime: {
|
||||
type: 'control-runtime',
|
||||
services: [
|
||||
'snapshot',
|
||||
'status',
|
||||
'refresh',
|
||||
'turn_on',
|
||||
'turn_off',
|
||||
'toggle',
|
||||
'set_level',
|
||||
],
|
||||
platforms: [
|
||||
'light',
|
||||
],
|
||||
controls: true,
|
||||
},
|
||||
localApi: {
|
||||
implemented: [
|
||||
'manual and mDNS local setup for Deako devices',
|
||||
'snapshot, raw data, snapshotProvider, and injected native client operation',
|
||||
'delegated light control through injected client.execute or commandExecutor',
|
||||
],
|
||||
explicitUnsupported: [
|
||||
'claiming live light command success without injected client.execute or commandExecutor',
|
||||
'cloud account flows and remote API polling',
|
||||
'device-specific Deako protocol commands not represented by snapshot/rawData/client/executor inputs',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
export * from './deako.classes.client.js';
|
||||
export * from './deako.classes.configflow.js';
|
||||
export * from './deako.classes.integration.js';
|
||||
export * from './deako.discovery.js';
|
||||
export * from './deako.mapper.js';
|
||||
export * from './deako.types.js';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalClient } from '../../core/index.js';
|
||||
import type { IDenonRs232Config } from './denon_rs232.types.js';
|
||||
import { denonRs232Profile } from './denon_rs232.types.js';
|
||||
|
||||
export class DenonRs232Client extends SimpleLocalClient<IDenonRs232Config> {
|
||||
constructor(configArg: IDenonRs232Config) {
|
||||
super(denonRs232Profile, configArg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalConfigFlow } from '../../core/index.js';
|
||||
import type { IDenonRs232Config } from './denon_rs232.types.js';
|
||||
import { denonRs232Profile } from './denon_rs232.types.js';
|
||||
|
||||
export class DenonRs232ConfigFlow extends SimpleLocalConfigFlow<IDenonRs232Config> {
|
||||
constructor() {
|
||||
super(denonRs232Profile);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,17 @@
|
||||
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
|
||||
import { SimpleLocalIntegration } from '../../core/index.js';
|
||||
import { DenonRs232ConfigFlow } from './denon_rs232.classes.configflow.js';
|
||||
import { createDenonRs232DiscoveryDescriptor } from './denon_rs232.discovery.js';
|
||||
import type { IDenonRs232Config } from './denon_rs232.types.js';
|
||||
import { denonRs232Domain, denonRs232Profile } from './denon_rs232.types.js';
|
||||
|
||||
export class DenonRs232Integration extends SimpleLocalIntegration<IDenonRs232Config> {
|
||||
public readonly domain = denonRs232Domain;
|
||||
public readonly discoveryDescriptor = createDenonRs232DiscoveryDescriptor();
|
||||
public readonly configFlow = new DenonRs232ConfigFlow();
|
||||
|
||||
export class HomeAssistantDenonRs232Integration extends DescriptorOnlyIntegration {
|
||||
constructor() {
|
||||
super({
|
||||
domain: "denon_rs232",
|
||||
displayName: "Denon RS232",
|
||||
status: 'descriptor-only',
|
||||
metadata: {
|
||||
"source": "home-assistant/core",
|
||||
"upstreamPath": "homeassistant/components/denon_rs232",
|
||||
"upstreamDomain": "denon_rs232",
|
||||
"integrationType": "hub",
|
||||
"iotClass": "local_push",
|
||||
"qualityScale": "bronze",
|
||||
"requirements": [
|
||||
"denon-rs232==4.1.0"
|
||||
],
|
||||
"dependencies": [
|
||||
"usb"
|
||||
],
|
||||
"afterDependencies": [],
|
||||
"codeowners": [
|
||||
"@balloob"
|
||||
]
|
||||
},
|
||||
});
|
||||
super(denonRs232Profile);
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeAssistantDenonRs232Integration extends DenonRs232Integration {}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createSimpleLocalDiscoveryDescriptor } from '../../core/index.js';
|
||||
import { denonRs232Profile } from './denon_rs232.types.js';
|
||||
|
||||
export const createDenonRs232DiscoveryDescriptor = () => createSimpleLocalDiscoveryDescriptor(denonRs232Profile);
|
||||
@@ -0,0 +1,26 @@
|
||||
import type * as shxInterfaces from '@smarthome.exchange/interfaces';
|
||||
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
|
||||
import type { IDenonRs232Config } from './denon_rs232.types.js';
|
||||
import { denonRs232Profile } from './denon_rs232.types.js';
|
||||
|
||||
export class DenonRs232Mapper {
|
||||
public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IDenonRs232Config>, 'profile'>): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: denonRs232Profile });
|
||||
}
|
||||
|
||||
public static toSnapshotFromRaw(configArg: IDenonRs232Config, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ profile: denonRs232Profile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
|
||||
}
|
||||
|
||||
public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
|
||||
return SimpleLocalMapper.toDevices(denonRs232Profile, snapshotArg);
|
||||
}
|
||||
|
||||
public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
|
||||
return SimpleLocalMapper.toEntities(denonRs232Profile, snapshotArg);
|
||||
}
|
||||
|
||||
public static slug(valueArg: unknown): string {
|
||||
return SimpleLocalMapper.slug(valueArg);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,83 @@
|
||||
export interface IHomeAssistantDenonRs232Config {
|
||||
// TODO: replace with the TypeScript-native config for denon_rs232.
|
||||
[key: string]: unknown;
|
||||
import type { ISimpleLocalConfig, ISimpleLocalIntegrationProfile, ISimpleLocalSnapshot, TSimpleLocalRawData } from '../../core/index.js';
|
||||
|
||||
export const denonRs232Domain = 'denon_rs232';
|
||||
export const denonRs232DefaultName = 'Denon Receiver';
|
||||
|
||||
export type TDenonRs232RawData = TSimpleLocalRawData;
|
||||
export interface IDenonRs232Snapshot extends ISimpleLocalSnapshot {}
|
||||
export interface IDenonRs232Config extends ISimpleLocalConfig {
|
||||
device?: string;
|
||||
model?: string;
|
||||
modelName?: string;
|
||||
}
|
||||
export interface IHomeAssistantDenonRs232Config extends IDenonRs232Config {}
|
||||
|
||||
export const denonRs232Profile: ISimpleLocalIntegrationProfile = {
|
||||
domain: denonRs232Domain,
|
||||
displayName: 'Denon RS232',
|
||||
manufacturer: 'Denon',
|
||||
model: 'RS232 receiver',
|
||||
defaultName: denonRs232DefaultName,
|
||||
defaultProtocol: 'local',
|
||||
status: 'read-only-runtime',
|
||||
platforms: [
|
||||
'media_player',
|
||||
],
|
||||
serviceDomains: [
|
||||
'media_player',
|
||||
],
|
||||
controlServices: [],
|
||||
discoverySources: [
|
||||
'manual',
|
||||
'usb',
|
||||
'custom',
|
||||
],
|
||||
discoveryKeywords: [
|
||||
'denon',
|
||||
'rs232',
|
||||
'receiver',
|
||||
'serial',
|
||||
],
|
||||
metadata: {
|
||||
source: 'home-assistant/core',
|
||||
upstreamPath: 'homeassistant/components/denon_rs232',
|
||||
upstreamDomain: 'denon_rs232',
|
||||
integrationType: 'hub',
|
||||
iotClass: 'local_push',
|
||||
qualityScale: 'bronze',
|
||||
requirements: [
|
||||
'denon-rs232==4.1.0',
|
||||
],
|
||||
dependencies: [
|
||||
'usb',
|
||||
],
|
||||
afterDependencies: [],
|
||||
codeowners: [
|
||||
'@balloob',
|
||||
],
|
||||
configFlow: true,
|
||||
runtime: {
|
||||
type: 'read-only-runtime',
|
||||
services: [
|
||||
'snapshot',
|
||||
'status',
|
||||
'refresh',
|
||||
],
|
||||
platforms: [
|
||||
'media_player',
|
||||
],
|
||||
controls: false,
|
||||
},
|
||||
localApi: {
|
||||
implemented: [
|
||||
'manual local serial endpoint metadata setup',
|
||||
'snapshot, raw data, snapshotProvider, and injected native client operation',
|
||||
'media player service dispatch only through injected client.execute or commandExecutor',
|
||||
],
|
||||
explicitUnsupported: [
|
||||
'opening serial ports directly without a supplied native Denon RS232 client',
|
||||
'claiming live media player command success without injected client.execute or commandExecutor',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
export * from './denon_rs232.classes.client.js';
|
||||
export * from './denon_rs232.classes.configflow.js';
|
||||
export * from './denon_rs232.classes.integration.js';
|
||||
export * from './denon_rs232.discovery.js';
|
||||
export * from './denon_rs232.mapper.js';
|
||||
export * from './denon_rs232.types.js';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalClient } from '../../core/index.js';
|
||||
import type { IDevialetConfig } from './devialet.types.js';
|
||||
import { devialetProfile } from './devialet.types.js';
|
||||
|
||||
export class DevialetClient extends SimpleLocalClient<IDevialetConfig> {
|
||||
constructor(configArg: IDevialetConfig) {
|
||||
super(devialetProfile, configArg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalConfigFlow } from '../../core/index.js';
|
||||
import type { IDevialetConfig } from './devialet.types.js';
|
||||
import { devialetProfile } from './devialet.types.js';
|
||||
|
||||
export class DevialetConfigFlow extends SimpleLocalConfigFlow<IDevialetConfig> {
|
||||
constructor() {
|
||||
super(devialetProfile);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,17 @@
|
||||
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
|
||||
import { SimpleLocalIntegration } from '../../core/index.js';
|
||||
import { DevialetConfigFlow } from './devialet.classes.configflow.js';
|
||||
import { createDevialetDiscoveryDescriptor } from './devialet.discovery.js';
|
||||
import type { IDevialetConfig } from './devialet.types.js';
|
||||
import { devialetDomain, devialetProfile } from './devialet.types.js';
|
||||
|
||||
export class DevialetIntegration extends SimpleLocalIntegration<IDevialetConfig> {
|
||||
public readonly domain = devialetDomain;
|
||||
public readonly discoveryDescriptor = createDevialetDiscoveryDescriptor();
|
||||
public readonly configFlow = new DevialetConfigFlow();
|
||||
|
||||
export class HomeAssistantDevialetIntegration extends DescriptorOnlyIntegration {
|
||||
constructor() {
|
||||
super({
|
||||
domain: "devialet",
|
||||
displayName: "Devialet",
|
||||
status: 'descriptor-only',
|
||||
metadata: {
|
||||
"source": "home-assistant/core",
|
||||
"upstreamPath": "homeassistant/components/devialet",
|
||||
"upstreamDomain": "devialet",
|
||||
"integrationType": "device",
|
||||
"iotClass": "local_polling",
|
||||
"requirements": [
|
||||
"devialet==1.5.7"
|
||||
],
|
||||
"dependencies": [],
|
||||
"afterDependencies": [
|
||||
"zeroconf"
|
||||
],
|
||||
"codeowners": [
|
||||
"@fwestenberg"
|
||||
]
|
||||
},
|
||||
});
|
||||
super(devialetProfile);
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeAssistantDevialetIntegration extends DevialetIntegration {}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createSimpleLocalDiscoveryDescriptor } from '../../core/index.js';
|
||||
import { devialetProfile } from './devialet.types.js';
|
||||
|
||||
export const createDevialetDiscoveryDescriptor = () => createSimpleLocalDiscoveryDescriptor(devialetProfile);
|
||||
@@ -0,0 +1,26 @@
|
||||
import type * as shxInterfaces from '@smarthome.exchange/interfaces';
|
||||
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
|
||||
import type { IDevialetConfig } from './devialet.types.js';
|
||||
import { devialetProfile } from './devialet.types.js';
|
||||
|
||||
export class DevialetMapper {
|
||||
public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IDevialetConfig>, 'profile'>): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: devialetProfile });
|
||||
}
|
||||
|
||||
public static toSnapshotFromRaw(configArg: IDevialetConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ profile: devialetProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
|
||||
}
|
||||
|
||||
public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
|
||||
return SimpleLocalMapper.toDevices(devialetProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
|
||||
return SimpleLocalMapper.toEntities(devialetProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static slug(valueArg: unknown): string {
|
||||
return SimpleLocalMapper.slug(valueArg);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,89 @@
|
||||
export interface IHomeAssistantDevialetConfig {
|
||||
// TODO: replace with the TypeScript-native config for devialet.
|
||||
[key: string]: unknown;
|
||||
import type { ISimpleLocalConfig, ISimpleLocalIntegrationProfile, ISimpleLocalSnapshot, TSimpleLocalRawData } from '../../core/index.js';
|
||||
|
||||
export const devialetDomain = 'devialet';
|
||||
export const devialetDefaultName = 'Devialet Speaker';
|
||||
|
||||
export type TDevialetRawData = TSimpleLocalRawData;
|
||||
export interface IDevialetSnapshot extends ISimpleLocalSnapshot {}
|
||||
export interface IDevialetConfig extends ISimpleLocalConfig {
|
||||
model?: string;
|
||||
serialNumber?: string;
|
||||
source?: string;
|
||||
soundMode?: string;
|
||||
}
|
||||
export interface IHomeAssistantDevialetConfig extends IDevialetConfig {}
|
||||
|
||||
export const devialetProfile: ISimpleLocalIntegrationProfile = {
|
||||
domain: devialetDomain,
|
||||
displayName: 'Devialet',
|
||||
manufacturer: 'Devialet',
|
||||
model: 'Phantom',
|
||||
defaultName: devialetDefaultName,
|
||||
defaultProtocol: 'http',
|
||||
status: 'read-only-runtime',
|
||||
platforms: [
|
||||
'media_player',
|
||||
],
|
||||
serviceDomains: [
|
||||
'media_player',
|
||||
],
|
||||
controlServices: [],
|
||||
discoverySources: [
|
||||
'manual',
|
||||
'mdns',
|
||||
'http',
|
||||
'custom',
|
||||
],
|
||||
discoveryKeywords: [
|
||||
'devialet',
|
||||
'phantom',
|
||||
'speaker',
|
||||
'_devialet-http._tcp.local.',
|
||||
],
|
||||
metadata: {
|
||||
source: 'home-assistant/core',
|
||||
upstreamPath: 'homeassistant/components/devialet',
|
||||
upstreamDomain: 'devialet',
|
||||
integrationType: 'device',
|
||||
iotClass: 'local_polling',
|
||||
qualityScale: undefined,
|
||||
requirements: [
|
||||
'devialet==1.5.7',
|
||||
],
|
||||
dependencies: [],
|
||||
afterDependencies: [
|
||||
'zeroconf',
|
||||
],
|
||||
codeowners: [
|
||||
'@fwestenberg',
|
||||
],
|
||||
configFlow: true,
|
||||
zeroconf: [
|
||||
'_devialet-http._tcp.local.',
|
||||
],
|
||||
runtime: {
|
||||
type: 'read-only-runtime',
|
||||
services: [
|
||||
'snapshot',
|
||||
'status',
|
||||
'refresh',
|
||||
],
|
||||
platforms: [
|
||||
'media_player',
|
||||
],
|
||||
controls: false,
|
||||
},
|
||||
localApi: {
|
||||
implemented: [
|
||||
'manual local endpoint setup',
|
||||
'zeroconf-compatible discovery hints',
|
||||
'snapshot, raw data, snapshotProvider, and injected native client operation',
|
||||
'media player service dispatch only through injected client.execute or commandExecutor',
|
||||
],
|
||||
explicitUnsupported: [
|
||||
'calling Devialet HTTP control endpoints without a supplied native client',
|
||||
'claiming live media player command success without injected client.execute or commandExecutor',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
export * from './devialet.classes.client.js';
|
||||
export * from './devialet.classes.configflow.js';
|
||||
export * from './devialet.classes.integration.js';
|
||||
export * from './devialet.discovery.js';
|
||||
export * from './devialet.mapper.js';
|
||||
export * from './devialet.types.js';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalClient } from '../../core/index.js';
|
||||
import type { IDevoloHomeControlConfig } from './devolo_home_control.types.js';
|
||||
import { devoloHomeControlProfile } from './devolo_home_control.types.js';
|
||||
|
||||
export class DevoloHomeControlClient extends SimpleLocalClient<IDevoloHomeControlConfig> {
|
||||
constructor(configArg: IDevoloHomeControlConfig) {
|
||||
super(devoloHomeControlProfile, configArg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalConfigFlow } from '../../core/index.js';
|
||||
import type { IDevoloHomeControlConfig } from './devolo_home_control.types.js';
|
||||
import { devoloHomeControlProfile } from './devolo_home_control.types.js';
|
||||
|
||||
export class DevoloHomeControlConfigFlow extends SimpleLocalConfigFlow<IDevoloHomeControlConfig> {
|
||||
constructor() {
|
||||
super(devoloHomeControlProfile);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,17 @@
|
||||
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
|
||||
import { SimpleLocalIntegration } from '../../core/index.js';
|
||||
import { DevoloHomeControlConfigFlow } from './devolo_home_control.classes.configflow.js';
|
||||
import { createDevoloHomeControlDiscoveryDescriptor } from './devolo_home_control.discovery.js';
|
||||
import type { IDevoloHomeControlConfig } from './devolo_home_control.types.js';
|
||||
import { devoloHomeControlDomain, devoloHomeControlProfile } from './devolo_home_control.types.js';
|
||||
|
||||
export class DevoloHomeControlIntegration extends SimpleLocalIntegration<IDevoloHomeControlConfig> {
|
||||
public readonly domain = devoloHomeControlDomain;
|
||||
public readonly discoveryDescriptor = createDevoloHomeControlDiscoveryDescriptor();
|
||||
public readonly configFlow = new DevoloHomeControlConfigFlow();
|
||||
|
||||
export class HomeAssistantDevoloHomeControlIntegration extends DescriptorOnlyIntegration {
|
||||
constructor() {
|
||||
super({
|
||||
domain: "devolo_home_control",
|
||||
displayName: "devolo Home Control",
|
||||
status: 'descriptor-only',
|
||||
metadata: {
|
||||
"source": "home-assistant/core",
|
||||
"upstreamPath": "homeassistant/components/devolo_home_control",
|
||||
"upstreamDomain": "devolo_home_control",
|
||||
"integrationType": "hub",
|
||||
"iotClass": "local_push",
|
||||
"qualityScale": "silver",
|
||||
"requirements": [
|
||||
"devolo-home-control-api==0.19.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"afterDependencies": [
|
||||
"zeroconf"
|
||||
],
|
||||
"codeowners": [
|
||||
"@2Fake",
|
||||
"@Shutgun"
|
||||
]
|
||||
},
|
||||
});
|
||||
super(devoloHomeControlProfile);
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeAssistantDevoloHomeControlIntegration extends DevoloHomeControlIntegration {}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createSimpleLocalDiscoveryDescriptor } from '../../core/index.js';
|
||||
import { devoloHomeControlProfile } from './devolo_home_control.types.js';
|
||||
|
||||
export const createDevoloHomeControlDiscoveryDescriptor = () => createSimpleLocalDiscoveryDescriptor(devoloHomeControlProfile);
|
||||
@@ -0,0 +1,26 @@
|
||||
import type * as shxInterfaces from '@smarthome.exchange/interfaces';
|
||||
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
|
||||
import type { IDevoloHomeControlConfig } from './devolo_home_control.types.js';
|
||||
import { devoloHomeControlProfile } from './devolo_home_control.types.js';
|
||||
|
||||
export class DevoloHomeControlMapper {
|
||||
public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IDevoloHomeControlConfig>, 'profile'>): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: devoloHomeControlProfile });
|
||||
}
|
||||
|
||||
public static toSnapshotFromRaw(configArg: IDevoloHomeControlConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ profile: devoloHomeControlProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
|
||||
}
|
||||
|
||||
public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
|
||||
return SimpleLocalMapper.toDevices(devoloHomeControlProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
|
||||
return SimpleLocalMapper.toEntities(devoloHomeControlProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static slug(valueArg: unknown): string {
|
||||
return SimpleLocalMapper.slug(valueArg);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,110 @@
|
||||
export interface IHomeAssistantDevoloHomeControlConfig {
|
||||
// TODO: replace with the TypeScript-native config for devolo_home_control.
|
||||
[key: string]: unknown;
|
||||
import type { ISimpleLocalConfig, ISimpleLocalIntegrationProfile, ISimpleLocalSnapshot, TSimpleLocalRawData } from '../../core/index.js';
|
||||
|
||||
export const devoloHomeControlDomain = 'devolo_home_control';
|
||||
export const devoloHomeControlDefaultName = 'devolo Home Control';
|
||||
|
||||
export type TDevoloHomeControlRawData = TSimpleLocalRawData;
|
||||
export interface IDevoloHomeControlSnapshot extends ISimpleLocalSnapshot {}
|
||||
export interface IDevoloHomeControlConfig extends ISimpleLocalConfig {
|
||||
gatewayId?: string;
|
||||
gatewayIds?: string[];
|
||||
}
|
||||
export interface IHomeAssistantDevoloHomeControlConfig extends IDevoloHomeControlConfig {}
|
||||
|
||||
export const devoloHomeControlProfile: ISimpleLocalIntegrationProfile = {
|
||||
domain: devoloHomeControlDomain,
|
||||
displayName: 'devolo Home Control',
|
||||
manufacturer: 'devolo',
|
||||
model: 'Home Control Central Unit',
|
||||
defaultName: devoloHomeControlDefaultName,
|
||||
defaultProtocol: 'local',
|
||||
status: 'read-only-runtime',
|
||||
platforms: [
|
||||
'binary_sensor',
|
||||
'climate',
|
||||
'cover',
|
||||
'light',
|
||||
'sensor',
|
||||
'switch',
|
||||
],
|
||||
serviceDomains: [
|
||||
'climate',
|
||||
'cover',
|
||||
'light',
|
||||
'siren',
|
||||
'switch',
|
||||
],
|
||||
controlServices: [],
|
||||
discoverySources: [
|
||||
'manual',
|
||||
'mdns',
|
||||
'custom',
|
||||
],
|
||||
discoveryKeywords: [
|
||||
'devolo',
|
||||
'home control',
|
||||
'homecontrol',
|
||||
'gateway',
|
||||
'2600',
|
||||
'2601',
|
||||
'_dvl-deviceapi._tcp.local.',
|
||||
],
|
||||
metadata: {
|
||||
source: 'home-assistant/core',
|
||||
upstreamPath: 'homeassistant/components/devolo_home_control',
|
||||
upstreamDomain: 'devolo_home_control',
|
||||
integrationType: 'hub',
|
||||
iotClass: 'local_push',
|
||||
qualityScale: 'silver',
|
||||
requirements: [
|
||||
'devolo-home-control-api==0.19.0',
|
||||
],
|
||||
dependencies: [],
|
||||
afterDependencies: [
|
||||
'zeroconf',
|
||||
],
|
||||
codeowners: [
|
||||
'@2Fake',
|
||||
'@Shutgun',
|
||||
],
|
||||
configFlow: true,
|
||||
zeroconf: [
|
||||
'_dvl-deviceapi._tcp.local.',
|
||||
],
|
||||
supportedModelTypes: [
|
||||
'2600',
|
||||
'2601',
|
||||
],
|
||||
runtime: {
|
||||
type: 'read-only-runtime',
|
||||
services: [
|
||||
'snapshot',
|
||||
'status',
|
||||
'refresh',
|
||||
],
|
||||
platforms: [
|
||||
'binary_sensor',
|
||||
'climate',
|
||||
'cover',
|
||||
'light',
|
||||
'sensor',
|
||||
'siren',
|
||||
'switch',
|
||||
],
|
||||
controls: false,
|
||||
},
|
||||
localApi: {
|
||||
implemented: [
|
||||
'manual local gateway metadata setup',
|
||||
'zeroconf-compatible discovery hints for devolo gateways',
|
||||
'snapshot, raw data, snapshotProvider, and injected native client operation',
|
||||
'entity service dispatch only through injected client.execute or commandExecutor',
|
||||
],
|
||||
explicitUnsupported: [
|
||||
'mydevolo cloud account authentication flow',
|
||||
'opening devolo Home Control websocket sessions without a supplied native client',
|
||||
'claiming live entity command success without injected client.execute or commandExecutor',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
export * from './devolo_home_control.classes.client.js';
|
||||
export * from './devolo_home_control.classes.configflow.js';
|
||||
export * from './devolo_home_control.classes.integration.js';
|
||||
export * from './devolo_home_control.discovery.js';
|
||||
export * from './devolo_home_control.mapper.js';
|
||||
export * from './devolo_home_control.types.js';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalClient } from '../../core/index.js';
|
||||
import type { IDoodsConfig } from './doods.types.js';
|
||||
import { doodsProfile } from './doods.types.js';
|
||||
|
||||
export class DoodsClient extends SimpleLocalClient<IDoodsConfig> {
|
||||
constructor(configArg: IDoodsConfig) {
|
||||
super(doodsProfile, configArg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalConfigFlow } from '../../core/index.js';
|
||||
import type { IDoodsConfig } from './doods.types.js';
|
||||
import { doodsProfile } from './doods.types.js';
|
||||
|
||||
export class DoodsConfigFlow extends SimpleLocalConfigFlow<IDoodsConfig> {
|
||||
constructor() {
|
||||
super(doodsProfile);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,17 @@
|
||||
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
|
||||
import { SimpleLocalIntegration } from '../../core/index.js';
|
||||
import { DoodsConfigFlow } from './doods.classes.configflow.js';
|
||||
import { createDoodsDiscoveryDescriptor } from './doods.discovery.js';
|
||||
import type { IDoodsConfig } from './doods.types.js';
|
||||
import { doodsDomain, doodsProfile } from './doods.types.js';
|
||||
|
||||
export class DoodsIntegration extends SimpleLocalIntegration<IDoodsConfig> {
|
||||
public readonly domain = doodsDomain;
|
||||
public readonly discoveryDescriptor = createDoodsDiscoveryDescriptor();
|
||||
public readonly configFlow = new DoodsConfigFlow();
|
||||
|
||||
export class HomeAssistantDoodsIntegration extends DescriptorOnlyIntegration {
|
||||
constructor() {
|
||||
super({
|
||||
domain: "doods",
|
||||
displayName: "DOODS - Dedicated Open Object Detection Service",
|
||||
status: 'descriptor-only',
|
||||
metadata: {
|
||||
"source": "home-assistant/core",
|
||||
"upstreamPath": "homeassistant/components/doods",
|
||||
"upstreamDomain": "doods",
|
||||
"iotClass": "local_polling",
|
||||
"qualityScale": "legacy",
|
||||
"requirements": [
|
||||
"pydoods==1.0.2",
|
||||
"Pillow==12.2.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"afterDependencies": [],
|
||||
"codeowners": []
|
||||
},
|
||||
});
|
||||
super(doodsProfile);
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeAssistantDoodsIntegration extends DoodsIntegration {}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createSimpleLocalDiscoveryDescriptor } from '../../core/index.js';
|
||||
import { doodsProfile } from './doods.types.js';
|
||||
|
||||
export const createDoodsDiscoveryDescriptor = () => createSimpleLocalDiscoveryDescriptor(doodsProfile);
|
||||
@@ -0,0 +1,26 @@
|
||||
import type * as shxInterfaces from '@smarthome.exchange/interfaces';
|
||||
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
|
||||
import type { IDoodsConfig } from './doods.types.js';
|
||||
import { doodsProfile } from './doods.types.js';
|
||||
|
||||
export class DoodsMapper {
|
||||
public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IDoodsConfig>, 'profile'>): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: doodsProfile });
|
||||
}
|
||||
|
||||
public static toSnapshotFromRaw(configArg: IDoodsConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ profile: doodsProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
|
||||
}
|
||||
|
||||
public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
|
||||
return SimpleLocalMapper.toDevices(doodsProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
|
||||
return SimpleLocalMapper.toEntities(doodsProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static slug(valueArg: unknown): string {
|
||||
return SimpleLocalMapper.slug(valueArg);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,74 @@
|
||||
export interface IHomeAssistantDoodsConfig {
|
||||
// TODO: replace with the TypeScript-native config for doods.
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { ISimpleLocalConfig, ISimpleLocalIntegrationProfile, ISimpleLocalSnapshot, TSimpleLocalRawData } from '../../core/index.js';
|
||||
|
||||
export const doodsDomain = 'doods';
|
||||
export const doodsDefaultName = 'DOODS - Dedicated Open Object Detection Service';
|
||||
|
||||
export type TDoodsRawData = TSimpleLocalRawData;
|
||||
export interface IDoodsSnapshot extends ISimpleLocalSnapshot {}
|
||||
export interface IDoodsConfig extends ISimpleLocalConfig {}
|
||||
export interface IHomeAssistantDoodsConfig extends IDoodsConfig {}
|
||||
|
||||
export const doodsProfile: ISimpleLocalIntegrationProfile = {
|
||||
domain: 'doods',
|
||||
displayName: 'DOODS - Dedicated Open Object Detection Service',
|
||||
manufacturer: 'DOODS',
|
||||
model: 'Object detection service',
|
||||
defaultName: 'DOODS - Dedicated Open Object Detection Service',
|
||||
defaultProtocol: 'http',
|
||||
status: 'read-only-runtime',
|
||||
platforms: [
|
||||
'sensor',
|
||||
],
|
||||
serviceDomains: [],
|
||||
controlServices: [],
|
||||
discoverySources: [
|
||||
'manual',
|
||||
'http',
|
||||
'custom',
|
||||
],
|
||||
discoveryKeywords: [
|
||||
'doods',
|
||||
'dedicated open object detection service',
|
||||
'object detection',
|
||||
'image processing',
|
||||
],
|
||||
metadata: {
|
||||
source: 'home-assistant/core',
|
||||
upstreamPath: 'homeassistant/components/doods',
|
||||
upstreamDomain: 'doods',
|
||||
iotClass: 'local_polling',
|
||||
qualityScale: 'legacy',
|
||||
requirements: [
|
||||
'pydoods==1.0.2',
|
||||
'Pillow==12.2.0',
|
||||
],
|
||||
dependencies: [],
|
||||
afterDependencies: [],
|
||||
codeowners: [],
|
||||
configFlow: false,
|
||||
runtime: {
|
||||
type: 'read-only-runtime',
|
||||
services: [
|
||||
'snapshot',
|
||||
'status',
|
||||
'refresh',
|
||||
],
|
||||
platforms: [
|
||||
'sensor',
|
||||
],
|
||||
controls: false,
|
||||
},
|
||||
localApi: {
|
||||
implemented: [
|
||||
'manual local DOODS endpoint setup',
|
||||
'snapshot, raw data, snapshotProvider, and injected native client operation',
|
||||
'generic HTTP local transport when config.path, config.transport, or documented defaults are supplied',
|
||||
],
|
||||
explicitUnsupported: [
|
||||
'claiming live image processing or command success without injected client.execute or commandExecutor',
|
||||
'camera image acquisition and rendered output file writing',
|
||||
'device-specific protocol features not represented by snapshot/rawData/client/executor inputs',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
export * from './doods.classes.client.js';
|
||||
export * from './doods.classes.configflow.js';
|
||||
export * from './doods.classes.integration.js';
|
||||
export * from './doods.discovery.js';
|
||||
export * from './doods.mapper.js';
|
||||
export * from './doods.types.js';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalClient } from '../../core/index.js';
|
||||
import type { IDormakabaDkeyConfig } from './dormakaba_dkey.types.js';
|
||||
import { dormakabaDkeyProfile } from './dormakaba_dkey.types.js';
|
||||
|
||||
export class DormakabaDkeyClient extends SimpleLocalClient<IDormakabaDkeyConfig> {
|
||||
constructor(configArg: IDormakabaDkeyConfig) {
|
||||
super(dormakabaDkeyProfile, configArg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalConfigFlow } from '../../core/index.js';
|
||||
import type { IDormakabaDkeyConfig } from './dormakaba_dkey.types.js';
|
||||
import { dormakabaDkeyProfile } from './dormakaba_dkey.types.js';
|
||||
|
||||
export class DormakabaDkeyConfigFlow extends SimpleLocalConfigFlow<IDormakabaDkeyConfig> {
|
||||
constructor() {
|
||||
super(dormakabaDkeyProfile);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,17 @@
|
||||
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
|
||||
import { SimpleLocalIntegration } from '../../core/index.js';
|
||||
import { DormakabaDkeyConfigFlow } from './dormakaba_dkey.classes.configflow.js';
|
||||
import { createDormakabaDkeyDiscoveryDescriptor } from './dormakaba_dkey.discovery.js';
|
||||
import type { IDormakabaDkeyConfig } from './dormakaba_dkey.types.js';
|
||||
import { dormakabaDkeyDomain, dormakabaDkeyProfile } from './dormakaba_dkey.types.js';
|
||||
|
||||
export class DormakabaDkeyIntegration extends SimpleLocalIntegration<IDormakabaDkeyConfig> {
|
||||
public readonly domain = dormakabaDkeyDomain;
|
||||
public readonly discoveryDescriptor = createDormakabaDkeyDiscoveryDescriptor();
|
||||
public readonly configFlow = new DormakabaDkeyConfigFlow();
|
||||
|
||||
export class HomeAssistantDormakabaDkeyIntegration extends DescriptorOnlyIntegration {
|
||||
constructor() {
|
||||
super({
|
||||
domain: "dormakaba_dkey",
|
||||
displayName: "Dormakaba dKey",
|
||||
status: 'descriptor-only',
|
||||
metadata: {
|
||||
"source": "home-assistant/core",
|
||||
"upstreamPath": "homeassistant/components/dormakaba_dkey",
|
||||
"upstreamDomain": "dormakaba_dkey",
|
||||
"integrationType": "device",
|
||||
"iotClass": "local_polling",
|
||||
"requirements": [
|
||||
"py-dormakaba-dkey==1.0.6"
|
||||
],
|
||||
"dependencies": [
|
||||
"bluetooth_adapters"
|
||||
],
|
||||
"afterDependencies": [],
|
||||
"codeowners": [
|
||||
"@emontnemery"
|
||||
]
|
||||
},
|
||||
});
|
||||
super(dormakabaDkeyProfile);
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeAssistantDormakabaDkeyIntegration extends DormakabaDkeyIntegration {}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createSimpleLocalDiscoveryDescriptor } from '../../core/index.js';
|
||||
import { dormakabaDkeyProfile } from './dormakaba_dkey.types.js';
|
||||
|
||||
export const createDormakabaDkeyDiscoveryDescriptor = () => createSimpleLocalDiscoveryDescriptor(dormakabaDkeyProfile);
|
||||
@@ -0,0 +1,26 @@
|
||||
import type * as shxInterfaces from '@smarthome.exchange/interfaces';
|
||||
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
|
||||
import type { IDormakabaDkeyConfig } from './dormakaba_dkey.types.js';
|
||||
import { dormakabaDkeyProfile } from './dormakaba_dkey.types.js';
|
||||
|
||||
export class DormakabaDkeyMapper {
|
||||
public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IDormakabaDkeyConfig>, 'profile'>): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: dormakabaDkeyProfile });
|
||||
}
|
||||
|
||||
public static toSnapshotFromRaw(configArg: IDormakabaDkeyConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
|
||||
return SimpleLocalMapper.toSnapshot({ profile: dormakabaDkeyProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
|
||||
}
|
||||
|
||||
public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
|
||||
return SimpleLocalMapper.toDevices(dormakabaDkeyProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
|
||||
return SimpleLocalMapper.toEntities(dormakabaDkeyProfile, snapshotArg);
|
||||
}
|
||||
|
||||
public static slug(valueArg: unknown): string {
|
||||
return SimpleLocalMapper.slug(valueArg);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,93 @@
|
||||
export interface IHomeAssistantDormakabaDkeyConfig {
|
||||
// TODO: replace with the TypeScript-native config for dormakaba_dkey.
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { ISimpleLocalConfig, ISimpleLocalIntegrationProfile, ISimpleLocalSnapshot, TSimpleLocalRawData } from '../../core/index.js';
|
||||
|
||||
export const dormakabaDkeyDomain = 'dormakaba_dkey';
|
||||
export const dormakabaDkeyDefaultName = 'Dormakaba dKey';
|
||||
|
||||
export type TDormakabaDkeyRawData = TSimpleLocalRawData;
|
||||
export interface IDormakabaDkeySnapshot extends ISimpleLocalSnapshot {}
|
||||
export interface IDormakabaDkeyConfig extends ISimpleLocalConfig {}
|
||||
export interface IHomeAssistantDormakabaDkeyConfig extends IDormakabaDkeyConfig {}
|
||||
|
||||
export const dormakabaDkeyProfile: ISimpleLocalIntegrationProfile = {
|
||||
domain: 'dormakaba_dkey',
|
||||
displayName: 'Dormakaba dKey',
|
||||
manufacturer: 'Dormakaba',
|
||||
model: 'MTL 9291',
|
||||
defaultName: 'Dormakaba dKey',
|
||||
defaultProtocol: 'local',
|
||||
status: 'control-runtime',
|
||||
platforms: [
|
||||
'binary_sensor',
|
||||
'sensor',
|
||||
'switch',
|
||||
],
|
||||
serviceDomains: [
|
||||
'lock',
|
||||
],
|
||||
controlServices: [
|
||||
'lock',
|
||||
'unlock',
|
||||
],
|
||||
discoverySources: [
|
||||
'manual',
|
||||
'bluetooth',
|
||||
'custom',
|
||||
],
|
||||
discoveryKeywords: [
|
||||
'dormakaba',
|
||||
'dkey',
|
||||
'e7a60000-6639-429f-94fd-86de8ea26897',
|
||||
'e7a60001-6639-429f-94fd-86de8ea26897',
|
||||
],
|
||||
metadata: {
|
||||
source: 'home-assistant/core',
|
||||
upstreamPath: 'homeassistant/components/dormakaba_dkey',
|
||||
upstreamDomain: 'dormakaba_dkey',
|
||||
integrationType: 'device',
|
||||
iotClass: 'local_polling',
|
||||
qualityScale: undefined,
|
||||
requirements: [
|
||||
'py-dormakaba-dkey==1.0.6',
|
||||
],
|
||||
dependencies: [
|
||||
'bluetooth_adapters',
|
||||
],
|
||||
afterDependencies: [],
|
||||
codeowners: [
|
||||
'@emontnemery',
|
||||
],
|
||||
configFlow: true,
|
||||
bluetooth: [
|
||||
{ service_uuid: 'e7a60000-6639-429f-94fd-86de8ea26897' },
|
||||
{ service_uuid: 'e7a60001-6639-429f-94fd-86de8ea26897' },
|
||||
],
|
||||
runtime: {
|
||||
type: 'control-runtime',
|
||||
services: [
|
||||
'snapshot',
|
||||
'status',
|
||||
'refresh',
|
||||
'lock',
|
||||
'unlock',
|
||||
],
|
||||
platforms: [
|
||||
'binary_sensor',
|
||||
'sensor',
|
||||
'switch',
|
||||
],
|
||||
controls: true,
|
||||
},
|
||||
localApi: {
|
||||
implemented: [
|
||||
'manual local dKey device setup',
|
||||
'bluetooth discovery records and offline association data snapshots',
|
||||
'snapshot, raw data, snapshotProvider, and injected native client operation',
|
||||
],
|
||||
explicitUnsupported: [
|
||||
'claiming live lock or unlock success without injected client.execute or commandExecutor',
|
||||
'Bluetooth association and activation-code exchange without an injected native client',
|
||||
'device-specific protocol features not represented by snapshot/rawData/client/executor inputs',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
export * from './dormakaba_dkey.classes.client.js';
|
||||
export * from './dormakaba_dkey.classes.configflow.js';
|
||||
export * from './dormakaba_dkey.classes.integration.js';
|
||||
export * from './dormakaba_dkey.discovery.js';
|
||||
export * from './dormakaba_dkey.mapper.js';
|
||||
export * from './dormakaba_dkey.types.js';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalClient } from '../../core/index.js';
|
||||
import type { IDovadoConfig } from './dovado.types.js';
|
||||
import { dovadoProfile } from './dovado.types.js';
|
||||
|
||||
export class DovadoClient extends SimpleLocalClient<IDovadoConfig> {
|
||||
constructor(configArg: IDovadoConfig) {
|
||||
super(dovadoProfile, configArg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleLocalConfigFlow } from '../../core/index.js';
|
||||
import type { IDovadoConfig } from './dovado.types.js';
|
||||
import { dovadoProfile } from './dovado.types.js';
|
||||
|
||||
export class DovadoConfigFlow extends SimpleLocalConfigFlow<IDovadoConfig> {
|
||||
constructor() {
|
||||
super(dovadoProfile);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,17 @@
|
||||
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
|
||||
import { SimpleLocalIntegration } from '../../core/index.js';
|
||||
import { DovadoConfigFlow } from './dovado.classes.configflow.js';
|
||||
import { createDovadoDiscoveryDescriptor } from './dovado.discovery.js';
|
||||
import type { IDovadoConfig } from './dovado.types.js';
|
||||
import { dovadoDomain, dovadoProfile } from './dovado.types.js';
|
||||
|
||||
export class DovadoIntegration extends SimpleLocalIntegration<IDovadoConfig> {
|
||||
public readonly domain = dovadoDomain;
|
||||
public readonly discoveryDescriptor = createDovadoDiscoveryDescriptor();
|
||||
public readonly configFlow = new DovadoConfigFlow();
|
||||
|
||||
export class HomeAssistantDovadoIntegration extends DescriptorOnlyIntegration {
|
||||
constructor() {
|
||||
super({
|
||||
domain: "dovado",
|
||||
displayName: "Dovado",
|
||||
status: 'descriptor-only',
|
||||
metadata: {
|
||||
"source": "home-assistant/core",
|
||||
"upstreamPath": "homeassistant/components/dovado",
|
||||
"upstreamDomain": "dovado",
|
||||
"iotClass": "local_polling",
|
||||
"qualityScale": "legacy",
|
||||
"requirements": [
|
||||
"dovado==0.4.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"afterDependencies": [],
|
||||
"codeowners": []
|
||||
},
|
||||
});
|
||||
super(dovadoProfile);
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeAssistantDovadoIntegration extends DovadoIntegration {}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createSimpleLocalDiscoveryDescriptor } from '../../core/index.js';
|
||||
import { dovadoProfile } from './dovado.types.js';
|
||||
|
||||
export const createDovadoDiscoveryDescriptor = () => createSimpleLocalDiscoveryDescriptor(dovadoProfile);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user