// Static regexes for version parsing (for lint rule e18e/prefer-static-regex) /* Copyright(C) 2022-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. * * device.ts: homebridge-firstalert. */ import type { API, HAP, Logging, PlatformAccessory } from 'homebridge' import type { ResideoDevice } from '../api/resideoClient.js' import type { ResideoPlatform } from '../platform.js' import type { ResideoPlatformConfig } from '../settings.js' export abstract class deviceBase { public readonly api: API public readonly log: Logging public readonly config!: ResideoPlatformConfig protected readonly hap: HAP // Config protected deviceLogging!: string protected deviceRefreshRate!: number protected deviceUpdateRate!: number protected devicePushRate!: number protected deviceFirmwareVersion!: string protected deviceMaxRetries!: number protected deviceDelayBetweenRetries!: number constructor( protected readonly platform: ResideoPlatform, protected accessory: PlatformAccessory, protected device: ResideoDevice, ) { this.api = (platform as any).api this.log = (platform as any).log this.config = (platform as any).config as ResideoPlatformConfig this.hap = (platform as any).api.hap this.getDeviceLogSettings() this.getDeviceRateSettings() this.getDeviceConfigSettings(device) this.getDeviceContext(accessory, device) // Set accessory information accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.Manufacturer, 'FirstAlert') .setCharacteristic(this.hap.Characteristic.Name, accessory.displayName) .setCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName) .setCharacteristic(this.hap.Characteristic.Model, accessory.context.model) .setCharacteristic(this.hap.Characteristic.SerialNumber, accessory.context.deviceID) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, this.deviceFirmwareVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) .updateValue(this.deviceFirmwareVersion) } async getDeviceLogSettings(): Promise { this.deviceLogging = 'standard' await this.debugLog(`Using Device Logging: ${this.deviceLogging}`) } async getDeviceRateSettings(): Promise { // Use default values; ResideoDevice does not have these properties this.deviceRefreshRate = 120 this.deviceUpdateRate = 5 this.devicePushRate = 0.1 await this.debugLog(`Using refreshRate: ${this.deviceRefreshRate}, updateRate: ${this.deviceUpdateRate}, pushRate: ${this.devicePushRate}`) this.deviceMaxRetries = 5 await this.debugLog(`Using maxRetries: ${this.deviceMaxRetries}`) this.deviceDelayBetweenRetries = 3 * 1000 await this.debugLog(`Using delayBetweenRetries: ${this.deviceDelayBetweenRetries}`) } async getDeviceConfigSettings(device: ResideoDevice): Promise { // No config merging; ResideoDevice does not have these properties this.debugSuccessLog(`Config: ${JSON.stringify(device)}`) } async getDeviceContext(accessory: PlatformAccessory, device: ResideoDevice): Promise { // Context Information accessory.context.model = device.globalDeviceType accessory.context.deviceID = device.deviceId accessory.context.deviceType = device.globalDeviceType // FirmwareRevision const version = '0.0.0' this.deviceFirmwareVersion = version accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, this.deviceFirmwareVersion) .setCharacteristic(this.hap.Characteristic.SoftwareRevision, this.deviceFirmwareVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, this.deviceFirmwareVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) .updateValue(this.deviceFirmwareVersion) this.debugSuccessLog(`deviceFirmwareVersion: ${this.deviceFirmwareVersion}`) } async statusCode(statusCode: number, action: string): Promise { switch (statusCode) { case 200: this.debugLog(`${this.device.name}: ${this.accessory.displayName} Standard Response, statusCode: ${statusCode}, Action: ${action}`) break case 400: this.errorLog(`${this.device.name}: ${this.accessory.displayName} Bad Request, statusCode: ${statusCode}, Action: ${action}`) break case 401: this.errorLog(`${this.device.name}: ${this.accessory.displayName} Unauthorized, statusCode: ${statusCode}, Action: ${action}`) break case 403: this.errorLog(`${this.device.name}: ${this.accessory.displayName} Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found, statusCode: ${statusCode}`) break case 404: this.errorLog(`${this.device.name}: ${this.accessory.displayName} Not Found, statusCode: ${statusCode}, Action: ${action}`) break case 429: this.errorLog(`${this.device.name}: ${this.accessory.displayName} Too Many Requests, statusCode: ${statusCode}, Action: ${action}`) break case 500: this.errorLog(`${this.device.name}: ${this.accessory.displayName} Internal Server Error (Meater Server), statusCode: ${statusCode}, Action: ${action}`) break default: this.infoLog(`${this.device.name}: ${this.accessory.displayName} Unknown statusCode: ${statusCode}, Action: ${action}, Report Bugs Here: https://bit.ly/homebridge-firstalert-bug-report`) } } async resideoAPIError(e: any, action: string): Promise { if (e.message.includes('400')) { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to ${action}, Bad Request`) this.debugLog('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.') } else if (e.message.includes('401')) { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to ${action}, Unauthorized Request`) this.debugLog('Authorization for the API is required, but the request has not been authenticated.') } else if (e.message.includes('403')) { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to ${action}, Forbidden Request`) this.debugLog('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.') } else if (e.message.includes('404')) { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to ${action}, Requst Not Found`) this.debugLog('Specifies the requested path does not exist.') } else if (e.message.includes('406')) { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to ${action}, Request Not Acceptable`) this.debugLog('The client has requested a MIME type via the Accept header for a value not supported by the server.') } else if (e.message.includes('415')) { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to ${action}, Unsupported Requst Header`) this.debugLog('The client has defined a contentType header that is not supported by the server.') } else if (e.message.includes('422')) { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to ${action}, Unprocessable Entity`) this.debugLog( 'The client has made a valid request, but the server cannot process it.' + ' This is often used for APIs for which certain limits have been exceeded.', ) } else if (e.message.includes('429')) { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to ${action}, Too Many Requests`) this.debugLog('The client has exceeded the number of requests allowed for a given time window.') } else if (e.message.includes('500')) { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to ${action}, Internal Server Error`) this.debugLog('An unexpected error on the SmartThings servers has occurred. These errors should be rare.') } else { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to ${action},`) } if (this.deviceLogging.includes('debug')) { this.errorLog(`${this.device.name}: ${this.accessory.displayName} failed to pushChanges, Error Message: ${JSON.stringify(e.message)}`) } } /** * Logging for Device */ async infoLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { this.log.info(`${this.device.name}: ${this.accessory.displayName}`, String(...log)) } } async successLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { this.log.success(`${this.device.name}: ${this.accessory.displayName}`, String(...log)) } } async debugSuccessLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (this.deviceLogging?.includes('debug')) { this.log.success(`[DEBUG] ${this.device.name}: ${this.accessory.displayName}`, String(...log)) } } } async warnLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { this.log.warn(`${this.device.name}: ${this.accessory.displayName}`, String(...log)) } } async debugWarnLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (this.deviceLogging?.includes('debug')) { this.log.warn(`[DEBUG] ${this.device.name}: ${this.accessory.displayName}`, String(...log)) } } } async errorLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { this.log.error(`${this.device.name}: ${this.accessory.displayName}`, String(...log)) } } async debugErrorLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (this.deviceLogging?.includes('debug')) { this.log.error(`[DEBUG] ${this.device.name}: ${this.accessory.displayName}`, String(...log)) } } } async debugLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (this.deviceLogging === 'debug') { this.log.info(`[DEBUG] ${this.device.name}: ${this.accessory.displayName}`, String(...log)) } else { this.log.debug(`${this.device.name}: ${this.accessory.displayName}`, String(...log)) } } } async enablingDeviceLogging(): Promise { return this.deviceLogging.includes('debug') ?? this.deviceLogging === 'standard' } }