homebridge-jci-hitachi-platform
Version:
Homebridge platform plugin providing HomeKit support for Jci Hitachi air conditioners.
562 lines (410 loc) • 20.6 kB
text/typescript
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);
}
}
}