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.
Files changed (87) hide show
  1. package/dist/airConditioner/airConditioner.d.ts +21 -20
  2. package/dist/airConditioner/airConditioner.js +168 -287
  3. package/dist/airConditioner/airConditioner.js.map +1 -1
  4. package/dist/airConditionerServices.d.ts +1 -1
  5. package/dist/dishwasher/controlLock.d.ts +1 -1
  6. package/dist/dishwasher/controlLock.js +1 -1
  7. package/dist/dishwasher/controlLock.js.map +1 -1
  8. package/dist/dishwasher/dishwasher.d.ts +1 -1
  9. package/dist/dishwasher/dishwasher.js +1 -1
  10. package/dist/dishwasher/dishwasher.js.map +1 -1
  11. package/dist/dishwasher/fanFresh.d.ts +1 -1
  12. package/dist/dishwasher/fanFresh.js +1 -1
  13. package/dist/dishwasher/fanFresh.js.map +1 -1
  14. package/dist/dishwasher/sabbathMode.d.ts +1 -1
  15. package/dist/dishwasher/sabbathMode.js +1 -1
  16. package/dist/dishwasher/sabbathMode.js.map +1 -1
  17. package/dist/dishwasher/soundOption.d.ts +1 -1
  18. package/dist/dishwasher/soundOption.js +1 -1
  19. package/dist/dishwasher/soundOption.js.map +1 -1
  20. package/dist/dishwasherServices.d.ts +1 -1
  21. package/dist/platform.d.ts +3 -2
  22. package/dist/platform.js +129 -119
  23. package/dist/platform.js.map +1 -1
  24. package/dist/refrigerator/controlLock.d.ts +1 -1
  25. package/dist/refrigerator/controlLock.js +1 -1
  26. package/dist/refrigerator/controlLock.js.map +1 -1
  27. package/dist/refrigerator/convertibleDrawer.d.ts +1 -1
  28. package/dist/refrigerator/convertibleDrawer.js +1 -1
  29. package/dist/refrigerator/convertibleDrawer.js.map +1 -1
  30. package/dist/refrigerator/dispenserLight.d.ts +1 -1
  31. package/dist/refrigerator/dispenserLight.js +1 -1
  32. package/dist/refrigerator/dispenserLight.js.map +1 -1
  33. package/dist/refrigerator/energy.d.ts +1 -1
  34. package/dist/refrigerator/energy.js +1 -1
  35. package/dist/refrigerator/energy.js.map +1 -1
  36. package/dist/refrigerator/freezer.d.ts +1 -1
  37. package/dist/refrigerator/freezer.js +1 -1
  38. package/dist/refrigerator/freezer.js.map +1 -1
  39. package/dist/refrigerator/iceMaker.d.ts +1 -1
  40. package/dist/refrigerator/iceMaker.js +1 -1
  41. package/dist/refrigerator/iceMaker.js.map +1 -1
  42. package/dist/refrigerator/interiorLight.d.ts +1 -1
  43. package/dist/refrigerator/interiorLight.js +1 -1
  44. package/dist/refrigerator/interiorLight.js.map +1 -1
  45. package/dist/refrigerator/refrigerator.d.ts +1 -1
  46. package/dist/refrigerator/refrigerator.js +1 -1
  47. package/dist/refrigerator/refrigerator.js.map +1 -1
  48. package/dist/refrigerator/refrigeratorAlerts.d.ts +1 -1
  49. package/dist/refrigerator/refrigeratorAlerts.js +1 -1
  50. package/dist/refrigerator/refrigeratorAlerts.js.map +1 -1
  51. package/dist/refrigerator/sabbathMode.d.ts +1 -1
  52. package/dist/refrigerator/sabbathMode.js +1 -1
  53. package/dist/refrigerator/sabbathMode.js.map +1 -1
  54. package/dist/refrigerator/temperatureUnits.d.ts +1 -1
  55. package/dist/refrigerator/temperatureUnits.js +1 -1
  56. package/dist/refrigerator/temperatureUnits.js.map +1 -1
  57. package/dist/refrigerator/turboCoolMode.d.ts +1 -1
  58. package/dist/refrigerator/turboCoolMode.js +1 -1
  59. package/dist/refrigerator/turboCoolMode.js.map +1 -1
  60. package/dist/refrigerator/waterFilter.d.ts +1 -1
  61. package/dist/refrigerator/waterFilter.js +1 -1
  62. package/dist/refrigerator/waterFilter.js.map +1 -1
  63. package/dist/refrigeratorServices.d.ts +1 -1
  64. package/package.json +9 -5
  65. package/src/airConditioner/airConditioner.ts +252 -335
  66. package/src/airConditionerServices.ts +1 -1
  67. package/src/dishwasher/controlLock.ts +1 -1
  68. package/src/dishwasher/dishwasher.ts +1 -1
  69. package/src/dishwasher/fanFresh.ts +1 -1
  70. package/src/dishwasher/sabbathMode.ts +1 -1
  71. package/src/dishwasher/soundOption.ts +1 -1
  72. package/src/dishwasherServices.ts +1 -1
  73. package/src/platform.ts +234 -152
  74. package/src/refrigerator/controlLock.ts +1 -1
  75. package/src/refrigerator/convertibleDrawer.ts +1 -1
  76. package/src/refrigerator/dispenserLight.ts +1 -1
  77. package/src/refrigerator/energy.ts +1 -1
  78. package/src/refrigerator/freezer.ts +1 -1
  79. package/src/refrigerator/iceMaker.ts +1 -1
  80. package/src/refrigerator/interiorLight.ts +1 -1
  81. package/src/refrigerator/refrigerator.ts +1 -1
  82. package/src/refrigerator/refrigeratorAlerts.ts +1 -1
  83. package/src/refrigerator/sabbathMode.ts +1 -1
  84. package/src/refrigerator/temperatureUnits.ts +1 -1
  85. package/src/refrigerator/turboCoolMode.ts +1 -1
  86. package/src/refrigerator/waterFilter.ts +1 -1
  87. package/src/refrigeratorServices.ts +1 -1
