diff --git a/src/boundaries.ts b/src/boundaries.ts index 6f7668a..5b2e67f 100644 --- a/src/boundaries.ts +++ b/src/boundaries.ts @@ -1,20 +1,27 @@ import { Array, Intersect, Literal, Number, Optional, Record, Static, String, Union } from 'runtypes' -export const NUMBER_TYPES = ['float', 'int', 'uint8', 'uint16', 'uint32', 'uint64'] as const -const NumberTypesLiterals = NUMBER_TYPES.map(Literal) -const NumberTypesBoundary = Union(NumberTypesLiterals[0], ...NumberTypesLiterals) -export type NumberTypes = Static +export const NUMBER_TYPES = [] as const + +const NumberAlikeTypesTypesBoundary = Union( + Literal('bool'), + Literal('float'), + Literal('int'), + Literal('uint8'), + Literal('uint16'), + Literal('uint32'), + Literal('uint64'), +) +export type NumberAlikeTypes = Static export const CharacteristicBoundary = Intersect( Record({ type: String, description: String }), Union( Record({ - format: NumberTypesBoundary, + format: NumberAlikeTypesTypesBoundary, value: Optional(Number), unit: Optional(String), }), Record({ format: Literal('string'), value: String }), - Record({ format: Literal('bool') }), ), ) export type Characteristic = Static diff --git a/src/metrics.ts b/src/metrics.ts index fabbbde..5ea2b7b 100644 --- a/src/metrics.ts +++ b/src/metrics.ts @@ -1,6 +1,5 @@ import type { Accessory, Device, Service } from './boundaries' -import { isType } from './std' -import { NUMBER_TYPES } from './boundaries' +import { assertTypeExhausted, isType } from './std' import { Service as HapService } from 'hap-nodejs' export class Metric { @@ -29,20 +28,33 @@ export function aggregate(devices: Device[], timestamp: Date): Metric[] { ...getServiceLabels(service), } for (const characteristic of service.characteristics) { - for (const numberType of NUMBER_TYPES) { - if (characteristic.format === numberType && typeof characteristic.value !== 'undefined') { - if (METRICS_FILTER.includes(characteristic.description)) { - continue - } - const name = formatName( - uuidToServerName(service.type), - characteristic.description, - characteristic.unit, - ) - if (!METRICS_FILTER.includes(name)) { + const format = characteristic.format + switch (format) { + case 'string': + break + + case 'bool': + case 'float': + case 'int': + case 'uint8': + case 'uint16': + case 'uint32': + case 'uint64': + if (typeof characteristic.value !== 'undefined') { + if (METRICS_FILTER.includes(characteristic.description)) { + break + } + const name = formatName( + uuidToServerName(service.type), + characteristic.description, + characteristic.unit, + ) metrics.push(new Metric(name, characteristic.value, timestamp, labels)) } - } + break + + default: + assertTypeExhausted(format) } } } @@ -117,10 +129,10 @@ function uuidToServerName(uuid: string): string { for (const name of Object.getOwnPropertyNames(HapService)) { const maybeService = (HapService as unknown as Record)[name] if (typeof maybeService === 'function' && 'UUID' in maybeService) { - if ((maybeService as Record)['UUID'] === uuid) { + if ((maybeService as Record)['UUID'] === uuid) { return name } } } throw new Error(`Could not resolve UUID ${uuid} to service`) -} \ No newline at end of file +} diff --git a/src/std.ts b/src/std.ts index 6162dd7..626362e 100644 --- a/src/std.ts +++ b/src/std.ts @@ -9,3 +9,7 @@ interface TypeMap { export function isType(type: T): (v: unknown) => v is TypeMap[T] { return (v: unknown): v is TypeMap[T] => typeof v === type } + +export function assertTypeExhausted(v: never): never { + throw new Error(`Type should be exhausted but is not. Value "${JSON.stringify(v)}`) +} diff --git a/tests/aggregator.test.ts b/tests/aggregator.test.ts index 08ea0ec..231b83f 100644 --- a/tests/aggregator.test.ts +++ b/tests/aggregator.test.ts @@ -69,11 +69,16 @@ describe('Metrics aggregator', () => { } expect(aggregate([tpLink], timestamp)).toEqual([ + new Metric('outlet_on', 0, timestamp, expectedLabelsAccessory1), + new Metric('outlet_in_use', 0, timestamp, expectedLabelsAccessory1), new Metric('outlet_amperes_a', 0.03, timestamp, expectedLabelsAccessory1), new Metric('outlet_total_consumption_kwh', 0.051, timestamp, expectedLabelsAccessory1), new Metric('outlet_apparent_power_va', 53248.8, timestamp, expectedLabelsAccessory1), new Metric('outlet_volts_v', 230.8, timestamp, expectedLabelsAccessory1), new Metric('outlet_consumption_w', 0, timestamp, expectedLabelsAccessory1), + + new Metric('outlet_on', 0, timestamp, expectedLabelsAccessory2), + new Metric('outlet_in_use', 0, timestamp, expectedLabelsAccessory2), new Metric('outlet_amperes_a', 0.03, timestamp, expectedLabelsAccessory2), new Metric('outlet_total_consumption_kwh', 13.025, timestamp, expectedLabelsAccessory2), new Metric('outlet_apparent_power_va', 53365.6, timestamp, expectedLabelsAccessory2), @@ -145,6 +150,7 @@ describe('Metrics aggregator', () => { new Metric('speaker_active', 1, timestamp, expectedLabels4), new Metric('speaker_volume_control_type', 3, timestamp, expectedLabels4), + new Metric('speaker_mute', 0, timestamp, expectedLabels4), new Metric('speaker_volume_percentage', 50, timestamp, expectedLabels4), ]) })