Use code generation for UUID service map (#14)

Instead of introspecting *hap-nodejs* at runtime for UUID to service
mapping, generate the map during build time.
This commit is contained in:
Lars Strojny 2022-11-10 11:07:31 +01:00 committed by GitHub
parent 38bf24df90
commit f6bc8ca90c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 207 additions and 25 deletions

View file

@ -43,7 +43,7 @@ module.exports = {
}, },
}, },
{ {
files: ['.eslintrc.js', 'jest.config.js', 'prettier.config.js'], files: ['**/*.js'],
env: { env: {
node: true, node: true,
browser: false, browser: false,

View file

@ -142,3 +142,4 @@ prettier.config.js
.eslintrc.js .eslintrc.js
jest.config.js jest.config.js
nodemon.json nodemon.json
/code-generation/

31
code-generation/hap-gen.js Executable file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env node
const hap = require('hap-nodejs')
const { format } = require('prettier')
const prettierConfig = require('../prettier.config')
const { writeFileSync } = require('fs')
const { join, basename } = require('path')
const uuidToServiceMap = {}
const serviceToUuidMap = {}
for (const [name, service] of Object.entries(hap.Service)) {
if (typeof service !== 'function' || typeof service.UUID !== 'string') {
console.log(`Skipping ${typeof service} ${name}`)
continue
}
uuidToServiceMap[service.UUID] = name
serviceToUuidMap[name] = service.UUID
}
const code = format(
`
// Auto-generated by "${join(basename(__dirname), basename(__filename))}", dont manually edit
export const Uuids: Record<string,string> = ${JSON.stringify(uuidToServiceMap)} as const
export const Services: Record<string,string> = ${JSON.stringify(serviceToUuidMap)} as const
`,
{ filepath: 'codegen.ts', ...prettierConfig },
)
writeFileSync(join(__dirname, '../src/generated/services.ts'), code)

1
package-lock.json generated
View file

@ -23,6 +23,7 @@
"eslint-import-resolver-typescript": "^3.5.2", "eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"hap-nodejs": "^0.10.4",
"homebridge": "^1.3.5", "homebridge": "^1.3.5",
"homebridge-cmdswitch2": "^0.2.10", "homebridge-cmdswitch2": "^0.2.10",
"jest": "^29.3.0", "jest": "^29.3.0",

View file

@ -16,13 +16,14 @@
}, },
"main": "dist/src/index.js", "main": "dist/src/index.js",
"scripts": { "scripts": {
"lint": "ifNotCi() { test \"$CI\" && echo \"$2\" || echo \"$1\"; }; `npm bin`/prettier --ignore-path=.gitignore `ifNotCi --write --check` '**/**.{ts,js,json}' && `npm bin`/eslint `ifNotCi --fix` --ignore-path=.gitignore '**/**.{ts,js,json}'", "lint": "ifNotCi() { test \"$CI\" && echo \"$2\" || echo \"$1\"; }; `npm bin`/tsc --noEmit && `npm bin`/prettier --ignore-path=.gitignore `ifNotCi --write --check` '**/**.{ts,js,json}' && `npm bin`/eslint `ifNotCi --fix` --ignore-path=.gitignore '**/**.{ts,js,json}'",
"start": "npm run build && npm run link && nodemon", "start": "npm run build && npm run link && nodemon",
"test": "ifNotCi() { test \"$CI\" && echo \"$2\" || echo \"$1\"; }; `npm bin`/jest `ifNotCi --watchAll`", "test": "ifNotCi() { test \"$CI\" && echo \"$2\" || echo \"$1\"; }; npm run code-generation && `npm bin`/jest `ifNotCi --watchAll`",
"link": "npm install --no-save file:///$PWD/", "link": "npm install --no-save file:///$PWD/",
"build": "rimraf ./dist && tsc", "build": "rimraf ./dist && npm run code-generation && tsc",
"prepublishOnly": "npm run lint && npm run build", "code-generation": "./code-generation/hap-gen.js",
"release": "release() { test \"$1\" && test `git rev-parse --abbrev-ref HEAD` == \"develop\" && git pull origin develop --rebase && npm version $1 && npm publish && git push origin develop && git push origin --tags; }; release" "prepublishOnly": "npm run code-generation && npm run lint && npm run build",
"release": "release() { test \"$1\" && test `git rev-parse --abbrev-ref HEAD` == \"develop\" && npm run code-generation && git pull origin develop --rebase && npm version $1 && npm publish && git push origin develop && git push origin --tags; }; release"
}, },
"keywords": [ "keywords": [
"homebridge-plugin", "homebridge-plugin",
@ -40,6 +41,7 @@
"eslint-import-resolver-typescript": "^3.5.2", "eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"hap-nodejs": "^0.10.4",
"homebridge": "^1.3.5", "homebridge": "^1.3.5",
"homebridge-cmdswitch2": "^0.2.10", "homebridge-cmdswitch2": "^0.2.10",
"jest": "^29.3.0", "jest": "^29.3.0",

160
src/generated/services.ts Normal file
View file

@ -0,0 +1,160 @@
// Auto-generated by "code-generation/hap-gen.js", dont manually edit
export const Uuids: Record<string, string> = {
'00000260-0000-1000-8000-0026BB765291': 'AccessCode',
'000000DA-0000-1000-8000-0026BB765291': 'AccessControl',
'0000003E-0000-1000-8000-0026BB765291': 'AccessoryInformation',
'00000270-0000-1000-8000-0026BB765291': 'AccessoryMetrics',
'00000239-0000-1000-8000-0026BB765291': 'AccessoryRuntimeInformation',
'000000BB-0000-1000-8000-0026BB765291': 'AirPurifier',
'0000008D-0000-1000-8000-0026BB765291': 'AirQualitySensor',
'00000267-0000-1000-8000-0026BB765291': 'AssetUpdate',
'0000026A-0000-1000-8000-0026BB765291': 'Assistant',
'00000127-0000-1000-8000-0026BB765291': 'AudioStreamManagement',
'00000096-0000-1000-8000-0026BB765291': 'Battery',
'000000A1-0000-1000-8000-0026BB765291': 'BridgeConfiguration',
'00000062-0000-1000-8000-0026BB765291': 'BridgingState',
'00000111-0000-1000-8000-0026BB765291': 'CameraControl',
'0000021A-0000-1000-8000-0026BB765291': 'CameraOperatingMode',
'00000204-0000-1000-8000-0026BB765291': 'CameraRecordingManagement',
'00000110-0000-1000-8000-0026BB765291': 'CameraRTPStreamManagement',
'00000097-0000-1000-8000-0026BB765291': 'CarbonDioxideSensor',
'0000007F-0000-1000-8000-0026BB765291': 'CarbonMonoxideSensor',
'0000005A-0000-1000-8000-0026BB765291': 'CloudRelay',
'00000080-0000-1000-8000-0026BB765291': 'ContactSensor',
'00000129-0000-1000-8000-0026BB765291': 'DataStreamTransportManagement',
'00000237-0000-1000-8000-0026BB765291': 'Diagnostics',
'00000081-0000-1000-8000-0026BB765291': 'Door',
'00000121-0000-1000-8000-0026BB765291': 'Doorbell',
'00000040-0000-1000-8000-0026BB765291': 'Fan',
'000000B7-0000-1000-8000-0026BB765291': 'Fanv2',
'000000D7-0000-1000-8000-0026BB765291': 'Faucet',
'000000BA-0000-1000-8000-0026BB765291': 'FilterMaintenance',
'00000041-0000-1000-8000-0026BB765291': 'GarageDoorOpener',
'000000BC-0000-1000-8000-0026BB765291': 'HeaterCooler',
'000000BD-0000-1000-8000-0026BB765291': 'HumidifierDehumidifier',
'00000082-0000-1000-8000-0026BB765291': 'HumiditySensor',
'000000D9-0000-1000-8000-0026BB765291': 'InputSource',
'000000CF-0000-1000-8000-0026BB765291': 'IrrigationSystem',
'00000083-0000-1000-8000-0026BB765291': 'LeakSensor',
'00000043-0000-1000-8000-0026BB765291': 'Lightbulb',
'00000084-0000-1000-8000-0026BB765291': 'LightSensor',
'00000044-0000-1000-8000-0026BB765291': 'LockManagement',
'00000045-0000-1000-8000-0026BB765291': 'LockMechanism',
'00000112-0000-1000-8000-0026BB765291': 'Microphone',
'00000085-0000-1000-8000-0026BB765291': 'MotionSensor',
'00000266-0000-1000-8000-0026BB765291': 'NFCAccess',
'00000086-0000-1000-8000-0026BB765291': 'OccupancySensor',
'00000047-0000-1000-8000-0026BB765291': 'Outlet',
'00000055-0000-1000-8000-0026BB765291': 'Pairing',
'00000221-0000-1000-8000-0026BB765291': 'PowerManagement',
'000000A2-0000-1000-8000-0026BB765291': 'ProtocolInformation',
'0000007E-0000-1000-8000-0026BB765291': 'SecuritySystem',
'000000CC-0000-1000-8000-0026BB765291': 'ServiceLabel',
'00000133-0000-1000-8000-0026BB765291': 'Siri',
'00000253-0000-1000-8000-0026BB765291': 'SiriEndpoint',
'000000B9-0000-1000-8000-0026BB765291': 'Slats',
'00000228-0000-1000-8000-0026BB765291': 'SmartSpeaker',
'00000087-0000-1000-8000-0026BB765291': 'SmokeSensor',
'00000113-0000-1000-8000-0026BB765291': 'TelevisionSpeaker',
'00000088-0000-1000-8000-0026BB765291': 'StatefulProgrammableSwitch',
'00000089-0000-1000-8000-0026BB765291': 'StatelessProgrammableSwitch',
'00000049-0000-1000-8000-0026BB765291': 'Switch',
'00000125-0000-1000-8000-0026BB765291': 'TargetControl',
'00000122-0000-1000-8000-0026BB765291': 'TargetControlManagement',
'000000D8-0000-1000-8000-0026BB765291': 'Television',
'0000008A-0000-1000-8000-0026BB765291': 'TemperatureSensor',
'0000004A-0000-1000-8000-0026BB765291': 'Thermostat',
'00000701-0000-1000-8000-0026BB765291': 'ThreadTransport',
'00000099-0000-1000-8000-0026BB765291': 'TimeInformation',
'00000203-0000-1000-8000-0026BB765291': 'TransferTransportManagement',
'00000056-0000-1000-8000-0026BB765291': 'Tunnel',
'000000D0-0000-1000-8000-0026BB765291': 'Valve',
'0000020A-0000-1000-8000-0026BB765291': 'WiFiRouter',
'0000020F-0000-1000-8000-0026BB765291': 'WiFiSatellite',
'0000022A-0000-1000-8000-0026BB765291': 'WiFiTransport',
'0000008B-0000-1000-8000-0026BB765291': 'Window',
'0000008C-0000-1000-8000-0026BB765291': 'WindowCovering',
} as const
export const Services: Record<string, string> = {
AccessCode: '00000260-0000-1000-8000-0026BB765291',
AccessControl: '000000DA-0000-1000-8000-0026BB765291',
AccessoryInformation: '0000003E-0000-1000-8000-0026BB765291',
AccessoryMetrics: '00000270-0000-1000-8000-0026BB765291',
AccessoryRuntimeInformation: '00000239-0000-1000-8000-0026BB765291',
AirPurifier: '000000BB-0000-1000-8000-0026BB765291',
AirQualitySensor: '0000008D-0000-1000-8000-0026BB765291',
AssetUpdate: '00000267-0000-1000-8000-0026BB765291',
Assistant: '0000026A-0000-1000-8000-0026BB765291',
AudioStreamManagement: '00000127-0000-1000-8000-0026BB765291',
BatteryService: '00000096-0000-1000-8000-0026BB765291',
Battery: '00000096-0000-1000-8000-0026BB765291',
BridgeConfiguration: '000000A1-0000-1000-8000-0026BB765291',
BridgingState: '00000062-0000-1000-8000-0026BB765291',
CameraControl: '00000111-0000-1000-8000-0026BB765291',
CameraOperatingMode: '0000021A-0000-1000-8000-0026BB765291',
CameraEventRecordingManagement: '00000204-0000-1000-8000-0026BB765291',
CameraRecordingManagement: '00000204-0000-1000-8000-0026BB765291',
CameraRTPStreamManagement: '00000110-0000-1000-8000-0026BB765291',
CarbonDioxideSensor: '00000097-0000-1000-8000-0026BB765291',
CarbonMonoxideSensor: '0000007F-0000-1000-8000-0026BB765291',
Relay: '0000005A-0000-1000-8000-0026BB765291',
CloudRelay: '0000005A-0000-1000-8000-0026BB765291',
ContactSensor: '00000080-0000-1000-8000-0026BB765291',
DataStreamTransportManagement: '00000129-0000-1000-8000-0026BB765291',
Diagnostics: '00000237-0000-1000-8000-0026BB765291',
Door: '00000081-0000-1000-8000-0026BB765291',
Doorbell: '00000121-0000-1000-8000-0026BB765291',
Fan: '00000040-0000-1000-8000-0026BB765291',
Fanv2: '000000B7-0000-1000-8000-0026BB765291',
Faucet: '000000D7-0000-1000-8000-0026BB765291',
FilterMaintenance: '000000BA-0000-1000-8000-0026BB765291',
GarageDoorOpener: '00000041-0000-1000-8000-0026BB765291',
HeaterCooler: '000000BC-0000-1000-8000-0026BB765291',
HumidifierDehumidifier: '000000BD-0000-1000-8000-0026BB765291',
HumiditySensor: '00000082-0000-1000-8000-0026BB765291',
InputSource: '000000D9-0000-1000-8000-0026BB765291',
IrrigationSystem: '000000CF-0000-1000-8000-0026BB765291',
LeakSensor: '00000083-0000-1000-8000-0026BB765291',
Lightbulb: '00000043-0000-1000-8000-0026BB765291',
LightSensor: '00000084-0000-1000-8000-0026BB765291',
LockManagement: '00000044-0000-1000-8000-0026BB765291',
LockMechanism: '00000045-0000-1000-8000-0026BB765291',
Microphone: '00000112-0000-1000-8000-0026BB765291',
MotionSensor: '00000085-0000-1000-8000-0026BB765291',
NFCAccess: '00000266-0000-1000-8000-0026BB765291',
OccupancySensor: '00000086-0000-1000-8000-0026BB765291',
Outlet: '00000047-0000-1000-8000-0026BB765291',
Pairing: '00000055-0000-1000-8000-0026BB765291',
PowerManagement: '00000221-0000-1000-8000-0026BB765291',
ProtocolInformation: '000000A2-0000-1000-8000-0026BB765291',
SecuritySystem: '0000007E-0000-1000-8000-0026BB765291',
ServiceLabel: '000000CC-0000-1000-8000-0026BB765291',
Siri: '00000133-0000-1000-8000-0026BB765291',
SiriEndpoint: '00000253-0000-1000-8000-0026BB765291',
Slat: '000000B9-0000-1000-8000-0026BB765291',
Slats: '000000B9-0000-1000-8000-0026BB765291',
SmartSpeaker: '00000228-0000-1000-8000-0026BB765291',
SmokeSensor: '00000087-0000-1000-8000-0026BB765291',
Speaker: '00000113-0000-1000-8000-0026BB765291',
StatefulProgrammableSwitch: '00000088-0000-1000-8000-0026BB765291',
StatelessProgrammableSwitch: '00000089-0000-1000-8000-0026BB765291',
Switch: '00000049-0000-1000-8000-0026BB765291',
TargetControl: '00000125-0000-1000-8000-0026BB765291',
TargetControlManagement: '00000122-0000-1000-8000-0026BB765291',
Television: '000000D8-0000-1000-8000-0026BB765291',
TelevisionSpeaker: '00000113-0000-1000-8000-0026BB765291',
TemperatureSensor: '0000008A-0000-1000-8000-0026BB765291',
Thermostat: '0000004A-0000-1000-8000-0026BB765291',
ThreadTransport: '00000701-0000-1000-8000-0026BB765291',
TimeInformation: '00000099-0000-1000-8000-0026BB765291',
TransferTransportManagement: '00000203-0000-1000-8000-0026BB765291',
TunneledBTLEAccessoryService: '00000056-0000-1000-8000-0026BB765291',
Tunnel: '00000056-0000-1000-8000-0026BB765291',
Valve: '000000D0-0000-1000-8000-0026BB765291',
WiFiRouter: '0000020A-0000-1000-8000-0026BB765291',
WiFiSatellite: '0000020F-0000-1000-8000-0026BB765291',
WiFiTransport: '0000022A-0000-1000-8000-0026BB765291',
Window: '0000008B-0000-1000-8000-0026BB765291',
WindowCovering: '0000008C-0000-1000-8000-0026BB765291',
} as const

View file

@ -1,7 +1,6 @@
import type { Accessory, Device, Service } from './boundaries/hap' import type { Accessory, Device, Service } from './boundaries/hap'
import { Uuids } from './generated/services'
import { assertTypeExhausted, isType } from './std' import { assertTypeExhausted, isType } from './std'
// eslint-disable-next-line import/no-extraneous-dependencies
import { Service as HapService } from 'hap-nodejs'
export class Metric { export class Metric {
constructor( constructor(
@ -48,7 +47,7 @@ export function aggregate(devices: Device[], timestamp: Date): Metric[] {
break break
} }
const name = formatName( const name = formatName(
uuidToServerName(service.type), Uuids[service.type] || 'custom',
characteristic.description, characteristic.description,
characteristic.unit, characteristic.unit,
) )
@ -127,15 +126,3 @@ function getServiceLabels(service: Service): Record<string, string> {
return labels return labels
} }
function uuidToServerName(uuid: string): string {
for (const name of Object.getOwnPropertyNames(HapService)) {
const maybeService = (HapService as unknown as Record<string, unknown>)[name]
if (typeof maybeService === 'function' && 'UUID' in maybeService) {
if ((maybeService as Record<string, string>)['UUID'] === uuid) {
return name
}
}
}
return 'custom'
}

View file

@ -149,10 +149,10 @@ describe('Metrics aggregator', () => {
new Metric('input_source_current_visibility_state', 0, timestamp, expectedLabels3), new Metric('input_source_current_visibility_state', 0, timestamp, expectedLabels3),
new Metric('input_source_target_visibility_state', 0, timestamp, expectedLabels3), new Metric('input_source_target_visibility_state', 0, timestamp, expectedLabels3),
new Metric('speaker_active', 1, timestamp, expectedLabels4), new Metric('television_speaker_active', 1, timestamp, expectedLabels4),
new Metric('speaker_volume_control_type', 3, timestamp, expectedLabels4), new Metric('television_speaker_volume_control_type', 3, timestamp, expectedLabels4),
new Metric('speaker_mute', 0, timestamp, expectedLabels4), new Metric('television_speaker_mute', 0, timestamp, expectedLabels4),
new Metric('speaker_volume_percentage', 50, timestamp, expectedLabels4), new Metric('television_speaker_volume_percentage', 50, timestamp, expectedLabels4),
]) ])
}) })