@@ -1,37 +1,38 @@
1
- import { CharacteristicValue, PlatformAccessory, Service, Characteristic } from 'homebridge';
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: DeviceService[];
8
- readonly deviceId: string;
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 modeSwitches;
21
+ private fanAutoSwitch;
22
+ private readonly modeToHK;
23
+ private readonly hkToMode;
22
24
  constructor(platform: SmartHqPlatform, accessory: PlatformAccessory, deviceServices: DeviceService[], deviceId: string);
23
- private updateModeSwitches;
25
+ private setupAccessories;
26
+ private setFanSpeed;
27
+ private setFanAuto;
24
28
  private fanSpeedToPercent;
25
29
  private percentToFanSpeed;
26
- private sendThermostatCommand;
30
+ private setTargetHeatingCoolingState;
31
+ private setTargetTemperature;
32
+ private getTargetHeatingCoolingState;
33
+ private sendCommand;
34
+ private handleUpdate;
35
+ private syncState;
27
36
  private findService;
28
- getCurrentHeatingCoolingState(): Promise<number>;
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
- // Mode switch service references for mutual exclusion updates
21
- modeSwitches = {};
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.COOL,
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
- // Seed initial state from service data at startup
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
- if (thermoSvc.state.mode != null)
55
+ }
56
+ if (thermoSvc.state.mode != null) {
55
57
  this.targetMode = thermoSvc.state.mode;
56
- if (thermoSvc.state.fanSpeed != null)
57
- this.targetFanSpeed = thermoSvc.state.fanSpeed;
58
- if (thermoSvc.state.on != null)
59
- this.isOn = thermoSvc.state.on;
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
- if (message.state?.fanSpeed !== undefined) {
81
- this.targetFanSpeed = message.state.fanSpeed;
82
- this.acFan.getCharacteristic(this.Characteristic.RotationSpeed).updateValue(this.fanSpeedToPercent(this.targetFanSpeed));
64
+ else {
65
+ this.fanAuto = false;
66
+ this.fanSpeedCloud = fs;
67
+ this.fanSpeedPercent = this.fanSpeedToPercent(fs);
83
68
  }
84
69
  }
85
- if (message.domainType === 'cloud.smarthq.domain.state.compressor') {
86
- const running = message.state?.on;
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
- // ── Accessory information ────────────────────────────────────────────────
94
- this.accessory.getService(this.Service.AccessoryInformation)
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 || 'AHTT08BC')
97
- .setCharacteristic(this.Characteristic.SerialNumber, accessory.context.device.serial || 'Default-Serial');
98
- // ── Thermostat service ───────────────────────────────────────────────────
99
- const displayName = 'Air Conditioner';
100
- this.acThermostat = this.accessory.getService(displayName)
101
- || this.accessory.addService(this.Service.Thermostat, displayName, 'ac-thermo1');
102
- this.acThermostat.setCharacteristic(this.Characteristic.Name, displayName);
103
- this.acThermostat.addOptionalCharacteristic(this.Characteristic.ConfiguredName);
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.getCharacteristic(this.Characteristic.CurrentTemperature)
138
- .onGet(this.getCurrentTemperature.bind(this));
139
- this.acThermostat.getCharacteristic(this.Characteristic.TargetTemperature)
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
- this.acThermostat.getCharacteristic(this.Characteristic.TemperatureDisplayUnits)
143
- .onGet(this.handleTemperatureDisplayUnitsGet.bind(this))
144
- .onSet(this.handleTemperatureDisplayUnitsSet.bind(this));
145
- // ── Fan service ───────────────────────────────────────────────────────────
146
- const fanDisplayName = 'AC Fan Speed';
147
- this.acFan = this.accessory.getService(fanDisplayName)
148
- || this.accessory.addService(this.Service.Fan, fanDisplayName, 'ac-fan1');
149
- this.acFan.setCharacteristic(this.Characteristic.Name, fanDisplayName);
150
- this.acFan.addOptionalCharacteristic(this.Characteristic.ConfiguredName);
151
- this.acFan.setCharacteristic(this.Characteristic.ConfiguredName, fanDisplayName);
152
- this.acFan.getCharacteristic(this.Characteristic.On)
153
- .onGet(() => this.isOn)
154
- .onSet(async (value) => {
155
- await this.setTargetHeatingCoolingState(value
156
- ? this.Characteristic.TargetHeatingCoolingState.COOL
157
- : this.Characteristic.TargetHeatingCoolingState.OFF);
158
- });
159
- this.acFan.getCharacteristic(this.Characteristic.RotationSpeed)
160
- .setProps({ minValue: 0, maxValue: 100, minStep: 25 })
161
- .onGet(() => this.fanSpeedToPercent(this.targetFanSpeed))
162
- .onSet(async (value) => {
163
- this.targetFanSpeed = this.percentToFanSpeed(value);
164
- await this.sendThermostatCommand(this.targetMode, this.targetCelsius, this.targetFanSpeed, this.isOn);
165
- });
166
- // ── Mode switches ─────────────────────────────────────────────────────────
167
- const modes = [
168
- { name: 'Cool', mode: 'cloud.smarthq.type.thermostatmode.cool', subtype: 'ac-mode-cool' },
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
- // ── Mode switch mutual exclusion ───────────────────────────────────────────
224
- updateModeSwitches(activeMode) {
225
- for (const [mode, svc] of Object.entries(this.modeSwitches)) {
226
- svc.getCharacteristic(this.Characteristic.On).updateValue(this.isOn && mode === activeMode);
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': return 100;
233
- case 'cloud.smarthq.type.fanspeed.medium': return 50;
234
- case 'cloud.smarthq.type.fanspeed.low': return 25;
235
- default: return 0;
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
- if (percent >= 15)
244
- return 'cloud.smarthq.type.fanspeed.low';
245
- return 'cloud.smarthq.type.fanspeed.auto';
161
+ return 'cloud.smarthq.type.fanspeed.low';
246
162
  }
247
- // ── Shared command helper ──────────────────────────────────────────────────
248
- async sendThermostatCommand(mode, celsius, fanSpeed, on) {
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
- // Only send mode and fanSpeed when turning on
257
- if (on) {
258
- command.mode = mode;
259
- // Dry mode doesn't accept fanSpeed
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
- const cmdBody = {
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
- async setTargetHeatingCoolingState(value) {
318
- const hkValue = value;
319
- const HK = this.Characteristic.TargetHeatingCoolingState;
320
- if (hkValue === HK.OFF) {
321
- this.isOn = false;
322
- await this.sendThermostatCommand(this.targetMode, this.targetCelsius, this.targetFanSpeed, false);
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
- this.isOn = true;
329
- const newMode = this.hkToMode[hkValue] ?? 'cloud.smarthq.type.thermostatmode.cool';
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
- catch (error) {
345
- this.client.debug('Error getting AC current temperature: ' + error);
346
- return this.targetCelsius;
225
+ if (state.mode !== undefined) {
226
+ this.targetMode = state.mode;
347
227
  }
348
- }
349
- getTargetTemperature() {
350
- return this.targetCelsius;
351
- }
352
- // Only send command when AC is already on — don't turn it on just by moving the temp slider
353
- async setTargetTemperature(value) {
354
- this.targetCelsius = value;
355
- if (this.isOn) {
356
- await this.sendThermostatCommand(this.targetMode, this.targetCelsius, this.targetFanSpeed, true);
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
- handleTemperatureDisplayUnitsGet() {
360
- return this.Characteristic.TemperatureDisplayUnits.FAHRENHEIT;
242
+ async syncState() {
243
+ this.acThermostat
244
+ .getCharacteristic(this.Characteristic.CurrentTemperature)
245
+ .updateValue(this.targetCelsius);
361
246
  }
362
- handleTemperatureDisplayUnitsSet(value) {
363
- this.client.debug('AC Temperature Display Units set to: ' + value);
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
- try {
369
- await this.client.authenticate();
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