Add native hub protocol integrations

This commit is contained in:
2026-05-05 14:57:06 +00:00
parent 2823a1c718
commit 1eebd71e7d
102 changed files with 16316 additions and 330 deletions
@@ -0,0 +1,51 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createXiaomiMiioDiscoveryDescriptor } from '../../ts/integrations/xiaomi_miio/index.js';
tap.test('matches Xiaomi Miio mDNS records', async () => {
const descriptor = createXiaomiMiioDiscoveryDescriptor();
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'xiaomi-miio-mdns-match');
const result = await matcher!.matches({
type: '_miio._udp.local.',
name: 'rockrobo-vacuum-v1_miio._udp.local.',
host: '192.168.1.50',
port: 54321,
txt: { poch: 'mac=286c0789abcd', did: '123456789' },
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.integrationDomain).toEqual('xiaomi_miio');
expect(result.candidate?.model).toEqual('rockrobo.vacuum.v1');
expect(result.candidate?.macAddress).toEqual('28:6c:07:89:ab:cd');
});
tap.test('matches manual host token entries and validates candidates', async () => {
const descriptor = createXiaomiMiioDiscoveryDescriptor();
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'xiaomi-miio-manual-match');
const validator = descriptor.getValidators()[0];
const result = await matcher!.matches({
host: '192.168.1.51',
token: '00112233445566778899aabbccddeeff',
model: 'zhimi.airpurifier.v2',
name: 'Bedroom purifier',
}, {});
expect(result.matched).toBeTrue();
expect(result.metadata?.tokenConfigured).toBeTrue();
const validation = await validator.validate(result.candidate!, {});
expect(validation.matched).toBeTrue();
expect(validation.confidence).toEqual('certain');
});
tap.test('matches Xiaomi Miio DHCP records', async () => {
const descriptor = createXiaomiMiioDiscoveryDescriptor();
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'xiaomi-miio-dhcp-match');
const result = await matcher!.matches({
ipAddress: '192.168.1.52',
hostname: 'roborock-vacuum',
manufacturer: 'Xiaomi',
macAddress: '28:6C:07:89:AB:CE',
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.host).toEqual('192.168.1.52');
expect(result.candidate?.macAddress).toEqual('28:6c:07:89:ab:ce');
});
export default tap.start();
@@ -0,0 +1,77 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { XiaomiMiioMapper } from '../../ts/integrations/xiaomi_miio/index.js';
const snapshot = XiaomiMiioMapper.toSnapshot({
devices: [
{
id: 'vacuum-1',
name: 'Roborock S5',
model: 'rockrobo.vacuum.v1',
state: { state_code: 5, battery: 82, clean_area: 22, clean_time: 1800, fan_speed: 'Balanced' },
},
{
id: 'fan-1',
name: 'Bedroom purifier',
model: 'zhimi.airpurifier.v2',
state: { is_on: true, mode: 'auto', fan_level: 2, temperature: 22.4, humidity: 44, pm25: 6 },
},
{
id: 'light-1',
name: 'Desk lamp',
model: 'philips.light.bulb',
state: { is_on: true, brightness: 80, color_temperature: 45 },
},
{
id: 'plug-1',
name: 'Coffee plug',
model: 'chuangmi.plug.v3',
state: { is_on: false, temperature: 30, load_power: 0 },
},
{
id: 'cover-1',
name: 'Living room curtain',
kind: 'cover',
state: { position: 55 },
},
{
id: 'humidifier-1',
name: 'Nursery humidifier',
model: 'zhimi.humidifier.ca4',
state: { is_on: true, humidity: 41, target_humidity: 50, mode: 'Auto' },
},
],
});
tap.test('maps Xiaomi Miio device states to canonical devices and entities', async () => {
const devices = XiaomiMiioMapper.toDevices(snapshot);
const entities = XiaomiMiioMapper.toEntities(snapshot);
expect(devices.some((deviceArg) => deviceArg.id === 'xiaomi_miio.device.vacuum_1')).toBeTrue();
expect(entities.some((entityArg) => entityArg.id === 'vacuum.roborock_s5' && entityArg.state === 'cleaning')).toBeTrue();
expect(entities.some((entityArg) => entityArg.id === 'fan.bedroom_purifier' && entityArg.state === 'on')).toBeTrue();
expect(entities.some((entityArg) => entityArg.id === 'light.desk_lamp' && entityArg.state === 'on')).toBeTrue();
expect(entities.some((entityArg) => entityArg.id === 'switch.coffee_plug' && entityArg.state === 'off')).toBeTrue();
expect(entities.some((entityArg) => entityArg.id === 'cover.living_room_curtain' && entityArg.state === 'open')).toBeTrue();
expect(entities.some((entityArg) => entityArg.id === 'humidifier.nursery_humidifier' && entityArg.platform === 'climate')).toBeTrue();
expect(entities.some((entityArg) => entityArg.id === 'sensor.bedroom_purifier_pm25' && entityArg.state === 6)).toBeTrue();
});
tap.test('maps vacuum and generic set_value service calls to client commands', async () => {
const vacuumCommand = XiaomiMiioMapper.commandForService(snapshot, {
domain: 'vacuum',
service: 'return_home',
target: { entityId: 'vacuum.roborock_s5' },
});
expect(vacuumCommand?.method).toEqual('return_home');
expect(vacuumCommand?.kind).toEqual('vacuum');
const valueCommand = XiaomiMiioMapper.commandForService(snapshot, {
domain: 'number',
service: 'set_value',
target: { entityId: 'number.bedroom_purifier_fan_level' },
data: { value: 3 },
});
expect(valueCommand?.method).toEqual('set_value');
expect(valueCommand?.payload.value).toEqual(3);
});
export default tap.start();