import { expect, tap } from '@git.zone/tstest/tapbundle'; import { HomekitControllerIntegration, HomekitControllerMapper } from '../../ts/integrations/homekit_controller/index.js'; const snapshot = HomekitControllerMapper.toSnapshot({ id: 'aa:bb:cc:dd:ee:ff', connected: true, accessories: [{ aid: 1, services: [{ iid: 1, type: '0000003E-0000-1000-8000-0026BB765291', characteristics: [ { iid: 2, type: '00000023-0000-1000-8000-0026BB765291', value: 'Living Room Bridge' }, { iid: 3, type: '00000020-0000-1000-8000-0026BB765291', value: 'Example' }, { iid: 4, type: '00000021-0000-1000-8000-0026BB765291', value: 'HK-Bridge' }, { iid: 5, type: '00000030-0000-1000-8000-0026BB765291', value: 'SERIAL1' }, ], }, { iid: 10, type: '00000043-0000-1000-8000-0026BB765291', characteristics: [ { iid: 11, type: '00000023-0000-1000-8000-0026BB765291', value: 'Lamp' }, { iid: 12, type: '00000025-0000-1000-8000-0026BB765291', value: true, perms: ['pr', 'pw', 'ev'] }, { iid: 13, type: '00000008-0000-1000-8000-0026BB765291', value: 42, perms: ['pr', 'pw', 'ev'] }, ], }, { iid: 20, type: '00000045-0000-1000-8000-0026BB765291', characteristics: [ { iid: 21, type: '0000001D-0000-1000-8000-0026BB765291', value: 1, perms: ['pr', 'ev'] }, { iid: 22, type: '0000001E-0000-1000-8000-0026BB765291', value: 1, perms: ['pr', 'pw', 'ev'] }, ], }, { iid: 30, type: '0000008C-0000-1000-8000-0026BB765291', characteristics: [ { iid: 31, type: '0000006D-0000-1000-8000-0026BB765291', value: 50, perms: ['pr', 'ev'] }, { iid: 32, type: '0000007C-0000-1000-8000-0026BB765291', value: 50, perms: ['pr', 'pw', 'ev'] }, { iid: 33, type: '00000072-0000-1000-8000-0026BB765291', value: 2, perms: ['pr', 'ev'] }, ], }, { iid: 40, type: '00000110-0000-1000-8000-0026BB765291', characteristics: [ { iid: 41, type: '000000B0-0000-1000-8000-0026BB765291', value: 1, perms: ['pr', 'pw', 'ev'] }, ], }, { iid: 50, type: '00000049-0000-1000-8000-0026BB765291', characteristics: [ { iid: 51, type: '00000023-0000-1000-8000-0026BB765291', value: 'Wall Switch' }, { iid: 52, type: '00000025-0000-1000-8000-0026BB765291', value: false, perms: ['pr', 'pw', 'ev'] }, ], }, { iid: 60, type: '00000047-0000-1000-8000-0026BB765291', characteristics: [ { iid: 61, type: '00000023-0000-1000-8000-0026BB765291', value: 'Outlet' }, { iid: 62, type: '00000025-0000-1000-8000-0026BB765291', value: true, perms: ['pr', 'pw', 'ev'] }, { iid: 63, type: '00000026-0000-1000-8000-0026BB765291', value: true, perms: ['pr', 'ev'] }, ], }, { iid: 70, type: '0000008A-0000-1000-8000-0026BB765291', characteristics: [ { iid: 71, type: '00000011-0000-1000-8000-0026BB765291', value: 21.5, perms: ['pr', 'ev'] }, ], }, { iid: 80, type: '0000004A-0000-1000-8000-0026BB765291', characteristics: [ { iid: 81, type: '00000011-0000-1000-8000-0026BB765291', value: 20, perms: ['pr', 'ev'] }, { iid: 82, type: '00000035-0000-1000-8000-0026BB765291', value: 23, perms: ['pr', 'pw', 'ev'] }, { iid: 83, type: '00000033-0000-1000-8000-0026BB765291', value: 1, perms: ['pr', 'pw', 'ev'] }, ], }, { iid: 90, type: '00000041-0000-1000-8000-0026BB765291', characteristics: [ { iid: 91, type: '0000000E-0000-1000-8000-0026BB765291', value: 1, perms: ['pr', 'ev'] }, { iid: 92, type: '00000032-0000-1000-8000-0026BB765291', value: 1, perms: ['pr', 'pw', 'ev'] }, { iid: 93, type: '00000024-0000-1000-8000-0026BB765291', value: false, perms: ['pr', 'ev'] }, ], }, { iid: 100, type: '000000B7-0000-1000-8000-0026BB765291', characteristics: [ { iid: 101, type: '000000B0-0000-1000-8000-0026BB765291', value: 1, perms: ['pr', 'pw', 'ev'] }, { iid: 102, type: '00000029-0000-1000-8000-0026BB765291', value: 55, perms: ['pr', 'pw', 'ev'] }, ], }], }], }); tap.test('maps HomeKit accessories to devices and entities', async () => { const devices = HomekitControllerMapper.toDevices(snapshot); const entities = HomekitControllerMapper.toEntities(snapshot); expect(devices[0].protocol).toEqual('homekit'); expect(devices[0].manufacturer).toEqual('Example'); expect(entities.some((entityArg) => entityArg.platform === 'light' && entityArg.state === 'on' && entityArg.attributes?.brightness === 42)).toBeTrue(); expect(entities.some((entityArg) => String(entityArg.platform) === 'lock' && entityArg.state === 'locked')).toBeTrue(); expect(entities.some((entityArg) => entityArg.platform === 'cover' && entityArg.state === 'open')).toBeTrue(); expect(entities.some((entityArg) => String(entityArg.platform) === 'camera' && entityArg.state === 'available')).toBeTrue(); expect(entities.some((entityArg) => entityArg.platform === 'switch' && entityArg.attributes?.serviceType === 'outlet' && entityArg.attributes?.outletInUse === true)).toBeTrue(); expect(entities.some((entityArg) => entityArg.platform === 'sensor' && entityArg.state === 21.5)).toBeTrue(); expect(entities.some((entityArg) => entityArg.platform === 'climate' && entityArg.state === 'heat' && entityArg.attributes?.targetTemperature === 23)).toBeTrue(); expect(entities.some((entityArg) => entityArg.platform === 'cover' && entityArg.state === 'closed' && entityArg.attributes?.serviceType === 'garage_door_opener')).toBeTrue(); expect(entities.some((entityArg) => entityArg.platform === 'fan' && entityArg.state === 'on' && entityArg.attributes?.percentage === 55)).toBeTrue(); }); tap.test('maps common entity services to HomeKit writes', async () => { const light = HomekitControllerMapper.toEntities(snapshot).find((entityArg) => entityArg.platform === 'light'); const command = HomekitControllerMapper.commandForService(snapshot, { domain: 'light', service: 'turn_off', target: { entityId: light?.id }, }); expect(command?.command).toEqual('write_characteristics'); expect(command?.writes?.[0]?.aid).toEqual(1); expect(command?.writes?.[0]?.iid).toEqual(12); expect(command?.writes?.[0]?.value).toEqual(false); const lightOnCommand = HomekitControllerMapper.commandForService(snapshot, { domain: 'light', service: 'turn_on', target: { entityId: light?.id }, data: { brightness: 128 }, }); expect(lightOnCommand?.writes?.some((writeArg) => writeArg.iid === 13 && writeArg.value === 50)).toBeTrue(); const fan = HomekitControllerMapper.toEntities(snapshot).find((entityArg) => entityArg.platform === 'fan'); const fanCommand = HomekitControllerMapper.commandForService(snapshot, { domain: 'fan', service: 'set_percentage', target: { entityId: fan?.id }, data: { percentage: 75 }, }); expect(fanCommand?.writes?.some((writeArg) => writeArg.iid === 102 && writeArg.value === 75)).toBeTrue(); const climate = HomekitControllerMapper.toEntities(snapshot).find((entityArg) => entityArg.platform === 'climate'); const climateCommand = HomekitControllerMapper.commandForService(snapshot, { domain: 'climate', service: 'set_hvac_mode', target: { entityId: climate?.id }, data: { hvac_mode: 'heat_cool' }, }); expect(climateCommand?.writes?.some((writeArg) => writeArg.iid === 83 && writeArg.value === 3)).toBeTrue(); const camera = HomekitControllerMapper.toEntities(snapshot).find((entityArg) => String(entityArg.platform) === 'camera'); const cameraCommand = HomekitControllerMapper.commandForService(snapshot, { domain: 'camera', service: 'snapshot', target: { entityId: camera?.id }, data: { width: 320, height: 240 }, }); expect(cameraCommand?.command).toEqual('camera_snapshot'); expect(cameraCommand?.aid).toEqual(1); }); tap.test('returns explicit unsupported errors for native HAP security operations', async () => { const integration = new HomekitControllerIntegration(); const runtime = await integration.setup({ host: 'desk-lamp.local', setupCode: '234-56-789' }, {}); const result = await runtime.callService?.({ domain: 'homekit_controller', service: 'pair_setup', target: {}, }); expect(result?.success).toEqual(false); expect(String(result?.error).includes('pair setup is not implemented')).toBeTrue(); await runtime.destroy(); }); export default tap.start();