homebridge-smarthq-client 1.2.2 → 1.2.4-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/airConditioner/airConditioner.d.ts +21 -20
- package/dist/airConditioner/airConditioner.js +168 -287
- package/dist/airConditioner/airConditioner.js.map +1 -1
- package/dist/airConditionerServices.d.ts +1 -1
- package/dist/dishwasher/controlLock.d.ts +1 -1
- package/dist/dishwasher/controlLock.js +1 -1
- package/dist/dishwasher/controlLock.js.map +1 -1
- package/dist/dishwasher/dishwasher.d.ts +1 -1
- package/dist/dishwasher/dishwasher.js +1 -1
- package/dist/dishwasher/dishwasher.js.map +1 -1
- package/dist/dishwasher/fanFresh.d.ts +1 -1
- package/dist/dishwasher/fanFresh.js +1 -1
- package/dist/dishwasher/fanFresh.js.map +1 -1
- package/dist/dishwasher/sabbathMode.d.ts +1 -1
- package/dist/dishwasher/sabbathMode.js +1 -1
- package/dist/dishwasher/sabbathMode.js.map +1 -1
- package/dist/dishwasher/soundOption.d.ts +1 -1
- package/dist/dishwasher/soundOption.js +1 -1
- package/dist/dishwasher/soundOption.js.map +1 -1
- package/dist/dishwasherServices.d.ts +1 -1
- package/dist/platform.d.ts +3 -2
- package/dist/platform.js +129 -119
- package/dist/platform.js.map +1 -1
- package/dist/refrigerator/controlLock.d.ts +1 -1
- package/dist/refrigerator/controlLock.js +1 -1
- package/dist/refrigerator/controlLock.js.map +1 -1
- package/dist/refrigerator/convertibleDrawer.d.ts +1 -1
- package/dist/refrigerator/convertibleDrawer.js +1 -1
- package/dist/refrigerator/convertibleDrawer.js.map +1 -1
- package/dist/refrigerator/dispenserLight.d.ts +1 -1
- package/dist/refrigerator/dispenserLight.js +1 -1
- package/dist/refrigerator/dispenserLight.js.map +1 -1
- package/dist/refrigerator/energy.d.ts +1 -1
- package/dist/refrigerator/energy.js +1 -1
- package/dist/refrigerator/energy.js.map +1 -1
- package/dist/refrigerator/freezer.d.ts +1 -1
- package/dist/refrigerator/freezer.js +1 -1
- package/dist/refrigerator/freezer.js.map +1 -1
- package/dist/refrigerator/iceMaker.d.ts +1 -1
- package/dist/refrigerator/iceMaker.js +1 -1
- package/dist/refrigerator/iceMaker.js.map +1 -1
- package/dist/refrigerator/interiorLight.d.ts +1 -1
- package/dist/refrigerator/interiorLight.js +1 -1
- package/dist/refrigerator/interiorLight.js.map +1 -1
- package/dist/refrigerator/refrigerator.d.ts +1 -1
- package/dist/refrigerator/refrigerator.js +1 -1
- package/dist/refrigerator/refrigerator.js.map +1 -1
- package/dist/refrigerator/refrigeratorAlerts.d.ts +1 -1
- package/dist/refrigerator/refrigeratorAlerts.js +1 -1
- package/dist/refrigerator/refrigeratorAlerts.js.map +1 -1
- package/dist/refrigerator/sabbathMode.d.ts +1 -1
- package/dist/refrigerator/sabbathMode.js +1 -1
- package/dist/refrigerator/sabbathMode.js.map +1 -1
- package/dist/refrigerator/temperatureUnits.d.ts +1 -1
- package/dist/refrigerator/temperatureUnits.js +1 -1
- package/dist/refrigerator/temperatureUnits.js.map +1 -1
- package/dist/refrigerator/turboCoolMode.d.ts +1 -1
- package/dist/refrigerator/turboCoolMode.js +1 -1
- package/dist/refrigerator/turboCoolMode.js.map +1 -1
- package/dist/refrigerator/waterFilter.d.ts +1 -1
- package/dist/refrigerator/waterFilter.js +1 -1
- package/dist/refrigerator/waterFilter.js.map +1 -1
- package/dist/refrigeratorServices.d.ts +1 -1
- package/package.json +9 -5
- package/src/airConditioner/airConditioner.ts +252 -335
- package/src/airConditionerServices.ts +1 -1
- package/src/dishwasher/controlLock.ts +1 -1
- package/src/dishwasher/dishwasher.ts +1 -1
- package/src/dishwasher/fanFresh.ts +1 -1
- package/src/dishwasher/sabbathMode.ts +1 -1
- package/src/dishwasher/soundOption.ts +1 -1
- package/src/dishwasherServices.ts +1 -1
- package/src/platform.ts +234 -152
- package/src/refrigerator/controlLock.ts +1 -1
- package/src/refrigerator/convertibleDrawer.ts +1 -1
- package/src/refrigerator/dispenserLight.ts +1 -1
- package/src/refrigerator/energy.ts +1 -1
- package/src/refrigerator/freezer.ts +1 -1
- package/src/refrigerator/iceMaker.ts +1 -1
- package/src/refrigerator/interiorLight.ts +1 -1
- package/src/refrigerator/refrigerator.ts +1 -1
- package/src/refrigerator/refrigeratorAlerts.ts +1 -1
- package/src/refrigerator/sabbathMode.ts +1 -1
- package/src/refrigerator/temperatureUnits.ts +1 -1
- package/src/refrigerator/turboCoolMode.ts +1 -1
- package/src/refrigerator/waterFilter.ts +1 -1
- package/src/refrigeratorServices.ts +1 -1
|
@@ -1,37 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DeviceService } from 'ge-smarthq';
|
|
1
|
+
import { PlatformAccessory, Service, Characteristic } from 'homebridge';
|
|
2
|
+
import { DeviceService } from 'ge-smarthq-api';
|
|
3
3
|
import { SmartHqPlatform } from '../platform.js';
|
|
4
4
|
export declare class AirConditioner {
|
|
5
5
|
private readonly platform;
|
|
6
6
|
private readonly accessory;
|
|
7
|
-
readonly deviceServices
|
|
8
|
-
readonly deviceId
|
|
7
|
+
private readonly deviceServices;
|
|
8
|
+
private readonly deviceId;
|
|
9
9
|
private targetCelsius;
|
|
10
10
|
private targetMode;
|
|
11
|
-
private targetFanSpeed;
|
|
12
11
|
private isOn;
|
|
12
|
+
private fanAuto;
|
|
13
|
+
private fanSpeedPercent;
|
|
14
|
+
private fanSpeedCloud;
|
|
13
15
|
private client;
|
|
16
|
+
private api;
|
|
14
17
|
Service: typeof Service;
|
|
15
18
|
Characteristic: typeof Characteristic;
|
|
16
|
-
private api;
|
|
17
|
-
private readonly modeToHK;
|
|
18
|
-
private hkToMode;
|
|
19
19
|
private acThermostat;
|
|
20
20
|
private acFan;
|
|
21
|
-
private
|
|
21
|
+
private fanAutoSwitch;
|
|
22
|
+
private readonly modeToHK;
|
|
23
|
+
private readonly hkToMode;
|
|
22
24
|
constructor(platform: SmartHqPlatform, accessory: PlatformAccessory, deviceServices: DeviceService[], deviceId: string);
|
|
23
|
-
private
|
|
25
|
+
private setupAccessories;
|
|
26
|
+
private setFanSpeed;
|
|
27
|
+
private setFanAuto;
|
|
24
28
|
private fanSpeedToPercent;
|
|
25
29
|
private percentToFanSpeed;
|
|
26
|
-
private
|
|
30
|
+
private setTargetHeatingCoolingState;
|
|
31
|
+
private setTargetTemperature;
|
|
32
|
+
private getTargetHeatingCoolingState;
|
|
33
|
+
private sendCommand;
|
|
34
|
+
private handleUpdate;
|
|
35
|
+
private syncState;
|
|
27
36
|
private findService;
|
|
28
|
-
|
|
29
|
-
getTargetHeatingCoolingState(): number;
|
|
30
|
-
setTargetHeatingCoolingState(value: CharacteristicValue): Promise<void>;
|
|
31
|
-
getCurrentTemperature(): Promise<number>;
|
|
32
|
-
getTargetTemperature(): number;
|
|
33
|
-
setTargetTemperature(value: CharacteristicValue): Promise<void>;
|
|
34
|
-
handleTemperatureDisplayUnitsGet(): number;
|
|
35
|
-
handleTemperatureDisplayUnitsSet(value: CharacteristicValue): void;
|
|
36
|
-
setupWebSocket(): Promise<void>;
|
|
37
|
+
private setupWebSocket;
|
|
37
38
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SmartHQClient } from 'ge-smarthq';
|
|
1
|
+
import { SmartHQClient } from 'ge-smarthq-api';
|
|
2
2
|
export class AirConditioner {
|
|
3
3
|
platform;
|
|
4
4
|
accessory;
|
|
@@ -6,19 +6,20 @@ export class AirConditioner {
|
|
|
6
6
|
deviceId;
|
|
7
7
|
targetCelsius = 22.22;
|
|
8
8
|
targetMode = 'cloud.smarthq.type.thermostatmode.cool';
|
|
9
|
-
targetFanSpeed = 'cloud.smarthq.type.fanspeed.auto';
|
|
10
9
|
isOn = false;
|
|
10
|
+
// Fan state (clean model)
|
|
11
|
+
fanAuto = true;
|
|
12
|
+
fanSpeedPercent = 50;
|
|
13
|
+
fanSpeedCloud = 'cloud.smarthq.type.fanspeed.medium';
|
|
11
14
|
client;
|
|
15
|
+
api;
|
|
12
16
|
Service;
|
|
13
17
|
Characteristic;
|
|
14
|
-
api;
|
|
15
|
-
modeToHK = {};
|
|
16
|
-
hkToMode = {};
|
|
17
|
-
// Promoted to class properties so handlers outside constructor can access them
|
|
18
18
|
acThermostat;
|
|
19
19
|
acFan;
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
fanAutoSwitch;
|
|
21
|
+
modeToHK = {};
|
|
22
|
+
hkToMode = {};
|
|
22
23
|
constructor(platform, accessory, deviceServices, deviceId) {
|
|
23
24
|
this.platform = platform;
|
|
24
25
|
this.accessory = accessory;
|
|
@@ -38,201 +39,118 @@ export class AirConditioner {
|
|
|
38
39
|
'cloud.smarthq.type.thermostatmode.cool': HK.COOL,
|
|
39
40
|
'cloud.smarthq.type.thermostatmode.cool.energysaver': HK.AUTO,
|
|
40
41
|
'cloud.smarthq.type.thermostatmode.fanonly': HK.OFF,
|
|
41
|
-
'cloud.smarthq.type.thermostatmode.dry': HK.
|
|
42
|
+
'cloud.smarthq.type.thermostatmode.dry': HK.HEAT,
|
|
42
43
|
};
|
|
43
44
|
this.hkToMode = {
|
|
44
45
|
[HK.OFF]: 'cloud.smarthq.type.thermostatmode.fanonly',
|
|
45
46
|
[HK.COOL]: 'cloud.smarthq.type.thermostatmode.cool',
|
|
46
|
-
[HK.HEAT]: 'cloud.smarthq.type.thermostatmode.cool',
|
|
47
47
|
[HK.AUTO]: 'cloud.smarthq.type.thermostatmode.cool.energysaver',
|
|
48
|
+
[HK.HEAT]: 'cloud.smarthq.type.thermostatmode.dry',
|
|
48
49
|
};
|
|
49
|
-
//
|
|
50
|
+
// Load initial state if available
|
|
50
51
|
const thermoSvc = this.findService('cloud.smarthq.service.thermostat.v1', 'cloud.smarthq.domain.thermostat');
|
|
51
52
|
if (thermoSvc?.state) {
|
|
52
|
-
if (thermoSvc.state.coolCelsiusConverted != null)
|
|
53
|
+
if (thermoSvc.state.coolCelsiusConverted != null) {
|
|
53
54
|
this.targetCelsius = thermoSvc.state.coolCelsiusConverted;
|
|
54
|
-
|
|
55
|
+
}
|
|
56
|
+
if (thermoSvc.state.mode != null) {
|
|
55
57
|
this.targetMode = thermoSvc.state.mode;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// authenticate() before connect() so both sendCommand and WebSocket work
|
|
62
|
-
this.setupWebSocket();
|
|
63
|
-
// WebSocket push updates
|
|
64
|
-
this.client.on('service_update', (message) => {
|
|
65
|
-
if (message.domainType === 'cloud.smarthq.domain.thermostat') {
|
|
66
|
-
if (message.state?.on !== undefined) {
|
|
67
|
-
this.isOn = message.state.on;
|
|
68
|
-
this.acFan.getCharacteristic(this.Characteristic.On).updateValue(this.isOn);
|
|
69
|
-
}
|
|
70
|
-
if (message.state?.coolCelsiusConverted !== undefined) {
|
|
71
|
-
this.targetCelsius = message.state.coolCelsiusConverted;
|
|
72
|
-
this.acThermostat.getCharacteristic(this.Characteristic.TargetTemperature).updateValue(this.targetCelsius);
|
|
73
|
-
}
|
|
74
|
-
if (message.state?.mode !== undefined) {
|
|
75
|
-
this.targetMode = message.state.mode;
|
|
76
|
-
const hkMode = this.modeToHK[this.targetMode] ?? HK.COOL;
|
|
77
|
-
this.acThermostat.getCharacteristic(this.Characteristic.TargetHeatingCoolingState).updateValue(hkMode);
|
|
78
|
-
this.updateModeSwitches(this.targetMode);
|
|
58
|
+
}
|
|
59
|
+
if (thermoSvc.state.fanSpeed != null) {
|
|
60
|
+
const fs = thermoSvc.state.fanSpeed;
|
|
61
|
+
if (fs === 'cloud.smarthq.type.fanspeed.auto') {
|
|
62
|
+
this.fanAuto = true;
|
|
79
63
|
}
|
|
80
|
-
|
|
81
|
-
this.
|
|
82
|
-
this.
|
|
64
|
+
else {
|
|
65
|
+
this.fanAuto = false;
|
|
66
|
+
this.fanSpeedCloud = fs;
|
|
67
|
+
this.fanSpeedPercent = this.fanSpeedToPercent(fs);
|
|
83
68
|
}
|
|
84
69
|
}
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
const currentState = running
|
|
88
|
-
? this.Characteristic.CurrentHeatingCoolingState.COOL
|
|
89
|
-
: this.Characteristic.CurrentHeatingCoolingState.OFF;
|
|
90
|
-
this.acThermostat.getCharacteristic(this.Characteristic.CurrentHeatingCoolingState).updateValue(currentState);
|
|
70
|
+
if (thermoSvc.state.on != null) {
|
|
71
|
+
this.isOn = thermoSvc.state.on;
|
|
91
72
|
}
|
|
73
|
+
}
|
|
74
|
+
this.setupAccessories();
|
|
75
|
+
this.setupWebSocket();
|
|
76
|
+
this.client.on('service_update', (message) => {
|
|
77
|
+
if (message.domainType !== 'cloud.smarthq.domain.thermostat')
|
|
78
|
+
return;
|
|
79
|
+
this.handleUpdate(message);
|
|
92
80
|
});
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
setInterval(() => {
|
|
82
|
+
this.syncState();
|
|
83
|
+
}, 30000);
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------
|
|
86
|
+
// ACCESSORIES
|
|
87
|
+
// ---------------------------
|
|
88
|
+
setupAccessories() {
|
|
89
|
+
const info = this.accessory.getService(this.Service.AccessoryInformation);
|
|
90
|
+
info
|
|
95
91
|
.setCharacteristic(this.Characteristic.Manufacturer, 'GE')
|
|
96
|
-
.setCharacteristic(this.Characteristic.Model, accessory.context.device.model || '
|
|
97
|
-
.setCharacteristic(this.Characteristic.SerialNumber, accessory.context.device.serial || '
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.acThermostat
|
|
103
|
-
|
|
104
|
-
this.acThermostat.setCharacteristic(this.Characteristic.ConfiguredName, displayName);
|
|
105
|
-
this.acThermostat.getCharacteristic(this.Characteristic.CurrentHeatingCoolingState).setProps({
|
|
106
|
-
minValue: this.Characteristic.CurrentHeatingCoolingState.OFF,
|
|
107
|
-
maxValue: this.Characteristic.CurrentHeatingCoolingState.COOL,
|
|
108
|
-
validValues: [
|
|
109
|
-
this.Characteristic.CurrentHeatingCoolingState.OFF,
|
|
110
|
-
this.Characteristic.CurrentHeatingCoolingState.COOL,
|
|
111
|
-
],
|
|
112
|
-
});
|
|
113
|
-
this.acThermostat.getCharacteristic(this.Characteristic.TargetHeatingCoolingState).setProps({
|
|
114
|
-
minValue: this.Characteristic.TargetHeatingCoolingState.OFF,
|
|
115
|
-
maxValue: this.Characteristic.TargetHeatingCoolingState.AUTO,
|
|
116
|
-
validValues: [
|
|
117
|
-
this.Characteristic.TargetHeatingCoolingState.OFF,
|
|
118
|
-
this.Characteristic.TargetHeatingCoolingState.COOL,
|
|
119
|
-
this.Characteristic.TargetHeatingCoolingState.AUTO,
|
|
120
|
-
],
|
|
121
|
-
});
|
|
122
|
-
this.acThermostat.getCharacteristic(this.Characteristic.CurrentTemperature).setProps({
|
|
123
|
-
minValue: -20,
|
|
124
|
-
maxValue: 50,
|
|
125
|
-
minStep: 0.1,
|
|
126
|
-
});
|
|
127
|
-
this.acThermostat.getCharacteristic(this.Characteristic.TargetTemperature).setProps({
|
|
128
|
-
minValue: 17.78,
|
|
129
|
-
maxValue: 30.0,
|
|
130
|
-
minStep: 0.5,
|
|
131
|
-
});
|
|
132
|
-
this.acThermostat.getCharacteristic(this.Characteristic.CurrentHeatingCoolingState)
|
|
133
|
-
.onGet(this.getCurrentHeatingCoolingState.bind(this));
|
|
134
|
-
this.acThermostat.getCharacteristic(this.Characteristic.TargetHeatingCoolingState)
|
|
92
|
+
.setCharacteristic(this.Characteristic.Model, this.accessory.context.device.model || 'AC Unit')
|
|
93
|
+
.setCharacteristic(this.Characteristic.SerialNumber, this.accessory.context.device.serial || 'Unknown');
|
|
94
|
+
const name = 'Air Conditioner';
|
|
95
|
+
this.acThermostat =
|
|
96
|
+
this.accessory.getService(name) ||
|
|
97
|
+
this.accessory.addService(this.Service.Thermostat, name, 'ac-thermo');
|
|
98
|
+
this.acThermostat
|
|
99
|
+
.getCharacteristic(this.Characteristic.TargetHeatingCoolingState)
|
|
135
100
|
.onGet(this.getTargetHeatingCoolingState.bind(this))
|
|
136
101
|
.onSet(this.setTargetHeatingCoolingState.bind(this));
|
|
137
|
-
this.acThermostat
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
.onGet(this.getTargetTemperature.bind(this))
|
|
102
|
+
this.acThermostat
|
|
103
|
+
.getCharacteristic(this.Characteristic.TargetTemperature)
|
|
104
|
+
.onGet(() => this.targetCelsius)
|
|
141
105
|
.onSet(this.setTargetTemperature.bind(this));
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
this.acFan
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
{ name: 'Eco', mode: 'cloud.smarthq.type.thermostatmode.cool.energysaver', subtype: 'ac-mode-eco' },
|
|
170
|
-
{ name: 'Dry', mode: 'cloud.smarthq.type.thermostatmode.dry', subtype: 'ac-mode-dry' },
|
|
171
|
-
{ name: 'Fan Only', mode: 'cloud.smarthq.type.thermostatmode.fanonly', subtype: 'ac-mode-fanonly' },
|
|
172
|
-
];
|
|
173
|
-
for (const { name, mode, subtype } of modes) {
|
|
174
|
-
const svc = this.accessory.getService(name)
|
|
175
|
-
|| this.accessory.addService(this.Service.Switch, name, subtype);
|
|
176
|
-
svc.setCharacteristic(this.Characteristic.Name, name);
|
|
177
|
-
svc.addOptionalCharacteristic(this.Characteristic.ConfiguredName);
|
|
178
|
-
svc.setCharacteristic(this.Characteristic.ConfiguredName, name);
|
|
179
|
-
svc.getCharacteristic(this.Characteristic.On)
|
|
180
|
-
.onGet(() => this.isOn && this.targetMode === mode)
|
|
181
|
-
.onSet(async (value) => {
|
|
182
|
-
if (value) {
|
|
183
|
-
// Switching to this mode turns the AC on and deactivates others
|
|
184
|
-
this.isOn = true;
|
|
185
|
-
this.targetMode = mode;
|
|
186
|
-
await this.sendThermostatCommand(mode, this.targetCelsius, this.targetFanSpeed, true);
|
|
187
|
-
this.updateModeSwitches(mode);
|
|
188
|
-
// Keep thermostat tile in sync
|
|
189
|
-
const hkMode = this.modeToHK[mode] ?? HK.COOL;
|
|
190
|
-
this.acThermostat.getCharacteristic(this.Characteristic.TargetHeatingCoolingState).updateValue(hkMode);
|
|
191
|
-
this.acFan.getCharacteristic(this.Characteristic.On).updateValue(true);
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
// Turning off a mode switch turns the AC off entirely
|
|
195
|
-
this.isOn = false;
|
|
196
|
-
await this.sendThermostatCommand(this.targetMode, this.targetCelsius, this.targetFanSpeed, false);
|
|
197
|
-
this.updateModeSwitches('');
|
|
198
|
-
this.acThermostat.getCharacteristic(this.Characteristic.TargetHeatingCoolingState)
|
|
199
|
-
.updateValue(this.Characteristic.TargetHeatingCoolingState.OFF);
|
|
200
|
-
this.acFan.getCharacteristic(this.Characteristic.On).updateValue(false);
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
this.modeSwitches[mode] = svc;
|
|
204
|
-
}
|
|
205
|
-
// Set initial switch states
|
|
206
|
-
this.updateModeSwitches(this.isOn ? this.targetMode : '');
|
|
207
|
-
// ── Polling (every 30 s) ──────────────────────────────────────────────────
|
|
208
|
-
setInterval(() => {
|
|
209
|
-
this.getCurrentTemperature().then(temp => {
|
|
210
|
-
this.acThermostat.getCharacteristic(this.Characteristic.CurrentTemperature).updateValue(temp);
|
|
211
|
-
});
|
|
212
|
-
this.getCurrentHeatingCoolingState().then(state => {
|
|
213
|
-
const running = state === this.Characteristic.CurrentHeatingCoolingState.COOL;
|
|
214
|
-
this.acThermostat.getCharacteristic(this.Characteristic.CurrentHeatingCoolingState).updateValue(state);
|
|
215
|
-
this.acFan.getCharacteristic(this.Characteristic.On).updateValue(running);
|
|
216
|
-
// Keep TargetHeatingCoolingState in sync with actual on/off state
|
|
217
|
-
this.acThermostat.getCharacteristic(this.Characteristic.TargetHeatingCoolingState).updateValue(this.isOn
|
|
218
|
-
? (this.modeToHK[this.targetMode] ?? this.Characteristic.TargetHeatingCoolingState.COOL)
|
|
219
|
-
: this.Characteristic.TargetHeatingCoolingState.OFF);
|
|
220
|
-
});
|
|
221
|
-
}, 30000);
|
|
106
|
+
// FAN SERVICE (slider only)
|
|
107
|
+
const fanName = 'AC Fan Speed';
|
|
108
|
+
this.acFan =
|
|
109
|
+
this.accessory.getService(fanName) ||
|
|
110
|
+
this.accessory.addService(this.Service.Fanv2, fanName, 'ac-fan');
|
|
111
|
+
this.acFan
|
|
112
|
+
.getCharacteristic(this.Characteristic.RotationSpeed)
|
|
113
|
+
.setProps({ minValue: 0, maxValue: 100, minStep: 1 })
|
|
114
|
+
.onGet(() => this.fanSpeedPercent)
|
|
115
|
+
.onSet(this.setFanSpeed.bind(this));
|
|
116
|
+
// AUTO SWITCH (IMPORTANT FIX)
|
|
117
|
+
this.fanAutoSwitch =
|
|
118
|
+
this.accessory.getService('Fan Auto Mode') ||
|
|
119
|
+
this.accessory.addService(this.Service.Switch, 'Fan Auto Mode', 'ac-fan-auto');
|
|
120
|
+
this.fanAutoSwitch
|
|
121
|
+
.getCharacteristic(this.Characteristic.On)
|
|
122
|
+
.onGet(() => this.fanAuto)
|
|
123
|
+
.onSet(this.setFanAuto.bind(this));
|
|
124
|
+
}
|
|
125
|
+
// ---------------------------
|
|
126
|
+
// FAN LOGIC
|
|
127
|
+
// ---------------------------
|
|
128
|
+
async setFanSpeed(value) {
|
|
129
|
+
this.fanAuto = false;
|
|
130
|
+
this.fanSpeedPercent = value;
|
|
131
|
+
this.fanSpeedCloud = this.percentToFanSpeed(this.fanSpeedPercent);
|
|
132
|
+
await this.sendCommand();
|
|
222
133
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
134
|
+
async setFanAuto(value) {
|
|
135
|
+
this.fanAuto = value;
|
|
136
|
+
if (this.fanAuto) {
|
|
137
|
+
this.fanSpeedCloud = 'cloud.smarthq.type.fanspeed.auto';
|
|
227
138
|
}
|
|
139
|
+
else {
|
|
140
|
+
this.fanSpeedCloud = this.percentToFanSpeed(this.fanSpeedPercent);
|
|
141
|
+
}
|
|
142
|
+
await this.sendCommand();
|
|
228
143
|
}
|
|
229
|
-
// ── Fan speed helpers ──────────────────────────────────────────────────────
|
|
230
144
|
fanSpeedToPercent(fanSpeed) {
|
|
231
145
|
switch (fanSpeed) {
|
|
232
|
-
case 'cloud.smarthq.type.fanspeed.high':
|
|
233
|
-
|
|
234
|
-
case 'cloud.smarthq.type.fanspeed.
|
|
235
|
-
|
|
146
|
+
case 'cloud.smarthq.type.fanspeed.high':
|
|
147
|
+
return 100;
|
|
148
|
+
case 'cloud.smarthq.type.fanspeed.medium':
|
|
149
|
+
return 50;
|
|
150
|
+
case 'cloud.smarthq.type.fanspeed.low':
|
|
151
|
+
return 25;
|
|
152
|
+
default:
|
|
153
|
+
return 50;
|
|
236
154
|
}
|
|
237
155
|
}
|
|
238
156
|
percentToFanSpeed(percent) {
|
|
@@ -240,138 +158,101 @@ export class AirConditioner {
|
|
|
240
158
|
return 'cloud.smarthq.type.fanspeed.high';
|
|
241
159
|
if (percent >= 40)
|
|
242
160
|
return 'cloud.smarthq.type.fanspeed.medium';
|
|
243
|
-
|
|
244
|
-
return 'cloud.smarthq.type.fanspeed.low';
|
|
245
|
-
return 'cloud.smarthq.type.fanspeed.auto';
|
|
161
|
+
return 'cloud.smarthq.type.fanspeed.low';
|
|
246
162
|
}
|
|
247
|
-
//
|
|
248
|
-
|
|
163
|
+
// ---------------------------
|
|
164
|
+
// THERMOSTAT LOGIC
|
|
165
|
+
// ---------------------------
|
|
166
|
+
async setTargetHeatingCoolingState(value) {
|
|
167
|
+
const HK = this.Characteristic.TargetHeatingCoolingState;
|
|
168
|
+
if (value === HK.OFF) {
|
|
169
|
+
this.isOn = false;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
this.isOn = true;
|
|
173
|
+
this.targetMode = this.hkToMode[value];
|
|
174
|
+
}
|
|
175
|
+
await this.sendCommand();
|
|
176
|
+
}
|
|
177
|
+
async setTargetTemperature(value) {
|
|
178
|
+
this.targetCelsius = value;
|
|
179
|
+
await this.sendCommand();
|
|
180
|
+
}
|
|
181
|
+
async getTargetHeatingCoolingState() {
|
|
182
|
+
return this.isOn
|
|
183
|
+
? this.modeToHK[this.targetMode] ??
|
|
184
|
+
this.Characteristic.TargetHeatingCoolingState.COOL
|
|
185
|
+
: this.Characteristic.TargetHeatingCoolingState.OFF;
|
|
186
|
+
}
|
|
187
|
+
// ---------------------------
|
|
188
|
+
// SMARTHQ COMMAND
|
|
189
|
+
// ---------------------------
|
|
190
|
+
async sendCommand() {
|
|
249
191
|
const svc = this.findService('cloud.smarthq.service.thermostat.v1', 'cloud.smarthq.domain.thermostat');
|
|
250
192
|
if (!svc)
|
|
251
193
|
return;
|
|
252
194
|
const command = {
|
|
253
195
|
commandType: 'cloud.smarthq.command.thermostat.v1.set',
|
|
254
|
-
on,
|
|
196
|
+
on: this.isOn,
|
|
255
197
|
};
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
command.
|
|
259
|
-
|
|
260
|
-
const isDry = mode === 'cloud.smarthq.type.thermostatmode.dry';
|
|
261
|
-
if (!isDry) {
|
|
262
|
-
command.fanSpeed = fanSpeed;
|
|
263
|
-
}
|
|
264
|
-
// Only send coolFahrenheit for cool modes
|
|
265
|
-
const needsCoolTemp = mode === 'cloud.smarthq.type.thermostatmode.cool'
|
|
266
|
-
|| mode === 'cloud.smarthq.type.thermostatmode.cool.energysaver';
|
|
267
|
-
if (needsCoolTemp) {
|
|
268
|
-
command.coolFahrenheit = Math.round(celsius * 9 / 5 + 32);
|
|
269
|
-
}
|
|
198
|
+
if (this.isOn) {
|
|
199
|
+
command.mode = this.targetMode;
|
|
200
|
+
command.fanSpeed = this.fanSpeedCloud;
|
|
201
|
+
command.coolFahrenheit = Math.round(this.targetCelsius * 1.8 + 32);
|
|
270
202
|
}
|
|
271
|
-
|
|
203
|
+
await this.client.sendCommand({
|
|
272
204
|
command,
|
|
273
205
|
kind: 'service#command',
|
|
274
206
|
deviceId: this.deviceId,
|
|
275
207
|
serviceDeviceType: 'cloud.smarthq.device.airconditioner',
|
|
276
208
|
serviceType: 'cloud.smarthq.service.thermostat.v1',
|
|
277
209
|
domainType: 'cloud.smarthq.domain.thermostat',
|
|
278
|
-
};
|
|
279
|
-
try {
|
|
280
|
-
await this.client.sendCommand(cmdBody);
|
|
281
|
-
}
|
|
282
|
-
catch (error) {
|
|
283
|
-
// handleApiError in ge-smarthq is async but callers don't await it,
|
|
284
|
-
// so the thrown value is a Promise<Error> — await it to get the real message.
|
|
285
|
-
const resolved = error instanceof Promise ? await error : error;
|
|
286
|
-
const msg = resolved instanceof Error ? resolved.message : JSON.stringify(resolved);
|
|
287
|
-
this.client.debug('Error sending AC thermostat command: ' + msg);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
// ── Service helpers ────────────────────────────────────────────────────────
|
|
291
|
-
findService(serviceType, domainType) {
|
|
292
|
-
return this.deviceServices.find(s => s.serviceType === serviceType && s.domainType === domainType);
|
|
293
|
-
}
|
|
294
|
-
// ── Handlers ──────────────────────────────────────────────────────────────
|
|
295
|
-
async getCurrentHeatingCoolingState() {
|
|
296
|
-
const svc = this.findService('cloud.smarthq.service.toggle', 'cloud.smarthq.domain.state.compressor');
|
|
297
|
-
if (!svc)
|
|
298
|
-
return this.Characteristic.CurrentHeatingCoolingState.OFF;
|
|
299
|
-
try {
|
|
300
|
-
const response = await this.client.getServiceDetails(this.deviceId, svc.serviceId);
|
|
301
|
-
const running = response?.state?.on ?? false;
|
|
302
|
-
this.isOn = running;
|
|
303
|
-
return running
|
|
304
|
-
? this.Characteristic.CurrentHeatingCoolingState.COOL
|
|
305
|
-
: this.Characteristic.CurrentHeatingCoolingState.OFF;
|
|
306
|
-
}
|
|
307
|
-
catch (error) {
|
|
308
|
-
this.client.debug('Error getting AC compressor state: ' + error);
|
|
309
|
-
return this.Characteristic.CurrentHeatingCoolingState.OFF;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
getTargetHeatingCoolingState() {
|
|
313
|
-
if (!this.isOn)
|
|
314
|
-
return this.Characteristic.TargetHeatingCoolingState.OFF;
|
|
315
|
-
return this.modeToHK[this.targetMode] ?? this.Characteristic.TargetHeatingCoolingState.COOL;
|
|
210
|
+
});
|
|
316
211
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
this.updateModeSwitches('');
|
|
324
|
-
// Explicitly update thermostat tile to OFF
|
|
325
|
-
this.acThermostat.getCharacteristic(this.Characteristic.TargetHeatingCoolingState).updateValue(HK.OFF);
|
|
212
|
+
// ---------------------------
|
|
213
|
+
// LIVE UPDATES
|
|
214
|
+
// ---------------------------
|
|
215
|
+
handleUpdate(message) {
|
|
216
|
+
const state = message.state;
|
|
217
|
+
if (!state)
|
|
326
218
|
return;
|
|
219
|
+
if (state.on !== undefined) {
|
|
220
|
+
this.isOn = state.on;
|
|
327
221
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
this.targetMode = newMode;
|
|
331
|
-
await this.sendThermostatCommand(newMode, this.targetCelsius, this.targetFanSpeed, true);
|
|
332
|
-
this.updateModeSwitches(newMode);
|
|
333
|
-
}
|
|
334
|
-
async getCurrentTemperature() {
|
|
335
|
-
const svc = this.findService('cloud.smarthq.service.temperature', 'cloud.smarthq.domain.indoor.ambient');
|
|
336
|
-
if (!svc)
|
|
337
|
-
return this.targetCelsius;
|
|
338
|
-
try {
|
|
339
|
-
const response = await this.client.getServiceDetails(this.deviceId, svc.serviceId);
|
|
340
|
-
if (response?.state?.celsiusConverted == null)
|
|
341
|
-
return this.targetCelsius;
|
|
342
|
-
return Number(response.state.celsiusConverted);
|
|
222
|
+
if (state.coolCelsiusConverted !== undefined) {
|
|
223
|
+
this.targetCelsius = state.coolCelsiusConverted;
|
|
343
224
|
}
|
|
344
|
-
|
|
345
|
-
this.
|
|
346
|
-
return this.targetCelsius;
|
|
225
|
+
if (state.mode !== undefined) {
|
|
226
|
+
this.targetMode = state.mode;
|
|
347
227
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
228
|
+
if (state.fanSpeed !== undefined) {
|
|
229
|
+
const fs = state.fanSpeed;
|
|
230
|
+
if (fs === 'cloud.smarthq.type.fanspeed.auto') {
|
|
231
|
+
this.fanAuto = true;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
this.fanAuto = false;
|
|
235
|
+
this.fanSpeedCloud = fs;
|
|
236
|
+
this.fanSpeedPercent = this.fanSpeedToPercent(fs);
|
|
237
|
+
}
|
|
238
|
+
this.fanAutoSwitch.updateCharacteristic(this.Characteristic.On, this.fanAuto);
|
|
239
|
+
this.acFan.updateCharacteristic(this.Characteristic.RotationSpeed, this.fanSpeedPercent);
|
|
357
240
|
}
|
|
358
241
|
}
|
|
359
|
-
|
|
360
|
-
|
|
242
|
+
async syncState() {
|
|
243
|
+
this.acThermostat
|
|
244
|
+
.getCharacteristic(this.Characteristic.CurrentTemperature)
|
|
245
|
+
.updateValue(this.targetCelsius);
|
|
361
246
|
}
|
|
362
|
-
|
|
363
|
-
|
|
247
|
+
// ---------------------------
|
|
248
|
+
// HELPERS
|
|
249
|
+
// ---------------------------
|
|
250
|
+
findService(serviceType, domainType) {
|
|
251
|
+
return this.deviceServices.find(s => s.serviceType === serviceType && s.domainType === domainType);
|
|
364
252
|
}
|
|
365
|
-
// authenticate() before connect() so token is valid for both
|
|
366
|
-
// sendCommand HTTP calls and the WebSocket connection
|
|
367
253
|
async setupWebSocket() {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
await this.client.connect();
|
|
371
|
-
}
|
|
372
|
-
catch (error) {
|
|
373
|
-
this.client.debug('Failed to connect to SmartHQ WebSocket for AC: ' + error);
|
|
374
|
-
}
|
|
254
|
+
await this.client.authenticate();
|
|
255
|
+
await this.client.connect();
|
|
375
256
|
}
|
|
376
257
|
}
|
|
377
258
|
//# sourceMappingURL=airConditioner.js.map
|