diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 6070426fc..10527b9c7 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -143,6 +143,8 @@ export const enum CharacteristicWarningType { TIMEOUT_WRITE = "timeout-write", SLOW_READ = "slow-read", TIMEOUT_READ = "timeout-read", + WARN_MESSAGE = "warn-message", + ERROR_MESSAGE = "error-message", } /** @@ -255,7 +257,7 @@ export const enum AccessoryEventTypes { PAIRED = "paired", UNPAIRED = "unpaired", - CHARACTERISTIC_WARNING = "characteristic-warning", + CHARACTERISTIC_WARNING = "characteristic-warning-v2", } export declare interface Accessory { @@ -268,7 +270,7 @@ export declare interface Accessory { on(event: "paired", listener: () => void): this; on(event: "unpaired", listener: () => void): this; - on(event: "characteristic-warning", listener: (type: CharacteristicWarningType, iid: number) => void): this; + on(event: "characteristic-warning-v2", listener: (characteristic: Characteristic, type: CharacteristicWarningType, message?: string) => void): this; emit(event: "identify", paired: boolean, callback: VoidCallback): boolean; @@ -280,7 +282,7 @@ export declare interface Accessory { emit(event: "paired"): boolean; emit(event: "unpaired"): boolean; - emit(event: "characteristic-warning", type: CharacteristicWarningType, iid: number): boolean; + emit(event: "characteristic-warning-v2", characteristic: Characteristic, type: CharacteristicWarningType, message: string): boolean; } /** @@ -372,7 +374,7 @@ export class Accessory extends EventEmitter { } } - addService = (serviceParam: Service | typeof Service, ...constructorArgs: any[]) => { + public addService(serviceParam: Service | typeof Service, ...constructorArgs: any[]): Service { // service might be a constructor like `Service.AccessoryInformation` instead of an instance // of Service. Coerce if necessary. const service: Service = typeof serviceParam === 'function' @@ -412,8 +414,7 @@ export class Accessory extends EventEmitter { this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); } - service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, this.handleServiceConfigurationChangeEvent.bind(this, service)); - service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, this, service)); + this.setupServiceEventHandlers(service); return service; } @@ -421,9 +422,9 @@ export class Accessory extends EventEmitter { /** * @deprecated use {@link Service.setPrimaryService} directly */ - setPrimaryService = (service: Service) => { + public setPrimaryService(service: Service): void { service.setPrimaryService(); - }; + } public removeService(service: Service): void { const index = this.services.indexOf(service); @@ -452,18 +453,19 @@ export class Accessory extends EventEmitter { } } - getService = >(name: string | T) => { - for (let index in this.services) { - const service = this.services[index]; - - if (typeof name === 'string' && (service.displayName === name || service.name === name || service.subtype === name)) + public getService>(name: string | T): Service | undefined { + for (const service of this.services) { + if (typeof name === 'string' && (service.displayName === name || service.name === name || service.subtype === name)) { return service; - else if (typeof name === 'function' && ((service instanceof name) || (name.UUID === service.UUID))) + } else if (typeof name === 'function' && ((service instanceof name) || (name.UUID === service.UUID))) { return service; + } } + + return undefined; } - getServiceById>(uuid: string | T, subType: string): Service | undefined { + public getServiceById>(uuid: string | T, subType: string): Service | undefined { for (const index in this.services) { const service = this.services[index]; @@ -483,11 +485,14 @@ export class Accessory extends EventEmitter { * * @returns the primary accessory */ - getPrimaryAccessory = (): Accessory => { + public getPrimaryAccessory = (): Accessory => { return this.bridged? this.bridge!: this; } - updateReachability = (reachable: boolean) => { + /** + * @deprecated Not supported anymore + */ + public updateReachability(reachable: boolean): void { if (!this.bridged) throw new Error("Cannot update reachability on non-bridged accessory!"); this.reachable = reachable; @@ -529,7 +534,7 @@ export class Accessory extends EventEmitter { return accessory; } - addBridgedAccessories = (accessories: Accessory[]) => { + public addBridgedAccessories(accessories: Accessory[]): void { for (let index in accessories) { const accessory = accessories[index]; this.addBridgedAccessory(accessory, true); @@ -538,7 +543,7 @@ export class Accessory extends EventEmitter { this.enqueueConfigurationUpdate(); } - removeBridgedAccessory = (accessory: Accessory, deferUpdate: boolean) => { + public removeBridgedAccessory(accessory: Accessory, deferUpdate: boolean): void { if (accessory._isBridge) throw new Error("Cannot Bridge another Bridge!"); @@ -563,7 +568,7 @@ export class Accessory extends EventEmitter { } } - removeBridgedAccessories = (accessories: Accessory[]) => { + public removeBridgedAccessories(accessories: Accessory[]): void { for (let index in accessories) { const accessory = accessories[index]; this.removeBridgedAccessory(accessory, true); @@ -572,7 +577,7 @@ export class Accessory extends EventEmitter { this.enqueueConfigurationUpdate(); } - removeAllBridgedAccessories = () => { + public removeAllBridgedAccessories(): void { for (let i = this.bridgedAccessories.length - 1; i >= 0; i --) { this.removeBridgedAccessory(this.bridgedAccessories[i], true); } @@ -1027,7 +1032,7 @@ export class Accessory extends EventEmitter { if (info.setupID) { this._setupID = info.setupID; } else if (this._accessoryInfo.setupID === undefined || this._accessoryInfo.setupID === "") { - this._setupID = this._generateSetupID(); + this._setupID = Accessory._generateSetupID(); } else { this._setupID = this._accessoryInfo.setupID; } @@ -1286,12 +1291,10 @@ export class Accessory extends EventEmitter { const aid = parseInt(split[0]); const iid = parseInt(split[1]); - const accessory = this.getAccessoryByAID(aid); - let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.SLOW_READ, iid); - if (!emitted) { - const characteristic = accessory?.getCharacteristicByIID(iid); - console.warn(`The read handler for the characteristic '${characteristic?.displayName || iid}' on the accessory '${accessory?.displayName}' was slow to respond!`); - } + const accessory = this.getAccessoryByAID(aid)!; + const characteristic = accessory.getCharacteristicByIID(iid)!; + characteristic.characteristicWarning("The read handler for the characteristic '" + characteristic.displayName + + "' on the accessory '" + accessory.displayName + "' was slow to respond!", CharacteristicWarningType.SLOW_READ); } // after a total of 10s we do not longer wait for a request to appear and just return status code timeout @@ -1303,13 +1306,10 @@ export class Accessory extends EventEmitter { const aid = parseInt(split[0]); const iid = parseInt(split[1]); - const accessory = this.getAccessoryByAID(aid); - let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.TIMEOUT_READ, iid); - if (!emitted) { - const characteristic = accessory?.getCharacteristicByIID(iid); - console.error("The read handler for the characteristic '" + (characteristic?.displayName || iid) + "' on the accessory '" + accessory?.displayName + - "' didn't respond at all!. Please check that you properly call the callback!"); - } + const accessory = this.getAccessoryByAID(aid)!; + const characteristic = accessory.getCharacteristicByIID(iid)!; + characteristic.characteristicWarning("The read handler for the characteristic '" + characteristic.displayName + "' on the accessory '" + accessory.displayName + + "' didn't respond at all!. Please check that you properly call the callback!", CharacteristicWarningType.TIMEOUT_READ); characteristics.push({ aid: aid, @@ -1455,12 +1455,10 @@ export class Accessory extends EventEmitter { const aid = parseInt(split[0]); const iid = parseInt(split[1]); - const accessory = this.getAccessoryByAID(aid); - let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.SLOW_WRITE, iid); - if (!emitted) { - const characteristic = accessory?.getCharacteristicByIID(iid); - console.warn(`The write handler for the characteristic '${characteristic?.displayName || iid}' on the accessory '${accessory?.displayName}' was slow to respond!`); - } + const accessory = this.getAccessoryByAID(aid)!; + const characteristic = accessory.getCharacteristicByIID(iid)!; + characteristic.characteristicWarning("The write handler for the characteristic '" + characteristic.displayName + + "' on the accessory '" + accessory.displayName + "' was slow to respond!", CharacteristicWarningType.SLOW_WRITE); } // after a total of 10s we do not longer wait for a request to appear and just return status code timeout @@ -1472,13 +1470,10 @@ export class Accessory extends EventEmitter { const aid = parseInt(split[0]); const iid = parseInt(split[1]); - const accessory = this.getAccessoryByAID(aid); - let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.TIMEOUT_WRITE, iid); - if (!emitted) { - const characteristic = accessory?.getCharacteristicByIID(iid); - console.error("The write handler for the characteristic '" + (characteristic?.displayName || iid) + "' on the accessory '" + accessory?.displayName + - "' didn't respond at all!. Please check that you properly call the callback!"); - } + const accessory = this.getAccessoryByAID(aid)!; + const characteristic = accessory.getCharacteristicByIID(iid)!; + characteristic.characteristicWarning("The write handler for the characteristic '" + characteristic.displayName + "' on the accessory '" + accessory.displayName + + "' didn't respond at all!. Please check that you properly call the callback!", CharacteristicWarningType.TIMEOUT_WRITE); characteristics.push({ aid: aid, @@ -1727,15 +1722,18 @@ export class Accessory extends EventEmitter { } } - _setupService = (service: Service) => { + private setupServiceEventHandlers(service: Service): void { service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, this.handleServiceConfigurationChangeEvent.bind(this, service)); service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, this, service)); + service.on(ServiceEventTypes.CHARACTERISTIC_WARNING, (characteristic, type, message) => { + this.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, characteristic, type, message); + }); } - _sideloadServices = (targetServices: Service[]) => { + private _sideloadServices(targetServices: Service[]): void { for (let index in targetServices) { const target = targetServices[index]; - this._setupService(target); + this.setupServiceEventHandlers(target); } this.services = targetServices.slice(); @@ -1752,7 +1750,7 @@ export class Accessory extends EventEmitter { }); } - _generateSetupID = () => { + private static _generateSetupID(): string { const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const bytes = crypto.randomBytes(4); let setupID = ''; diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index f34a8dd3f..4905b3de5 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -3,6 +3,7 @@ import createDebug from "debug"; import { EventEmitter } from "events"; import { CharacteristicJsonObject } from "../internal-types"; import { CharacteristicValue, Nullable, VoidCallback, } from '../types'; +import { CharacteristicWarningType } from "./Accessory"; import { AccessControlLevel, AccessoryFlags, @@ -404,6 +405,10 @@ export const enum CharacteristicEventTypes { * @internal */ UNSUBSCRIBE = "unsubscribe", + /** + * @internal + */ + CHARACTERISTIC_WARNING = "characteristic-warning", } export type CharacteristicGetCallback = (status?: HAPStatus | null | Error, value?: Nullable) => void; @@ -426,6 +431,10 @@ export declare interface Characteristic { * @internal */ on(event: "unsubscribe", listener: VoidCallback): this; + /** + * @internal + */ + on(event: "characteristic-warning", listener: (type: CharacteristicWarningType, message: string) => void): this; /** * @internal @@ -447,6 +456,10 @@ export declare interface Characteristic { * @internal */ emit(event: "unsubscribe"): boolean; + /** + * @internal + */ + emit(event: "characteristic-warning", type: CharacteristicWarningType, message: string): boolean; } @@ -752,7 +765,7 @@ export class Characteristic extends EventEmitter { */ public onGet(handler: CharacteristicGetHandler) { if (typeof handler !== 'function' || handler.length !== 0) { - console.warn(`[${this.displayName}] .onGet handler must be a function with exactly zero input arguments.`); + this.characteristicWarning(`.onGet handler must be a function with exactly zero input arguments.`); return this; } this.getHandler = handler; @@ -775,7 +788,7 @@ export class Characteristic extends EventEmitter { */ public onSet(handler: CharacteristicSetHandler) { if (typeof handler !== 'function' || handler.length !== 1) { - console.warn(`[${this.displayName}] .onSet handler must be a function with exactly one input argument.`); + this.characteristicWarning(`.onSet handler must be a function with exactly one input argument.`); return this; } this.setHandler = handler; @@ -819,7 +832,7 @@ export class Characteristic extends EventEmitter { if (props.maxLen !== undefined) { if (props.maxLen != null) { if (props.maxLen > 256) { - console.warn(""); + this.characteristicWarning("setProps: string maxLen cannot be bigger than 256!"); props.maxLen = 256; } this.props.maxLen = props.maxLen; @@ -1000,7 +1013,7 @@ export class Characteristic extends EventEmitter { if (this.getHandler) { if (this.listeners(CharacteristicEventTypes.GET).length > 0) { - console.warn(`[${this.displayName}] Ignoring on('get') handler as onGet handler was defined instead.`); + this.characteristicWarning(`Ignoring on('get') handler as onGet handler was defined instead.`); } try { @@ -1010,7 +1023,7 @@ export class Characteristic extends EventEmitter { try { value = this.validateUserInput(value); } catch (error) { - console.warn(`[${this.displayName}] An illegal value was supplied by the read handler for characteristic: ${error.message}`); + this.characteristicWarning(`An illegal value was supplied by the read handler for characteristic: ${error.message}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; return Promise.reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE) } @@ -1027,7 +1040,7 @@ export class Characteristic extends EventEmitter { } else if (error instanceof HapStatusError) { this.status = error.hapStatus; } else { - console.warn(`[${this.displayName}] Unhandled error thrown inside read handler for characteristic: ${error.stack}`); + this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error.stack}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; } throw this.status; @@ -1071,7 +1084,7 @@ export class Characteristic extends EventEmitter { } }), context, connection); } catch (error) { - console.warn(`[${this.displayName}] Unhandled error thrown inside read handler for characteristic: ${error.stack}`); + this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error.stack}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE); } @@ -1109,7 +1122,7 @@ export class Characteristic extends EventEmitter { if (this.setHandler) { if (this.listeners(CharacteristicEventTypes.SET).length > 0) { - console.warn(`[${this.displayName}] Ignoring on('set') handler as onSet handler was defined instead.`); + this.characteristicWarning(`Ignoring on('set') handler as onSet handler was defined instead.`); } try { @@ -1120,7 +1133,7 @@ export class Characteristic extends EventEmitter { this.value = writeResponse; } else { if (writeResponse != null) { - console.warn(`[${this.displayName}] SET handler returned write response value, though the characteristic doesn't support write response!`); + this.characteristicWarning(`SET handler returned write response value, though the characteristic doesn't support write response!`); } this.value = value; @@ -1136,7 +1149,7 @@ export class Characteristic extends EventEmitter { } else if (error instanceof HapStatusError) { this.status = error.hapStatus; } else { - console.warn(`[${this.displayName}] Unhandled error thrown inside write handler for characteristic: ${error.stack}`); + this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error.stack}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; } throw this.status; @@ -1174,7 +1187,7 @@ export class Characteristic extends EventEmitter { resolve(writeResponse); } else { if (writeResponse != null) { - console.warn(`[${this.displayName}] SET handler returned write response value, though the characteristic doesn't support write response!`); + this.characteristicWarning(`SET handler returned write response value, though the characteristic doesn't support write response!`); } this.value = value; resolve(); @@ -1185,7 +1198,7 @@ export class Characteristic extends EventEmitter { } }), context, connection); } catch (error) { - console.warn(`[${this.displayName}] Unhandled error thrown inside write handler for characteristic: ${error.stack}`); + this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error.stack}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE); } @@ -1404,11 +1417,11 @@ export class Characteristic extends EventEmitter { */ private validateUserInput(value?: Nullable): Nullable { if (value === undefined) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: undefined! This might throw errors in the future!`); + this.characteristicWarning(`characteristic was supplied illegal value: undefined! This might throw errors in the future!`); return this.value; // don't change the value } else if (value === null) { if (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID) { // mirrors the statement in case: Formats.STRING - console.error(new Error(`[${this.displayName}] characteristic must have a non null value otherwise HomeKit will reject this accessory. Ignoring new value.`).stack); + this.characteristicWarning(new Error(`characteristic must have a non null value otherwise HomeKit will reject this accessory. Ignoring new value.`).stack + "", CharacteristicWarningType.ERROR_MESSAGE); return this.value; // don't change the value } @@ -1436,7 +1449,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + this.characteristicWarning(`characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); value = parseInt(value, 10); } else if (typeof value !== "number") { throw new Error("characteristic value expected number and received " + typeof value); @@ -1450,7 +1463,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of float. Supplying illegal values will throw errors in the future!`); + this.characteristicWarning(`characteristic was supplied illegal value: string instead of float. Supplying illegal values will throw errors in the future!`); value = parseFloat(value); } else if (typeof value !== "number") { throw new Error("characteristic value expected float and received " + typeof value); @@ -1468,7 +1481,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + this.characteristicWarning(`characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); value = parseInt(value, 10); } else if (typeof value !== "number") { throw new Error("characteristic value expected number and received " + typeof value); @@ -1482,7 +1495,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + this.characteristicWarning(`characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); value = parseInt(value, 10); } else if (typeof value !== "number") { throw new Error("characteristic value expected number and received " + typeof value); @@ -1496,7 +1509,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + this.characteristicWarning(`characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); value = parseInt(value, 10); } else if (typeof value !== "number") { throw new Error("characteristic value expected number and received " + typeof value); @@ -1510,7 +1523,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + this.characteristicWarning(`characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); value = parseInt(value, 10); } else if (typeof value !== "number") { throw new Error("characteristic value expected number and received " + typeof value); @@ -1522,20 +1535,20 @@ export class Characteristic extends EventEmitter { } case Formats.STRING: { if (typeof value === "number") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: number instead of string. Supplying illegal values will throw errors in the future!`); + this.characteristicWarning(`characteristic was supplied illegal value: number instead of string. Supplying illegal values will throw errors in the future!`); value = String(value); } else if (typeof value !== "string") { throw new Error("characteristic value expected string and received " + (typeof value)); } if (value.length <= 1 && (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID)) { // mirrors the case value = null at the beginning - console.error(new Error(`[${this.displayName}] characteristic must have a length of more than 1 character otherwise HomeKit will reject this accessory. Ignoring new value.`).stack); + this.characteristicWarning(new Error(`[${this.displayName}] characteristic must have a length of more than 1 character otherwise HomeKit will reject this accessory. Ignoring new value.`).stack + "", CharacteristicWarningType.ERROR_MESSAGE); return this.value; // just return the current value } const maxLength = this.props.maxLen != null? this.props.maxLen: 64; // default is 64 (max is 256 which is set in setProps) if (value.length > maxLength) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}.`); + this.characteristicWarning(`characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}.`); value = value.substring(0, maxLength); } @@ -1557,11 +1570,11 @@ export class Characteristic extends EventEmitter { if (typeof value === "number") { if (numericMin != null && value < numericMin) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}.`); + this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}.`); value = numericMin; } if (numericMax != null && value > numericMax) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}.`); + this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}.`); value = numericMax; } @@ -1571,10 +1584,10 @@ export class Characteristic extends EventEmitter { if (this.props.validValueRanges && this.props.validValueRanges.length === 2) { if (value < this.props.validValueRanges[0]) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} not contained in valid value range of ${this.props.validValueRanges}. Supplying illegal values will throw errors in the future!`); + this.characteristicWarning(`characteristic was supplied illegal value: number ${value} not contained in valid value range of ${this.props.validValueRanges}. Supplying illegal values will throw errors in the future!`); value = this.props.validValueRanges[0]; } else if (value > this.props.validValueRanges[1]) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} not contained in valid value range of ${this.props.validValueRanges}. Supplying illegal values will throw errors in the future!`); + this.characteristicWarning(`characteristic was supplied illegal value: number ${value} not contained in valid value range of ${this.props.validValueRanges}. Supplying illegal values will throw errors in the future!`); value = this.props.validValueRanges[1]; } } @@ -1593,6 +1606,20 @@ export class Characteristic extends EventEmitter { this.iid = identifierCache.getIID(accessoryName, serviceUUID, serviceSubtype, this.UUID); } + /** + * @internal + */ + characteristicWarning(message: string, type = CharacteristicWarningType.WARN_MESSAGE): void { + const emitted = this.emit(CharacteristicEventTypes.CHARACTERISTIC_WARNING, type, message); + if (!emitted) { + if (type === CharacteristicWarningType.ERROR_MESSAGE || type === CharacteristicWarningType.TIMEOUT_READ || CharacteristicWarningType.TIMEOUT_WRITE) { + console.error(`[${this.displayName}] ${message}`); + } else { + console.warn(`[${this.displayName}] ${message}`); + } + } + } + /** * Returns a JSON representation of this characteristic suitable for delivering to HAP clients. * @internal used to generate response to /accessories query diff --git a/src/lib/Service.ts b/src/lib/Service.ts index f6c8ec094..e4523f4c7 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -2,6 +2,7 @@ import assert from "assert"; import { EventEmitter } from "events"; import { ServiceJsonObject } from "../internal-types"; import { CharacteristicValue, Nullable, WithUUID } from '../types'; +import { CharacteristicWarningType } from "./Accessory"; import { Characteristic, CharacteristicChange, @@ -115,14 +116,17 @@ export type EventService = ServiceEventTypes.CHARACTERISTIC_CHANGE | ServiceEven export const enum ServiceEventTypes { CHARACTERISTIC_CHANGE = "characteristic-change", SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", + CHARACTERISTIC_WARNING = "characteristic-warning", } export declare interface Service { on(event: "characteristic-change", listener: (change: ServiceCharacteristicChange) => void): this; on(event: "service-configurationChange", listener: () => void): this; + on(event: "characteristic-warning", listener: (characteristic: Characteristic, type: CharacteristicWarningType, message: string) => void): this; emit(event: "characteristic-change", change: ServiceCharacteristicChange): boolean; emit(event: "service-configurationChange"): boolean; + emit(event: "characteristic-warning", characteristic: Characteristic, type: CharacteristicWarningType, message: string): boolean; } /** @@ -316,10 +320,7 @@ export class Service extends EventEmitter { throw new Error("Cannot add more than " + MAX_CHARACTERISTICS + " characteristics to a single service!"); } - // listen for changes in characteristics and bubble them up - characteristic.on(CharacteristicEventTypes.CHANGE, (change: CharacteristicChange) => { - this.emit(ServiceEventTypes.CHARACTERISTIC_CHANGE, { ...change, characteristic: characteristic }); - }); + this.setupCharacteristicEventHandlers(characteristic); this.characteristics.push(characteristic); @@ -425,11 +426,14 @@ export class Service extends EventEmitter { return this.addCharacteristic(name); } } + + const instance = this.addCharacteristic(name); // Not found in optional Characteristics. Adding anyway, but warning about it if it isn't the Name. if (name.UUID !== Characteristic.Name.UUID) { - console.warn("HAP Warning: Characteristic %s not in required or optional characteristics for service %s. Adding anyway.", name.UUID, this.UUID); + instance.characteristicWarning("Characteristic not in required or optional characteristic section for service " + this.constructor.name + ". Adding anyway."); } - return this.addCharacteristic(name); + + return instance; } } @@ -580,15 +584,15 @@ export class Service extends EventEmitter { const missingCharacteristics: Set = new Set(); let timeout: Timeout | undefined = setTimeout(() => { for (const characteristic of missingCharacteristics) { - console.warn(`The read handler for the characteristic '${characteristic.displayName}' was slow to respond!`); + characteristic.characteristicWarning(`The read handler for the characteristic '${characteristic.displayName}' was slow to respond!`, CharacteristicWarningType.SLOW_READ); } timeout = setTimeout(() => { timeout = undefined; for (const characteristic of missingCharacteristics) { - console.error("The read handler for the characteristic '" + characteristic?.displayName + "' didn't respond at all!. " + - "Please check that you properly call the callback!"); + characteristic.characteristicWarning("The read handler for the characteristic '" + characteristic?.displayName + "' didn't respond at all!. " + + "Please check that you properly call the callback!", CharacteristicWarningType.TIMEOUT_READ); service.characteristics.push(characteristic.internalHAPRepresentation()); // value is set to null } @@ -649,20 +653,23 @@ export class Service extends EventEmitter { /** * @internal */ - _setupCharacteristic(characteristic: Characteristic): void { + private setupCharacteristicEventHandlers(characteristic: Characteristic): void { // listen for changes in characteristics and bubble them up characteristic.on(CharacteristicEventTypes.CHANGE, (change: CharacteristicChange) => { this.emit(ServiceEventTypes.CHARACTERISTIC_CHANGE, { ...change, characteristic: characteristic }); }); + + characteristic.on(CharacteristicEventTypes.CHARACTERISTIC_WARNING, (type, message) => { + this.emit(ServiceEventTypes.CHARACTERISTIC_WARNING, characteristic, type, message); + }); } /** * @internal */ - _sideloadCharacteristics(targetCharacteristics: Characteristic[]): void { - for (let index in targetCharacteristics) { - const target = targetCharacteristics[index]; - this._setupCharacteristic(target); + private _sideloadCharacteristics(targetCharacteristics: Characteristic[]): void { + for (const target of targetCharacteristics) { + this.setupCharacteristicEventHandlers(target); } this.characteristics = targetCharacteristics.slice();