UNPKG

homebridge-jci-hitachi-platform

Version:

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

407 lines 25.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const settings_1 = require("../settings"); const types_1 = require("../types"); var ClimateCommandType; (function (ClimateCommandType) { ClimateCommandType["Power"] = "Switch"; ClimateCommandType["Mode"] = "Mode"; ClimateCommandType["CurrentTemperature"] = "IndoorTemperature"; ClimateCommandType["TargetTemperature"] = "TemperatureSetting"; ClimateCommandType["FanSpeed"] = "FanSpeed"; ClimateCommandType["VerticalWindDirectionSwitch"] = "VerticalWindDirectionSwitch"; ClimateCommandType["HorizontalWindDirectionSetting"] = "HorizontalWindDirectionSetting"; ClimateCommandType["QuickMode"] = "QuickMode"; ClimateCommandType["CleanSwitch"] = "CleanSwitch"; })(ClimateCommandType || (ClimateCommandType = {})); var ClimateFanSpeedMode; (function (ClimateFanSpeedMode) { ClimateFanSpeedMode[ClimateFanSpeedMode["Auto"] = 0] = "Auto"; ClimateFanSpeedMode[ClimateFanSpeedMode["Silent"] = 1] = "Silent"; ClimateFanSpeedMode[ClimateFanSpeedMode["Low"] = 2] = "Low"; ClimateFanSpeedMode[ClimateFanSpeedMode["Medium"] = 3] = "Medium"; ClimateFanSpeedMode[ClimateFanSpeedMode["High"] = 4] = "High"; ClimateFanSpeedMode[ClimateFanSpeedMode["Rapid"] = 5] = "Rapid"; ClimateFanSpeedMode[ClimateFanSpeedMode["Express"] = 6] = "Express"; })(ClimateFanSpeedMode || (ClimateFanSpeedMode = {})); var ClimateMode; (function (ClimateMode) { ClimateMode[ClimateMode["Cool"] = 0] = "Cool"; ClimateMode[ClimateMode["Dry"] = 1] = "Dry"; ClimateMode[ClimateMode["FanOnly"] = 2] = "FanOnly"; ClimateMode[ClimateMode["Auto"] = 3] = "Auto"; ClimateMode[ClimateMode["Heat"] = 4] = "Heat"; })(ClimateMode || (ClimateMode = {})); /** * An instance of this class is created for each accessory the platform registers. * Each accessory may expose multiple services of different service types. */ class ClimateAccessory extends types_1.JciHitachiAccessory { constructor(platform, accessory) { var _a, _b; super(platform, accessory); this.platform = platform; this.accessory = accessory; this.services = {}; if (!accessory.context.device === undefined) { this.platform.log.error('Device is undefined'); return; } // Accessory Information // https://developers.homebridge.io/#/service/AccessoryInformation (_a = this.accessory.getService(this.platform.Service.AccessoryInformation)) === null || _a === void 0 ? void 0 : _a.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); (_b = this.accessory.getService(this.platform.Service.AccessoryInformation)) === null || _b === void 0 ? void 0 : _b.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, '凍結洗淨通知'); if (this.platform.platformConfig.autoCleanWhenPowerOff && !this.platform.jciHitachiAWSAPI.isHost) { this.platform.log.warn(`'${this.accessory.displayName}': autoCleanWhenPowerOff is enabled, but the ` + 'configured account is not the host (owner) account. 凍結洗淨 is a host-only command, ' + 'so the automatic frost wash will not be triggered.'); } this.refreshDeviceStatus(); } /** * Retrieves the device status from JciHitachi and updates its characteristics. */ async refreshDeviceStatus() { var _a; this.platform.log.debug(`Accessory: Refresh status for device '${this.accessory.displayName}'`); try { this.platform.jciHitachiAWSAPI.RefeshDevice(((_a = this.accessory.context.device) === null || _a === void 0 ? void 0 : _a.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), settings_1.DEVICE_STATUS_REFRESH_INTERVAL); } } dispose() { if (this._refreshInterval) { clearInterval(this._refreshInterval); this._refreshInterval = undefined; } } async getStatus(actionName) { var _a; return this.platform.jciHitachiAWSAPI.GetDeviceStatus(((_a = this.accessory.context.device) === null || _a === void 0 ? void 0 : _a.ThingName) || '', actionName); } async setStatus(actionName, value) { var _a; return this.platform.jciHitachiAWSAPI.SetDeviceStatus(((_a = this.accessory.context.device) === null || _a === void 0 ? void 0 : _a.ThingName) || '', actionName, value); } async setActive(value) { 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() { return this.accessory.context.device.SwitchOn ? this.platform.Characteristic.Active.ACTIVE : this.platform.Characteristic.Active.INACTIVE; } async getRotationSpeed() { return this.accessory.context.device.FanSpeed ? this.accessory.context.device.FanSpeed : 0; } async setRotationSpeed(value) { this.setStatus(ClimateCommandType.FanSpeed, value); this.services['Climate'].updateCharacteristic(this.platform.Characteristic.RotationSpeed, value); } async setQuickMode(value) { 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() { return this.accessory.context.device.QuickMode; } async setCleanSwitch(value) { 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() { return this.accessory.context.device.CleanSwitch; } async getCleanNotification() { return this.accessory.context.device.CleanNotification; } /** * Auto frost wash (凍結洗淨): when the device reports an on -> off transition while * its clean notification is active, trigger CleanSwitch automatically. * * This runs on device-reported state (MQTT status pushes), so it covers a * power-off from HomeKit, the IR remote and the vendor app alike, and only fires * once the unit has confirmed it is off - no race with the power-off command. */ checkAutoClean() { var _a; const switchOn = this.accessory.context.device.SwitchOn; const turnedOff = this.lastSwitchOn === 1 && switchOn === 0; this.lastSwitchOn = switchOn; if (!turnedOff || !this.platform.platformConfig.autoCleanWhenPowerOff) { return; } if (!this.accessory.context.device.CleanNotification) { return; } if (this.accessory.context.device.CleanSwitch) { // A frost wash is already running; don't send the command again. return; } if (!this.platform.jciHitachiAWSAPI.isHost) { // CleanSwitch is host-only (same gate as the QuickMode/CleanSwitch switches); // a shared secondary account cannot trigger it. A warning is logged once at // startup, so keep this at debug level to avoid spamming on every power-off. this.platform.log.debug(`Accessory: '${this.accessory.displayName}' skipped auto 凍結洗淨 (not the host account).`); return; } this.platform.log.info(`Accessory: '${this.accessory.displayName}' turned off with a pending ` + 'clean notification; triggering 凍結洗淨 automatically.'); this.setStatus(ClimateCommandType.CleanSwitch, 1); (_a = this.services['CleanSwitch']) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.platform.Characteristic.On, true); } async getCurrentHeaterCoolerState() { var _a; 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 (_a = this.services['Climate'].getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState).value) !== null && _a !== void 0 ? _a : this.platform.Characteristic.CurrentHeaterCoolerState.INACTIVE; } async setCoolingThresholdTemperature(value) { this.platform.log.debug(`Accessory: setCoolingThresholdTemperature() for device '${this.accessory.displayName}'`); const threshold = +value; this.setStatus(ClimateCommandType.TargetTemperature, threshold); this.services['Climate'].getCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature) .updateValue(value); } async setHeatingThresholdTemperature(value) { this.platform.log.debug(`Accessory: setHeatingThresholdTemperature() for device '${this.accessory.displayName}'`); const threshold = +value; this.setStatus(ClimateCommandType.TargetTemperature, threshold); this.services['Climate'].getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature) .updateValue(value); } async setTargetHeaterCoolerState(value) { 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() { 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() { return this.accessory.context.device.PM25 || 0; } async updateStatus() { var _a, _b; if (this.platform.jciHitachiAWSAPI.isConnected == false) { // Reconnection is owned by the platform (single backoff timer). Don't kick off a // login from each accessory - that is what caused the connect storm in issue #11. this.platform.log.debug('AWS IoT is not connected; waiting for the platform to reconnect.'); this.platform.scheduleReconnect(); return; } this.checkAutoClean(); const temperatureMin = this.accessory.context.device.TemperatureSettingMin; const temperatureMax = this.accessory.context.device.TemperatureSettingMax; if (temperatureMax > temperatureMin && (temperatureMin !== this.appliedTemperatureMin || temperatureMax !== this.appliedTemperatureMax)) { this.services['Climate'].getCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature) .setProps({ minValue: temperatureMin, maxValue: temperatureMax, minStep: 1 }); this.services['Climate'].getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature) .setProps({ minValue: temperatureMin, maxValue: temperatureMax, minStep: 1 }); this.appliedTemperatureMin = temperatureMin; this.appliedTemperatureMax = temperatureMax; } let temperatureSetting = 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; } (_a = this.accessory.getService(this.platform.Service.AccessoryInformation)) === null || _a === void 0 ? void 0 : _a.setCharacteristic(this.platform.Characteristic.Model, this.accessory.context.device.Model || 'Unknown'); (_b = this.accessory.getService(this.platform.Service.AccessoryInformation)) === null || _b === void 0 ? void 0 : _b.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); } } } exports.default = ClimateAccessory; //# sourceMappingURL=climate.js.map