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,54 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createNanoleafDiscoveryDescriptor } from '../../ts/integrations/nanoleaf/index.js';
tap.test('matches Nanoleaf mDNS zeroconf records', async () => {
const descriptor = createNanoleafDiscoveryDescriptor();
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'nanoleaf-mdns-match');
const result = await matcher!.matches({
name: 'Nanoleaf Shapes ABCD',
type: '_nanoleafapi._tcp.local.',
host: 'nanoleaf-shapes.local',
port: 16021,
txt: {
id: 'NL123ABC',
md: 'NL42',
},
}, {});
expect(result.matched).toBeTrue();
expect(result.normalizedDeviceId).toEqual('NL123ABC');
expect(result.candidate?.port).toEqual(16021);
});
tap.test('matches Nanoleaf SSDP records', async () => {
const descriptor = createNanoleafDiscoveryDescriptor();
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'nanoleaf-ssdp-match');
const result = await matcher!.matches({
st: 'nanoleaf:nl42',
usn: 'uuid:nanoleaf-nl42',
headers: {
'_host': '192.168.1.55:16021',
'nl-devicename': 'Nanoleaf Shapes ABCD',
'nl-deviceid': 'NL123ABC',
},
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.host).toEqual('192.168.1.55');
expect(result.candidate?.model).toEqual('NL42');
});
tap.test('validates manual Nanoleaf candidates', async () => {
const descriptor = createNanoleafDiscoveryDescriptor();
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'nanoleaf-manual-match');
const manualResult = await matcher!.matches({
host: '192.168.1.56',
port: 16021,
metadata: { nanoleaf: true },
}, {});
expect(manualResult.matched).toBeTrue();
const validator = descriptor.getValidators()[0];
const validation = await validator.validate(manualResult.candidate!, {});
expect(validation.matched).toBeTrue();
});
export default tap.start();
@@ -0,0 +1,59 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { NanoleafMapper } from '../../ts/integrations/nanoleaf/index.js';
const snapshot = {
controllerInfo: {
name: 'Living Room Shapes',
serialNo: 'NL123ABC',
manufacturer: 'Nanoleaf',
model: 'NL42',
firmwareVersion: '9.6.1',
},
state: {
on: { value: true },
brightness: { value: 80, min: 0, max: 100 },
hue: { value: 220, min: 0, max: 360 },
sat: { value: 65, min: 0, max: 100 },
ct: { value: 4000, min: 1200, max: 6500 },
colorMode: 'effect',
},
effects: {
select: 'Northern Lights',
effectsList: ['Northern Lights', '*Solid*'],
},
panelLayout: {
layout: {
numPanels: 2,
sideLength: 150,
positionData: [
{ panelId: 101, x: 0, y: 0, o: 0, shapeType: 7 },
{ panelId: 102, x: 150, y: 0, o: 60, shapeType: 7 },
],
},
},
rhythm: {
rhythmConnected: true,
rhythmActive: false,
},
};
tap.test('maps Nanoleaf controller and panels to canonical devices', async () => {
const devices = NanoleafMapper.toDevices(snapshot);
expect(devices[0].id).toEqual('nanoleaf.controller.nl123abc');
expect(devices[0].features.some((featureArg) => featureArg.id === 'panel_count')).toBeTrue();
expect(devices.some((deviceArg) => deviceArg.id === 'nanoleaf.panel.nl123abc.101')).toBeTrue();
});
tap.test('maps Nanoleaf light state, sensors, select, and button entities', async () => {
const entities = NanoleafMapper.toEntities(snapshot);
const light = entities.find((entityArg) => entityArg.id === 'light.living_room_shapes');
const effect = entities.find((entityArg) => entityArg.id === 'select.living_room_shapes_effect');
expect(light?.state).toEqual('on');
expect(light?.attributes?.brightness).toEqual(80);
expect(effect?.state).toEqual('Northern Lights');
expect(entities.some((entityArg) => entityArg.id === 'button.living_room_shapes_identify')).toBeTrue();
expect(entities.some((entityArg) => entityArg.id === 'sensor.living_room_shapes_panel_count')).toBeTrue();
expect(entities.some((entityArg) => entityArg.id === 'sensor.living_room_shapes_panel_101')).toBeTrue();
});
export default tap.start();