UNPKG

homebridge-jci-hitachi-platform

Version:

Homebridge platform plugin providing HomeKit support for Jci Hitachi air conditioners.

562 lines (410 loc) 20.6 kB
import { Service, PlatformAccessory, CharacteristicValue, CharacteristicSetCallback, CharacteristicGetCallback, CharacteristicEventTypes } from 'homebridge'; import JciHitachiPlatform from '../platform'; import { DEVICE_STATUS_REFRESH_INTERVAL, MAX_NO_OF_FAILED_LOGIN_ATTEMPTS } from '../settings'; import { JciHitachiAccessoryContext, JciHitachiAccessory} from '../types'; import { AWSThings } from '../jci-hitachi-aws-api'; enum ClimateCommandType { Power = 'Switch', Mode = 'Mode', CurrentTemperature = 'IndoorTemperature', TargetTemperature = 'TemperatureSetting', FanSpeed = 'FanSpeed', VerticalWindDirectionSwitch = 'VerticalWindDirectionSwitch', HorizontalWindDirectionSetting = 'HorizontalWindDirectionSetting', QuickMode = 'QuickMode', CleanSwitch = 'CleanSwitch' } enum ClimateFanSpeedMode { Auto = 0, Silent = 1, Low = 2, Medium = 3, High = 4, Rapid = 5, Express = 6, } enum ClimateMode { Cool = 0, Dry = 1, FanOnly = 2, Auto = 3, Heat = 4, } /** * An instance of this class is created for each accessory the platform registers. * Each accessory may expose multiple services of different service types. */ export default class ClimateAccessory extends JciHitachiAccessory{ private services: Service[] = []; private _refreshInterval: NodeJS.Timer | undefined; constructor( protected readonly platform: JciHitachiPlatform, protected readonly accessory: PlatformAccessory<JciHitachiAccessoryContext>, ) { super(platform, accessory); if(!accessory.context.device === undefined) { this.platform.log.error('Device is undefined'); return; } // Accessory Information // https://developers.homebridge.io/#/service/AccessoryInformation this.accessory.getService(this.platform.Service.AccessoryInformation) ?.setCharacteristic( this.platform.Characteristic.Manufacturer, 'JciHitachi TW', ) .setCharacteristic( this.platform.Characteristic.Model, accessory.context.device.Model || 'Unknown', ) .setCharacteristic( this.platform.Characteristic.SerialNumber, this.accessory.UUID, ); this.accessory.getService(this.platform.Service.AccessoryInformation) ?.setCharacteristic( this.platform.Characteristic.FirmwareRevision, this.accessory.context.device.FirmwareVersion || '0', ); this.services['Climate'] = this.accessory.getService(this.platform.Service.HeaterCooler) || this.accessory.addService(this.platform.Service.HeaterCooler); // This is what is displayed as the default name on the Home app this.services['Climate'].setCharacteristic( this.platform.Characteristic.Name, accessory.context.device.CustomDeviceName || '空調', ); this.services['Climate'] .getCharacteristic(this.platform.Characteristic.Active) .onSet(this.setActive.bind(this)) .onGet(this.getActive.bind(this)); this.services['Climate'] .getCharacteristic(this.platform.Characteristic.CurrentTemperature) .setProps({ minValue: -100, maxValue: 100, minStep: 0.01, }); this.services['Climate'] .getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .onGet(this.getCurrentHeaterCoolerState.bind(this)); this.services['Climate'] .getCharacteristic(this.platform.Characteristic.TargetHeaterCoolerState) .onSet(this.setTargetHeaterCoolerState.bind(this)); // Cooling Threshold Temperature (optional) this.services['Climate'] .getCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature) .setProps({ minValue: this.accessory.context.device.TemperatureSettingMin, maxValue: this.accessory.context.device.TemperatureSettingMax, minStep: 1, }) .onSet(this.setCoolingThresholdTemperature.bind(this)); // Heating Threshold Temperature (optional) this.services['Climate'] .getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature) .setProps({ minValue: this.accessory.context.device.TemperatureSettingMin, maxValue: this.accessory.context.device.TemperatureSettingMax, minStep: 1, }) .onSet(this.setHeatingThresholdTemperature.bind(this)); this.services['Climate'] .getCharacteristic(this.platform.Characteristic.RotationSpeed) .setProps({ minValue: 0, maxValue: 6, minStep: 1, }) .onGet(this.getRotationSpeed.bind(this)) .onSet(this.setRotationSpeed.bind(this)); ///// this.services['HumiditySensor'] = this.accessory.getService(this.platform.Service.HumiditySensor) || this.accessory.addService(this.platform.Service.HumiditySensor); this.services['HumiditySensor'].getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity) .onGet(this.getCurrentRelativeHumidity.bind(this)); ///// this.services['AirQualitySensor'] = this.accessory.getService(this.platform.Service.AirQualitySensor) || this.accessory.addService(this.platform.Service.AirQualitySensor); this.services['AirQualitySensor'].getCharacteristic(this.platform.Characteristic.AirQuality) .onGet(this.getCurrentAirQuality.bind(this)); this.services['AirQualitySensor'].getCharacteristic(this.platform.Characteristic.PM2_5Density) .onGet(this.getCurrentPM2_5Density.bind(this)); this.services['AirQualitySensor'].getCharacteristic(this.platform.Characteristic.StatusActive) .onGet(this.getActive.bind(this)); ////////// if(this.platform.jciHitachiAWSAPI.isHost){ const buttonQuickModeName = '快速運轉'; this.services['QuickMode'] = this.accessory.getServiceById(this.platform.Service.Switch, ClimateCommandType.QuickMode) || this.accessory.addService(this.platform.Service.Switch, buttonQuickModeName, ClimateCommandType.QuickMode); this.services['QuickMode'].setCharacteristic(this.platform.Characteristic.Name, buttonQuickModeName); this.services['QuickMode'].getCharacteristic(this.platform.Characteristic.On) .onSet(this.setQuickMode.bind(this)) .onGet(this.getQuickMode.bind(this)); this.services['QuickMode'].addOptionalCharacteristic(this.platform.Characteristic.ConfiguredName); this.services['QuickMode'].setCharacteristic(this.platform.Characteristic.ConfiguredName, buttonQuickModeName); const buttonCleanSwitch = '凍結洗淨'; this.services['CleanSwitch'] = this.accessory.getServiceById(this.platform.Service.Switch, ClimateCommandType.CleanSwitch) || this.accessory.addService(this.platform.Service.Switch, buttonCleanSwitch, ClimateCommandType.CleanSwitch); this.services['CleanSwitch'].setCharacteristic(this.platform.Characteristic.Name, buttonCleanSwitch); this.services['CleanSwitch'].getCharacteristic(this.platform.Characteristic.On) .onSet(this.setCleanSwitch.bind(this)) .onGet(this.getCleanSwitch.bind(this)); this.services['CleanSwitch'].addOptionalCharacteristic(this.platform.Characteristic.ConfiguredName); this.services['CleanSwitch'].setCharacteristic(this.platform.Characteristic.ConfiguredName, buttonCleanSwitch); } /////////// this.services['LeakSensor'] = this.accessory.getService(this.platform.Service.LeakSensor) || this.accessory.addService(this.platform.Service.LeakSensor, '凍結洗淨通知'); this.services['LeakSensor'].setCharacteristic(this.platform.Characteristic.Name, '凍結洗淨通知'); this.services['LeakSensor'].getCharacteristic(this.platform.Characteristic.LeakDetected) .onGet(this.getCleanNotification.bind(this)); this.services['LeakSensor'].addOptionalCharacteristic(this.platform.Characteristic.ConfiguredName); this.services['LeakSensor'].setCharacteristic(this.platform.Characteristic.ConfiguredName, '凍結洗淨通知'); this.refreshDeviceStatus(); } /** * Retrieves the device status from JciHitachi and updates its characteristics. */ async refreshDeviceStatus() { this.platform.log.debug(`Accessory: Refresh status for device '${this.accessory.displayName}'`); try { this.platform.jciHitachiAWSAPI.RefeshDevice(this.accessory.context.device?.ThingName || ''); } catch (error) { this.platform.log.error('An error occurred while refreshing the device status. Turn on debug mode for more information.'); if (error) { this.platform.log.debug(error); } } // Schedule continuous device updates on the first run if (!this._refreshInterval) { this._refreshInterval = setInterval( this.refreshDeviceStatus.bind(this), DEVICE_STATUS_REFRESH_INTERVAL, ); } } async getStatus(actionName:string):Promise<Object|undefined>{ return this.platform.jciHitachiAWSAPI.GetDeviceStatus(this.accessory.context.device?.ThingName || '', actionName); } async setStatus(actionName:string, value:number):Promise<Object|undefined>{ return this.platform.jciHitachiAWSAPI.SetDeviceStatus(this.accessory.context.device?.ThingName || '', actionName, value); } async setActive(value: CharacteristicValue) { this.platform.log.debug(`Accessory: setActive() for device '${this.accessory.displayName}'`); this.setStatus(ClimateCommandType.Power, value === this.platform.Characteristic.Active.ACTIVE ? 1 : 0); this.services['Climate'].updateCharacteristic(this.platform.Characteristic.Active, value); } async getActive():Promise<CharacteristicValue> { return this.accessory.context.device.SwitchOn ? this.platform.Characteristic.Active.ACTIVE : this.platform.Characteristic.Active.INACTIVE; } async getRotationSpeed():Promise<CharacteristicValue> { return this.accessory.context.device.FanSpeed ? this.accessory.context.device.FanSpeed : 0; } async setRotationSpeed(value: CharacteristicValue) { this.setStatus(ClimateCommandType.FanSpeed, value as number); this.services['Climate'].updateCharacteristic(this.platform.Characteristic.RotationSpeed, value); } async setQuickMode(value: CharacteristicValue) { this.platform.log.debug(`Accessory: setQuickMode() for device '${this.accessory.displayName}' to ${value}}`); this.setStatus(ClimateCommandType.QuickMode, value ? 1 : 0); this.services['QuickMode'].updateCharacteristic(this.platform.Characteristic.On, value); } async getQuickMode():Promise<CharacteristicValue> { return this.accessory.context.device.QuickMode; } async setCleanSwitch(value: CharacteristicValue) { this.platform.log.debug(`Accessory: setCleanSwitch() for device '${this.accessory.displayName}' to ${value}}`); this.setStatus(ClimateCommandType.CleanSwitch, value ? 1 : 0); this.services['CleanSwitch'].updateCharacteristic(this.platform.Characteristic.On, value); } async getCleanSwitch():Promise<CharacteristicValue> { return this.accessory.context.device.CleanSwitch; } async getCleanNotification():Promise<CharacteristicValue> { return this.accessory.context.device.CleanNotification; } async getCurrentHeaterCoolerState():Promise<CharacteristicValue> { const currentTemperature = await this.getStatus(ClimateCommandType.CurrentTemperature) || 0; const setTemperature = await this.getStatus(ClimateCommandType.TargetTemperature) || 0; const currentMode = await this.getStatus(ClimateCommandType.Mode) || 0; switch (currentMode) { // Auto case ClimateMode.Auto: // Set target state and current state (based on current temperature) this.services['Climate'].updateCharacteristic( this.platform.Characteristic.TargetHeaterCoolerState, this.platform.Characteristic.TargetHeaterCoolerState.AUTO, ); if (currentTemperature < setTemperature) { this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.HEATING); } else if (currentTemperature > setTemperature) { this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.COOLING); } else { this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.IDLE); } break; // Heat case ClimateMode.Heat: this.services['Climate'].updateCharacteristic( this.platform.Characteristic.TargetHeaterCoolerState, this.platform.Characteristic.TargetHeaterCoolerState.HEAT, ); if (currentTemperature < setTemperature) { this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.HEATING); } else { this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.IDLE); } break; // Cool case ClimateMode.Cool: this.services['Climate'].updateCharacteristic( this.platform.Characteristic.TargetHeaterCoolerState, this.platform.Characteristic.TargetHeaterCoolerState.COOL, ); if (currentTemperature > setTemperature) { this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.COOLING); } else { this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.IDLE); } break; // Dry (Dehumidifier) case ClimateMode.Dry: this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.IDLE); this.services['Climate'].updateCharacteristic( this.platform.Characteristic.TargetHeaterCoolerState, this.platform.Characteristic.TargetHeaterCoolerState.AUTO, ); break; // Fan case ClimateMode.FanOnly: this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.IDLE); this.services['Climate'].updateCharacteristic( this.platform.Characteristic.TargetHeaterCoolerState, this.platform.Characteristic.TargetHeaterCoolerState.AUTO, ); break; default: this.platform.log.error( `Unknown TargetHeaterCoolerState state: '${this.accessory.displayName}' '${currentMode}'`); break; } return this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState).value ; } async setCoolingThresholdTemperature(value: CharacteristicValue) { this.platform.log.debug(`Accessory: setCoolingThresholdTemperature() for device '${this.accessory.displayName}'`); const threshold:number = +value; this.setStatus(ClimateCommandType.TargetTemperature, threshold); this.services['Climate'].getCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature) .updateValue(value); } async setHeatingThresholdTemperature(value: CharacteristicValue) { this.platform.log.debug(`Accessory: setHeatingThresholdTemperature() for device '${this.accessory.displayName}'`); const threshold:number = +value; this.setStatus(ClimateCommandType.TargetTemperature, threshold); this.services['Climate'].getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature) .updateValue(value); } async setTargetHeaterCoolerState(value: CharacteristicValue) { this.platform.log.debug(`Accessory: setTargetHeaterCoolerState() for device '${this.accessory.displayName}'`); let mode = ClimateMode.Auto; switch (value) { case this.platform.Characteristic.TargetHeaterCoolerState.AUTO: mode = ClimateMode.Auto; break; case this.platform.Characteristic.TargetHeaterCoolerState.COOL: mode = ClimateMode.Cool; break; case this.platform.Characteristic.TargetHeaterCoolerState.HEAT: mode = ClimateMode.Heat; break; default: this.platform.log.error('Unknown TargetHeaterCoolerState', value ); return; } this.setStatus(ClimateCommandType.Mode, mode); this.services['Climate'].getCharacteristic(this.platform.Characteristic.TargetHeaterCoolerState) .updateValue(value); } async getCurrentRelativeHumidity(){ return this.accessory.context.device.IndoorHumidity || 0; } async getCurrentAirQuality():Promise<CharacteristicValue> { const pm25 = this.accessory.context.device.PM25 || 0; const pm25Quality = pm25 <= 35 ? 1 : (pm25 <= 53 ? 2 : (pm25 <= 70 ? 3 : (pm25 <= 150 ? 4 : 5))); return pm25Quality; } async getCurrentPM2_5Density():Promise<CharacteristicValue>{ return this.accessory.context.device.PM25 || 0; } public async updateStatus() { if(this.platform.jciHitachiAWSAPI.isConnected == false){ this.platform.log.error('AWS IoT is not connected'); this.platform.jciHitachiAWSAPI.Login(); return; } let temperatureSetting:number = this.accessory.context.device.TemperatureSetting || 0; if(temperatureSetting > this.accessory.context.device.TemperatureSettingMax || temperatureSetting < this.accessory.context.device.TemperatureSettingMin){ temperatureSetting = this.accessory.context.device.IndoorTemperature || this.accessory.context.device.TemperatureSettingMin; } this.accessory.getService(this.platform.Service.AccessoryInformation) ?.setCharacteristic( this.platform.Characteristic.Model, this.accessory.context.device.Model || 'Unknown', ); this.accessory.getService(this.platform.Service.AccessoryInformation) ?.setCharacteristic( this.platform.Characteristic.FirmwareRevision, this.accessory.context.device.FirmwareVersion || '0', ); this.services['Climate'].updateCharacteristic( this.platform.Characteristic.Name, this.accessory.context.device.CustomDeviceName || '空調', ); this.services['Climate'].updateCharacteristic( this.platform.Characteristic.Active, this.accessory.context.device.SwitchOn || 0, ); this.services['Climate'].updateCharacteristic( this.platform.Characteristic.CurrentTemperature, this.accessory.context.device.IndoorTemperature || 0, ); this.services['Climate'].updateCharacteristic( this.platform.Characteristic.CoolingThresholdTemperature, temperatureSetting ); this.services['Climate'].updateCharacteristic( this.platform.Characteristic.HeatingThresholdTemperature, temperatureSetting ); this.services['Climate'].updateCharacteristic( this.platform.Characteristic.CurrentHeaterCoolerState, await this.getCurrentHeaterCoolerState() ); this.services['HumiditySensor'].updateCharacteristic( this.platform.Characteristic.CurrentRelativeHumidity, this.accessory.context.device.IndoorHumidity || 0, ); this.services['AirQualitySensor'].updateCharacteristic( this.platform.Characteristic.AirQuality, await this.getCurrentAirQuality() ); this.services['AirQualitySensor'].updateCharacteristic( this.platform.Characteristic.PM2_5Density, this.accessory.context.device.PM25 || 0, ); this.services['AirQualitySensor'].updateCharacteristic( this.platform.Characteristic.StatusActive, this.accessory.context.device.SwitchOn || 0, ); if(this.platform.jciHitachiAWSAPI.isHost){ this.services['QuickMode'].updateCharacteristic(this.platform.Characteristic.On, this.accessory.context.device.QuickMode || 0); this.services['CleanSwitch'].updateCharacteristic(this.platform.Characteristic.On, this.accessory.context.device.CleanSwitch || 0); } } }