@homebridge-plugins/homebridge-smarthq 0.5.0-beta.22 → 0.5.0-beta.23

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 (51) hide show
  1. package/MATTER.md +304 -0
  2. package/dist/devices/advantium.js.map +1 -1
  3. package/dist/devices/airConditioner.js.map +1 -1
  4. package/dist/devices/beverageCenter.js.map +1 -1
  5. package/dist/devices/clothesDryer.js.map +1 -1
  6. package/dist/devices/clothesWasher.js.map +1 -1
  7. package/dist/devices/coffeeMaker.js.map +1 -1
  8. package/dist/devices/device.d.ts +83 -21
  9. package/dist/devices/device.d.ts.map +1 -1
  10. package/dist/devices/device.js +216 -70
  11. package/dist/devices/device.js.map +1 -1
  12. package/dist/devices/dishwasher.js.map +1 -1
  13. package/dist/devices/hood.js.map +1 -1
  14. package/dist/devices/microwave.js.map +1 -1
  15. package/dist/devices/oven.js.map +1 -1
  16. package/dist/devices/refrigerator.d.ts +37 -0
  17. package/dist/devices/refrigerator.d.ts.map +1 -1
  18. package/dist/devices/refrigerator.js +392 -172
  19. package/dist/devices/refrigerator.js.map +1 -1
  20. package/dist/devices/waterFilter.js.map +1 -1
  21. package/dist/devices/waterHeater.js.map +1 -1
  22. package/dist/devices/waterSoftener.js.map +1 -1
  23. package/dist/platform.d.ts +15 -1
  24. package/dist/platform.d.ts.map +1 -1
  25. package/dist/platform.js +82 -32
  26. package/dist/platform.js.map +1 -1
  27. package/dist/settings.d.ts +1 -0
  28. package/dist/settings.d.ts.map +1 -1
  29. package/dist/settings.js.map +1 -1
  30. package/docs/assets/search.js +1 -1
  31. package/docs/classes/SmartHQPlatform.html +15 -9
  32. package/docs/interfaces/DeviceOptions.html +2 -2
  33. package/docs/interfaces/SmartHQPlatformConfig.html +2 -2
  34. package/docs/interfaces/SmartHqContext.html +2 -2
  35. package/docs/interfaces/SmartHqERDResponse.html +2 -2
  36. package/docs/interfaces/credentials.html +2 -2
  37. package/docs/interfaces/devicesConfig.html +3 -2
  38. package/docs/interfaces/options.html +2 -2
  39. package/docs/variables/API_URL.html +1 -1
  40. package/docs/variables/ERD_CODES.html +1 -1
  41. package/docs/variables/ERD_TYPES.html +1 -1
  42. package/docs/variables/KEEPALIVE_TIMEOUT.html +1 -1
  43. package/docs/variables/LOGIN_URL.html +1 -1
  44. package/docs/variables/OAUTH2_CLIENT_ID.html +1 -1
  45. package/docs/variables/OAUTH2_CLIENT_SECRET.html +1 -1
  46. package/docs/variables/OAUTH2_REDIRECT_URI.html +1 -1
  47. package/docs/variables/PLATFORM_NAME.html +1 -1
  48. package/docs/variables/PLUGIN_NAME.html +1 -1
  49. package/docs/variables/SECURE_URL.html +1 -1
  50. package/docs/variables/default.html +1 -1
  51. package/package.json +1 -1
@@ -1,38 +1,172 @@
1
1
  import { interval, skipWhile } from 'rxjs';
2
2
  import { ERD_TYPES } from '../settings.js';
3
3
  import { deviceBase } from './device.js';
4
+ /**
5
+ * SmartHQ Refrigerator - Unified HAP/Matter Implementation
6
+ * Supports both HomeKit Accessory Protocol and Matter protocol
7
+ */
4
8
  export class SmartHQRefrigerator extends deviceBase {
5
9
  platform;
6
10
  device;
7
11
  // Updates
8
12
  SensorUpdateInProgress;
9
13
  deviceStatus;
14
+ // Matter support override flag
15
+ useMatterOverride = false;
10
16
  constructor(platform, accessory, device) {
11
17
  super(platform, accessory, device);
12
18
  this.platform = platform;
13
19
  this.device = device;
20
+ // Check if we should use Matter protocol
21
+ this.useMatterOverride = device.useMatter ?? false;
14
22
  this.debugLog(`Refrigerator Features: ${JSON.stringify(accessory.context.device.features)}`);
15
- // DOOR_STATUS ERD returns hex string where each byte represents a door compartment
16
- // Format: "XXYYZZ..." where XX=byte0, YY=byte1, etc.
17
- // 0xFF = not available/unused, 0x00 = closed, 0x01 = open
18
- // Byte mapping for this fridge model:
19
- // byte0 (pos 0-1) = Fridge Right Door
20
- // byte1 (pos 2-3) = Fridge Left Door
21
- // byte2 (pos 4-5) = Freezer Door
22
- // byte3 (pos 6-7) = Unused (0xFF)
23
- // Check which door bytes are available (not 0xFF)
23
+ this.debugLog(`Using protocol: ${this.useMatterOverride ? 'Matter' : 'HAP'}`);
24
+ // Initialize the appropriate protocol
25
+ if (this.useMatterOverride) {
26
+ this.initializeMatter();
27
+ }
28
+ else {
29
+ this.initializeHAP();
30
+ }
31
+ // Start periodic refresh
32
+ this.SensorUpdateInProgress = false;
33
+ interval(this.deviceRefreshRate * 1000)
34
+ .pipe(skipWhile(() => this.SensorUpdateInProgress))
35
+ .subscribe(async () => {
36
+ await this.refreshDeviceStatus();
37
+ });
38
+ // Initial refresh after 2 seconds
39
+ setTimeout(() => this.refreshDeviceStatus(), 2000);
40
+ }
41
+ /**
42
+ * Initialize Matter protocol
43
+ */
44
+ initializeMatter() {
45
+ const matterAPI = this.api.matter;
46
+ if (!matterAPI) {
47
+ this.errorLog('Matter API not available');
48
+ return;
49
+ }
50
+ const serialNumber = this.device.applianceId || 'unknown';
51
+ this.matterUuid = matterAPI.uuid.generate(serialNumber);
52
+ // Create Matter accessory configuration with refrigerator-specific clusters
53
+ const matterAccessory = {
54
+ UUID: this.matterUuid,
55
+ displayName: this.device.nickname || 'SmartHQ Refrigerator',
56
+ serialNumber,
57
+ manufacturer: this.device.brand && this.device.brand !== 'Unknown' ? this.device.brand : 'GE Appliances',
58
+ model: this.device.model || 'SmartHQ',
59
+ firmwareRevision: this.deviceFirmwareVersion,
60
+ hardwareRevision: this.deviceFirmwareVersion,
61
+ deviceType: matterAPI.deviceTypes.RefrigeratorFreezer,
62
+ clusters: {
63
+ // Temperature control for main compartment
64
+ thermostat: {
65
+ localTemperature: 400, // 4°C in 0.01°C units
66
+ occupiedCoolingSetpoint: 400,
67
+ systemMode: 3, // COOL
68
+ thermostatRunningMode: 3,
69
+ controlSequenceOfOperation: 2, // cooling only
70
+ },
71
+ temperatureMeasurement: {
72
+ measuredValue: 400, // 4°C
73
+ minMeasuredValue: 0, // 0°C
74
+ maxMeasuredValue: 720, // 7.2°C
75
+ },
76
+ // Refrigerator and Temperature Controlled Cabinet Mode Cluster (0x0052)
77
+ refrigeratorAndTemperatureControlledCabinetMode: {
78
+ mode: 0, // 0=Normal, 1=RapidCool, 2=RapidFreeze
79
+ },
80
+ // Refrigerator Alarm Cluster (0x0057)
81
+ refrigeratorAlarm: {
82
+ mask: 0, // Bitmap: bit 0=door open, bit 1=temp high, bit 2=temp low
83
+ state: 0, // Current alarm state
84
+ supported: 7, // Support door, temp high, temp low alarms
85
+ },
86
+ },
87
+ // Multi-endpoint structure for fridge and freezer compartments
88
+ parts: [
89
+ // Fridge compartment endpoint
90
+ {
91
+ UUID: matterAPI.uuid.generate(`${serialNumber}-fridge`),
92
+ displayName: 'Fridge Compartment',
93
+ serialNumber: `${serialNumber}-fridge`,
94
+ manufacturer: this.device.brand || 'GE Appliances',
95
+ model: `${this.device.model || 'SmartHQ'} Fridge`,
96
+ deviceType: matterAPI.deviceTypes.TemperatureSensor,
97
+ clusters: {
98
+ temperatureMeasurement: {
99
+ measuredValue: 400, // 4°C
100
+ minMeasuredValue: 0,
101
+ maxMeasuredValue: 720,
102
+ },
103
+ // Boolean state for ice maker
104
+ booleanState: {
105
+ stateValue: false, // Ice maker state
106
+ },
107
+ // Resource monitoring for water filter
108
+ resourceMonitoring: {
109
+ condition: 100, // 100% = OK, 0% = needs replacement
110
+ degradationDirection: 1, // 1 = down (degrades over time)
111
+ changeIndication: 0, // 0=OK, 1=Warning, 2=Critical
112
+ productIdentifierType: 0, // 0=UPC, 1=GTIN8, 2=EAN, 3=GTIN14
113
+ productIdentifierValue: 'FILTER',
114
+ },
115
+ },
116
+ },
117
+ // Freezer compartment endpoint
118
+ {
119
+ UUID: matterAPI.uuid.generate(`${serialNumber}-freezer`),
120
+ displayName: 'Freezer Compartment',
121
+ serialNumber: `${serialNumber}-freezer`,
122
+ manufacturer: this.device.brand || 'GE Appliances',
123
+ model: `${this.device.model || 'SmartHQ'} Freezer`,
124
+ deviceType: matterAPI.deviceTypes.TemperatureSensor,
125
+ clusters: {
126
+ temperatureMeasurement: {
127
+ measuredValue: -1800, // -18°C
128
+ minMeasuredValue: -2100,
129
+ maxMeasuredValue: -330,
130
+ },
131
+ // Boolean state for turbo freeze
132
+ booleanState: {
133
+ stateValue: false, // Turbo freeze state
134
+ },
135
+ },
136
+ },
137
+ ],
138
+ handlers: {
139
+ thermostat: {
140
+ setpointRaiseLower: async (request) => {
141
+ await this.handleMatterSetpointChange(request);
142
+ },
143
+ },
144
+ refrigeratorAndTemperatureControlledCabinetMode: {
145
+ changeToMode: async (request) => {
146
+ await this.handleMatterModeChange(request);
147
+ },
148
+ },
149
+ },
150
+ };
151
+ // Register Matter accessory
152
+ matterAPI.registerAccessory(matterAccessory);
153
+ this.infoLog('Created Matter Refrigerator with advanced clusters and multi-endpoint structure');
154
+ }
155
+ /**
156
+ * Initialize HAP (HomeKit) protocol
157
+ */
158
+ initializeHAP() {
159
+ // Helper functions for parsing ERD values
24
160
  const checkDoorAvailability = async () => {
25
161
  const r = await this.readErd(ERD_TYPES.DOOR_STATUS);
26
162
  if (!r || r.length < 2) {
27
163
  return { byte0: false, byte1: false, byte2: false };
28
164
  }
29
165
  try {
30
- // Parse hex bytes - each byte is 2 characters
31
166
  const byte0 = r.substring(0, 2);
32
167
  const byte1 = r.substring(2, 4);
33
168
  const byte2 = r.substring(4, 6);
34
169
  this.debugLog(`Door status bytes: byte0=${byte0}, byte1=${byte1}, byte2=${byte2}`);
35
- // A byte is available if it exists and isn't 0xFF (unused)
36
170
  return {
37
171
  byte0: byte0 !== 'FF' && byte0 !== '',
38
172
  byte1: byte1 !== 'FF' && byte1 !== '' && byte1 !== undefined,
@@ -44,7 +178,6 @@ export class SmartHQRefrigerator extends deviceBase {
44
178
  return { byte0: false, byte1: false, byte2: false };
45
179
  }
46
180
  };
47
- // Helper to parse individual door byte
48
181
  const parseDoorByte = async (byteIndex) => {
49
182
  const r = await this.readErd(ERD_TYPES.DOOR_STATUS);
50
183
  if (!r) {
@@ -54,7 +187,7 @@ export class SmartHQRefrigerator extends deviceBase {
54
187
  const byteValue = r.substring(byteIndex * 2, byteIndex * 2 + 2);
55
188
  const state = Number.parseInt(byteValue, 16);
56
189
  this.debugLog(`Door byte${byteIndex} value: ${byteValue} = ${state}`);
57
- return state !== 0; // 0 = closed, anything else = open
190
+ return state !== 0;
58
191
  }
59
192
  catch (parseError) {
60
193
  this.debugLog(`Door byte${byteIndex} parse error: ${parseError}`);
@@ -66,48 +199,52 @@ export class SmartHQRefrigerator extends deviceBase {
66
199
  const availableDoors = await checkDoorAvailability();
67
200
  // Byte 0: Fridge Right Door
68
201
  if (availableDoors.byte0) {
69
- const doorService = this.accessory.getService('Fridge Right Door') ?? this.accessory.addService(this.platform.Service.ContactSensor, 'Fridge Right Door', 'FridgeRightDoor');
70
- doorService.setCharacteristic(this.platform.Characteristic.Name, 'Fridge Right Door');
71
- doorService
202
+ const fridgeRightDoor = this.accessory.getService('Fridge Right Door') ?? this.accessory.addService(this.platform.Service.ContactSensor, 'Fridge Right Door', 'FridgeRightDoor');
203
+ fridgeRightDoor.setCharacteristic(this.platform.Characteristic.Name, 'Fridge Right Door');
204
+ fridgeRightDoor
72
205
  .getCharacteristic(this.platform.Characteristic.ContactSensorState)
73
- .onGet(async () => parseDoorByte(0));
206
+ .onGet(async () => {
207
+ const isOpen = await parseDoorByte(0);
208
+ return isOpen ? this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED;
209
+ });
74
210
  }
75
211
  // Byte 1: Fridge Left Door
76
212
  if (availableDoors.byte1) {
77
- const door2Service = this.accessory.getService('Fridge Left Door') ?? this.accessory.addService(this.platform.Service.ContactSensor, 'Fridge Left Door', 'FridgeLeftDoor');
78
- door2Service.setCharacteristic(this.platform.Characteristic.Name, 'Fridge Left Door');
79
- door2Service
213
+ const fridgeLeftDoor = this.accessory.getService('Fridge Left Door') ?? this.accessory.addService(this.platform.Service.ContactSensor, 'Fridge Left Door', 'FridgeLeftDoor');
214
+ fridgeLeftDoor.setCharacteristic(this.platform.Characteristic.Name, 'Fridge Left Door');
215
+ fridgeLeftDoor
80
216
  .getCharacteristic(this.platform.Characteristic.ContactSensorState)
81
- .onGet(async () => parseDoorByte(1));
217
+ .onGet(async () => {
218
+ const isOpen = await parseDoorByte(1);
219
+ return isOpen ? this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED;
220
+ });
82
221
  }
83
222
  // Byte 2: Freezer Door
84
223
  if (availableDoors.byte2) {
85
- const door3Service = this.accessory.getService('Freezer Door') ?? this.accessory.addService(this.platform.Service.ContactSensor, 'Freezer Door', 'FreezerDoor');
86
- door3Service.setCharacteristic(this.platform.Characteristic.Name, 'Freezer Door');
87
- door3Service
224
+ const freezerDoor = this.accessory.getService('Freezer Door') ?? this.accessory.addService(this.platform.Service.ContactSensor, 'Freezer Door', 'FreezerDoor');
225
+ freezerDoor.setCharacteristic(this.platform.Characteristic.Name, 'Freezer Door');
226
+ freezerDoor
88
227
  .getCharacteristic(this.platform.Characteristic.ContactSensorState)
89
- .onGet(async () => parseDoorByte(2));
228
+ .onGet(async () => {
229
+ const isOpen = await parseDoorByte(2);
230
+ return isOpen ? this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED;
231
+ });
90
232
  }
91
233
  })();
92
- // Ice Bucket Status (Contact Sensors - "open" when full)
93
- // Format: "XY" where X=fridge ice maker, Y=freezer ice maker
94
- // Each nibble: 0=empty, 1=not full, 2=full, 3=full
95
- // "Open" state (CONTACT_NOT_DETECTED) = bucket is full
96
- // Helper to parse ice bucket nibble (half-byte)
234
+ // Ice Bucket Status helpers
97
235
  const parseIceBucketNibble = async (nibbleIndex) => {
98
236
  const r = await this.readErd(ERD_TYPES.ICE_MAKER_BUCKET_STATUS);
99
237
  if (!r || r.length < 2) {
100
- return false; // Not full
238
+ return false;
101
239
  }
102
240
  try {
103
- const nibble = r.charAt(nibbleIndex);
104
- const status = Number.parseInt(nibble, 16);
105
- const isFull = status >= 2;
106
- this.debugLog(`Ice Bucket ${nibbleIndex === 0 ? 'Fridge' : 'Freezer'} status: ${nibble} (${status}) - ${isFull ? 'Full' : 'Not Full'}`);
107
- return isFull;
241
+ const nibbleValue = nibbleIndex === 0 ? r.charAt(0) : r.charAt(1);
242
+ const status = Number.parseInt(nibbleValue, 16);
243
+ this.debugLog(`Ice bucket nibble${nibbleIndex} value: ${nibbleValue} = ${status}`);
244
+ return status >= 2;
108
245
  }
109
246
  catch (parseError) {
110
- this.debugLog(`Ice Bucket ${nibbleIndex} parse error: ${parseError}`);
247
+ this.debugLog(`Ice bucket nibble${nibbleIndex} parse error: ${parseError}`);
111
248
  return false;
112
249
  }
113
250
  };
@@ -118,9 +255,7 @@ export class SmartHQRefrigerator extends deviceBase {
118
255
  .getCharacteristic(this.platform.Characteristic.ContactSensorState)
119
256
  .onGet(async () => {
120
257
  const isFull = await parseIceBucketNibble(0);
121
- return isFull
122
- ? this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED // Open = full
123
- : this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED; // Closed = not full
258
+ return isFull ? this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED;
124
259
  });
125
260
  // Freezer Ice Bucket (nibble 1)
126
261
  const freezerIceBucket = this.accessory.getService('Freezer Ice Bucket') ?? this.accessory.addService(this.platform.Service.ContactSensor, 'Freezer Ice Bucket', 'FreezerIceBucket');
@@ -129,163 +264,57 @@ export class SmartHQRefrigerator extends deviceBase {
129
264
  .getCharacteristic(this.platform.Characteristic.ContactSensorState)
130
265
  .onGet(async () => {
131
266
  const isFull = await parseIceBucketNibble(1);
132
- return isFull
133
- ? this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED // Open = full
134
- : this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED; // Closed = not full
267
+ return isFull ? this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED;
135
268
  });
136
- // Fridge Thermostat (with temperature sensor and target temp control)
269
+ // Fridge Thermostat
137
270
  const fridgeThermostat = this.accessory.getService('Fridge') ?? this.accessory.addService(this.platform.Service.Thermostat, 'Fridge', 'FridgeThermostat');
138
271
  fridgeThermostat.setCharacteristic(this.platform.Characteristic.Name, 'Fridge');
139
272
  fridgeThermostat.setCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits, this.platform.Characteristic.TemperatureDisplayUnits.FAHRENHEIT);
140
273
  fridgeThermostat.setCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, this.platform.Characteristic.CurrentHeatingCoolingState.COOL);
141
274
  fridgeThermostat.setCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState, this.platform.Characteristic.TargetHeatingCoolingState.COOL);
142
- // Restrict to only COOL mode
143
275
  fridgeThermostat
144
276
  .getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState)
145
277
  .setProps({ validValues: [this.platform.Characteristic.TargetHeatingCoolingState.COOL] });
146
278
  fridgeThermostat
147
279
  .getCharacteristic(this.platform.Characteristic.CurrentTemperature)
148
280
  .onGet(async () => {
149
- const r = await this.readErd(ERD_TYPES.CURRENT_TEMPERATURE);
150
- this.debugLog(`Raw CURRENT_TEMPERATURE ERD response: ${r}`);
151
- if (!r || r === 'undefined') {
152
- return 2.8; // Default to ~37°F in Celsius
153
- }
154
- try {
155
- const temps = JSON.parse(r);
156
- const fridgeTempF = temps.fridge || temps.Fridge;
157
- this.debugLog(`Fridge current temp: ${fridgeTempF}°F`);
158
- if (!fridgeTempF || Number.isNaN(Number(fridgeTempF))) {
159
- return 2.8;
160
- }
161
- return (Number(fridgeTempF) - 32) * 5 / 9;
162
- }
163
- catch (parseError) {
164
- this.debugLog(`Fridge Temperature: JSON parse error, using default`);
165
- return 2.8;
166
- }
281
+ const temp = await this.parseTemperature('fridge');
282
+ return temp ?? 4.0;
167
283
  });
168
284
  fridgeThermostat
169
285
  .getCharacteristic(this.platform.Characteristic.TargetTemperature)
170
- .setProps({ minValue: 0, maxValue: 7.2, minStep: 0.5 }) // 32°F to 45°F range
286
+ .setProps({ minValue: 0, maxValue: 7.2, minStep: 0.5 })
171
287
  .onGet(async () => {
172
- const r = await this.readErd(ERD_TYPES.TEMPERATURE_SETTING);
173
- if (!r || r === 'undefined') {
174
- return 3.3; // Default to ~38°F
175
- }
176
- try {
177
- const setpoints = JSON.parse(r);
178
- const fridgeTargetF = setpoints.fridge || setpoints.Fridge;
179
- this.debugLog(`Fridge target temp: ${fridgeTargetF}°F`);
180
- if (!fridgeTargetF || Number.isNaN(Number(fridgeTargetF))) {
181
- return 3.3;
182
- }
183
- return (Number(fridgeTargetF) - 32) * 5 / 9;
184
- }
185
- catch (parseError) {
186
- this.debugLog(`Fridge Target Temperature: JSON parse error, using default`);
187
- return 3.3;
188
- }
288
+ const temp = await this.parseSetpoint('fridge');
289
+ return temp ?? 4.0;
189
290
  })
190
291
  .onSet(async (value) => {
191
- try {
192
- // Convert Celsius to Fahrenheit
193
- const targetF = Math.round(value * 9 / 5 + 32);
194
- this.infoLog(`Setting Fridge target temperature to ${targetF}°F`);
195
- // Get current freezer setting to preserve it
196
- const currentSettings = await this.readErd(ERD_TYPES.TEMPERATURE_SETTING);
197
- let freezerTargetF = 0; // Default
198
- if (currentSettings && currentSettings !== 'undefined') {
199
- try {
200
- const setpoints = JSON.parse(currentSettings);
201
- freezerTargetF = setpoints.freezer || setpoints.Freezer || 0;
202
- }
203
- catch { }
204
- }
205
- // Write both fridge and freezer settings
206
- const newSettings = JSON.stringify({ fridge: targetF, freezer: freezerTargetF });
207
- await this.writeErd(ERD_TYPES.TEMPERATURE_SETTING, newSettings);
208
- }
209
- catch (error) {
210
- this.warnLog?.(`Fridge set target temp error: ${error?.message ?? error}`);
211
- }
292
+ await this.writeSetpoint('fridge', value);
212
293
  });
213
- // Freezer Thermostat (with temperature sensor and target temp control)
294
+ // Freezer Thermostat
214
295
  const freezerThermostat = this.accessory.getService('Freezer') ?? this.accessory.addService(this.platform.Service.Thermostat, 'Freezer', 'FreezerThermostat');
215
296
  freezerThermostat.setCharacteristic(this.platform.Characteristic.Name, 'Freezer');
216
297
  freezerThermostat.setCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits, this.platform.Characteristic.TemperatureDisplayUnits.FAHRENHEIT);
217
298
  freezerThermostat.setCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, this.platform.Characteristic.CurrentHeatingCoolingState.COOL);
218
299
  freezerThermostat.setCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState, this.platform.Characteristic.TargetHeatingCoolingState.COOL);
219
- // Restrict to only COOL mode
220
300
  freezerThermostat
221
301
  .getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState)
222
302
  .setProps({ validValues: [this.platform.Characteristic.TargetHeatingCoolingState.COOL] });
223
303
  freezerThermostat
224
304
  .getCharacteristic(this.platform.Characteristic.CurrentTemperature)
225
305
  .onGet(async () => {
226
- const r = await this.readErd(ERD_TYPES.CURRENT_TEMPERATURE);
227
- this.debugLog(`Raw CURRENT_TEMPERATURE ERD response: ${r}`);
228
- if (!r || r === 'undefined') {
229
- return -17.8; // Default to ~0°F in Celsius
230
- }
231
- try {
232
- const temps = JSON.parse(r);
233
- const freezerTempF = temps.freezer || temps.Freezer;
234
- this.debugLog(`Freezer current temp: ${freezerTempF}°F`);
235
- if (!freezerTempF || Number.isNaN(Number(freezerTempF))) {
236
- return -17.8;
237
- }
238
- return (Number(freezerTempF) - 32) * 5 / 9;
239
- }
240
- catch (parseError) {
241
- this.debugLog(`Freezer Temperature: JSON parse error, using default`);
242
- return -17.8;
243
- }
306
+ const temp = await this.parseTemperature('freezer');
307
+ return temp ?? -18.0;
244
308
  });
245
309
  freezerThermostat
246
310
  .getCharacteristic(this.platform.Characteristic.TargetTemperature)
247
- .setProps({ minValue: -21, maxValue: -3.3, minStep: 0.5 }) // -6°F to 26°F range
311
+ .setProps({ minValue: -21, maxValue: -3.3, minStep: 0.5 })
248
312
  .onGet(async () => {
249
- const r = await this.readErd(ERD_TYPES.TEMPERATURE_SETTING);
250
- if (!r || r === 'undefined') {
251
- return -17.8; // Default to ~0°F
252
- }
253
- try {
254
- const setpoints = JSON.parse(r);
255
- const freezerTargetF = setpoints.freezer || setpoints.Freezer;
256
- this.debugLog(`Freezer target temp: ${freezerTargetF}°F`);
257
- if (!freezerTargetF || Number.isNaN(Number(freezerTargetF))) {
258
- return -17.8;
259
- }
260
- return (Number(freezerTargetF) - 32) * 5 / 9;
261
- }
262
- catch (parseError) {
263
- this.debugLog(`Freezer Target Temperature: JSON parse error, using default`);
264
- return -17.8;
265
- }
313
+ const temp = await this.parseSetpoint('freezer');
314
+ return temp ?? -18.0;
266
315
  })
267
316
  .onSet(async (value) => {
268
- try {
269
- // Convert Celsius to Fahrenheit
270
- const targetF = Math.round(value * 9 / 5 + 32);
271
- this.infoLog(`Setting Freezer target temperature to ${targetF}°F`);
272
- // Get current fridge setting to preserve it
273
- const currentSettings = await this.readErd(ERD_TYPES.TEMPERATURE_SETTING);
274
- let fridgeTargetF = 37; // Default
275
- if (currentSettings && currentSettings !== 'undefined') {
276
- try {
277
- const setpoints = JSON.parse(currentSettings);
278
- fridgeTargetF = setpoints.fridge || setpoints.Fridge || 37;
279
- }
280
- catch { }
281
- }
282
- // Write both fridge and freezer settings
283
- const newSettings = JSON.stringify({ fridge: fridgeTargetF, freezer: targetF });
284
- await this.writeErd(ERD_TYPES.TEMPERATURE_SETTING, newSettings);
285
- }
286
- catch (error) {
287
- this.warnLog?.(`Freezer set target temp error: ${error?.message ?? error}`);
288
- }
317
+ await this.writeSetpoint('freezer', value);
289
318
  });
290
319
  // Air Filter Maintenance
291
320
  const filterService = this.accessory.getService('Air Filter') ?? this.accessory.addService(this.platform.Service.FilterMaintenance, 'Air Filter', 'AirFilter');
@@ -301,7 +330,7 @@ export class SmartHQRefrigerator extends deviceBase {
301
330
  ? this.platform.Characteristic.FilterChangeIndication.CHANGE_FILTER
302
331
  : this.platform.Characteristic.FilterChangeIndication.FILTER_OK;
303
332
  });
304
- // Ice Maker Control (Switch)
333
+ // Ice Maker Control
305
334
  const iceMakerService = this.accessory.getService('Ice Maker') ?? this.accessory.addService(this.platform.Service.Switch, 'Ice Maker', 'IceMaker');
306
335
  iceMakerService.setCharacteristic(this.platform.Characteristic.Name, 'Ice Maker');
307
336
  iceMakerService
@@ -337,16 +366,207 @@ export class SmartHQRefrigerator extends deviceBase {
337
366
  .onSet(async (value) => {
338
367
  await this.writeErd(ERD_TYPES.TURBO_FREEZE_STATUS, value);
339
368
  });
340
- // this is subject we use to track when we need to POST changes to the SmartHQ API
341
- this.SensorUpdateInProgress = false;
342
- // Retrieve initial values and updateHomekit
343
- // this.refreshStatus()
344
- // Start an update interval
345
- interval(this.deviceRefreshRate * 10000)
346
- .pipe(skipWhile(() => this.SensorUpdateInProgress))
347
- .subscribe(async () => {
348
- // await this.refreshStatus()
349
- });
369
+ }
370
+ /**
371
+ * Shared helper: Parse temperature from ERD (works for both HAP and Matter)
372
+ */
373
+ async parseTemperature(compartment) {
374
+ const r = await this.readErd(ERD_TYPES.CURRENT_TEMPERATURE);
375
+ this.debugLog(`Raw CURRENT_TEMPERATURE ERD response: ${r}`);
376
+ if (!r || r === 'undefined') {
377
+ return undefined;
378
+ }
379
+ try {
380
+ const parsed = JSON.parse(r);
381
+ if (parsed[compartment] !== undefined) {
382
+ const tempCelsius = Number(parsed[compartment]);
383
+ this.debugLog(`${compartment} temperature: ${tempCelsius}°C`);
384
+ return tempCelsius;
385
+ }
386
+ }
387
+ catch (parseError) {
388
+ this.debugLog(`Temperature parse error: ${parseError}`);
389
+ }
390
+ return undefined;
391
+ }
392
+ /**
393
+ * Shared helper: Parse setpoint from ERD
394
+ */
395
+ async parseSetpoint(compartment) {
396
+ const r = await this.readErd(ERD_TYPES.TEMPERATURE_SETTING);
397
+ if (!r || r === 'undefined') {
398
+ return undefined;
399
+ }
400
+ try {
401
+ const parsed = JSON.parse(r);
402
+ if (parsed[compartment] !== undefined) {
403
+ const tempCelsius = Number(parsed[compartment]);
404
+ this.debugLog(`${compartment} setpoint: ${tempCelsius}°C`);
405
+ return tempCelsius;
406
+ }
407
+ }
408
+ catch (parseError) {
409
+ this.debugLog(`Setpoint parse error: ${parseError}`);
410
+ }
411
+ return undefined;
412
+ }
413
+ /**
414
+ * Shared helper: Write setpoint to ERD
415
+ */
416
+ async writeSetpoint(compartment, temperature) {
417
+ try {
418
+ const value = Math.round(temperature).toString();
419
+ const erdData = JSON.stringify({ [compartment]: value });
420
+ await this.writeErd(ERD_TYPES.TEMPERATURE_SETTING, erdData);
421
+ await this.successLog(`Set ${compartment} temperature to ${value}°C`);
422
+ }
423
+ catch (error) {
424
+ await this.errorLog(`Failed to write ${compartment} setpoint: ${error?.message ?? error}`);
425
+ }
426
+ }
427
+ /**
428
+ * Handle Matter setpoint change
429
+ */
430
+ async handleMatterSetpointChange(request) {
431
+ try {
432
+ const amount = request.amount;
433
+ const currentSetpoint = await this.parseSetpoint('fridge') ?? 4;
434
+ const newSetpoint = currentSetpoint + (amount / 10);
435
+ await this.infoLog(`Matter setpoint adjust: ${amount * 0.1}°C (${currentSetpoint}°C -> ${newSetpoint}°C)`);
436
+ await this.writeSetpoint('fridge', newSetpoint);
437
+ }
438
+ catch (error) {
439
+ await this.errorLog(`Failed to adjust Matter setpoint: ${error?.message ?? error}`);
440
+ }
441
+ }
442
+ /**
443
+ * Handle Matter mode change (Normal, Rapid Cool, Rapid Freeze)
444
+ */
445
+ async handleMatterModeChange(request) {
446
+ try {
447
+ const newMode = request.newMode;
448
+ await this.infoLog(`Matter mode change to: ${newMode === 0 ? 'Normal' : newMode === 1 ? 'Rapid Cool' : 'Rapid Freeze'}`);
449
+ // Map Matter modes to SmartHQ ERD codes
450
+ if (newMode === 1) {
451
+ // Rapid Cool - enable Turbo Cool
452
+ await this.writeErd(ERD_TYPES.TURBO_COOL_STATUS, true);
453
+ await this.writeErd(ERD_TYPES.TURBO_FREEZE_STATUS, false);
454
+ }
455
+ else if (newMode === 2) {
456
+ // Rapid Freeze - enable Turbo Freeze
457
+ await this.writeErd(ERD_TYPES.TURBO_COOL_STATUS, false);
458
+ await this.writeErd(ERD_TYPES.TURBO_FREEZE_STATUS, true);
459
+ }
460
+ else {
461
+ // Normal - disable both
462
+ await this.writeErd(ERD_TYPES.TURBO_COOL_STATUS, false);
463
+ await this.writeErd(ERD_TYPES.TURBO_FREEZE_STATUS, false);
464
+ }
465
+ // Update the mode cluster
466
+ if (this.matterUuid) {
467
+ const matterAPI = this.api.matter;
468
+ await matterAPI.updateAccessoryState(this.matterUuid, 'refrigeratorAndTemperatureControlledCabinetMode', { mode: newMode });
469
+ }
470
+ }
471
+ catch (error) {
472
+ await this.errorLog(`Failed to change Matter mode: ${error?.message ?? error}`);
473
+ }
474
+ }
475
+ /**
476
+ * Refresh device status - update both HAP and Matter states
477
+ */
478
+ async refreshDeviceStatus() {
479
+ try {
480
+ this.SensorUpdateInProgress = true;
481
+ // Get temperatures
482
+ const fridgeTemp = await this.parseTemperature('fridge');
483
+ const freezerTemp = await this.parseTemperature('freezer');
484
+ // Get setpoints
485
+ const fridgeSetpoint = await this.parseSetpoint('fridge');
486
+ const freezerSetpoint = await this.parseSetpoint('freezer');
487
+ // Get door status for alarm cluster
488
+ const doorStatus = await this.readErd(ERD_TYPES.DOOR_STATUS);
489
+ // Get ice maker status
490
+ const iceMakerStatus = await this.readErd(ERD_TYPES.ICE_MAKER_CONTROL);
491
+ // Get turbo cool/freeze status for mode cluster
492
+ const turboCoolStatus = await this.readErd(ERD_TYPES.TURBO_COOL_STATUS);
493
+ const turboFreezeStatus = await this.readErd(ERD_TYPES.TURBO_FREEZE_STATUS);
494
+ // Get filter status for resource monitoring
495
+ const filterStatus = await this.readErd(ERD_TYPES.AIR_FILTER_STATUS);
496
+ // Update Matter state if using Matter
497
+ if (this.useMatterOverride && this.matterUuid) {
498
+ const matterAPI = this.api.matter;
499
+ // Update fridge values
500
+ if (fridgeTemp !== undefined) {
501
+ await matterAPI.updateAccessoryState(this.matterUuid, matterAPI.clusterNames.Thermostat, { localTemperature: Math.round(fridgeTemp * 100) });
502
+ await matterAPI.updateAccessoryState(this.matterUuid, 'temperatureMeasurement', { measuredValue: Math.round(fridgeTemp * 100) });
503
+ }
504
+ // Update fridge setpoint
505
+ if (fridgeSetpoint !== undefined) {
506
+ await matterAPI.updateAccessoryState(this.matterUuid, matterAPI.clusterNames.Thermostat, { occupiedCoolingSetpoint: Math.round(fridgeSetpoint * 100) });
507
+ }
508
+ // Update freezer values
509
+ if (freezerTemp !== undefined) {
510
+ await matterAPI.updateAccessoryState(this.matterUuid, matterAPI.clusterNames.Thermostat, { localTemperature: Math.round(freezerTemp * 100) });
511
+ }
512
+ // Update freezer setpoint
513
+ if (freezerSetpoint !== undefined) {
514
+ await matterAPI.updateAccessoryState(this.matterUuid, matterAPI.clusterNames.Thermostat, { occupiedCoolingSetpoint: Math.round(freezerSetpoint * 100) });
515
+ }
516
+ // Update refrigerator mode cluster based on turbo status
517
+ let currentMode = 0; // Normal
518
+ if (turboCoolStatus && Number.parseInt(turboCoolStatus) !== 0) {
519
+ currentMode = 1; // Rapid Cool
520
+ }
521
+ else if (turboFreezeStatus && Number.parseInt(turboFreezeStatus) !== 0) {
522
+ currentMode = 2; // Rapid Freeze
523
+ }
524
+ await matterAPI.updateAccessoryState(this.matterUuid, 'refrigeratorAndTemperatureControlledCabinetMode', { mode: currentMode });
525
+ // Update refrigerator alarm cluster
526
+ let alarmState = 0;
527
+ if (doorStatus) {
528
+ // Check if any door is open (non-zero value)
529
+ const byte0 = doorStatus.substring(0, 2);
530
+ const byte1 = doorStatus.substring(2, 4);
531
+ const byte2 = doorStatus.substring(4, 6);
532
+ if (byte0 !== '00' || byte1 !== '00' || byte2 !== '00') {
533
+ alarmState |= 1; // Set door open alarm bit
534
+ }
535
+ }
536
+ await matterAPI.updateAccessoryState(this.matterUuid, 'refrigeratorAlarm', { state: alarmState });
537
+ // Update fridge compartment endpoint
538
+ const fridgeEndpointUuid = matterAPI.uuid.generate(`${this.device.applianceId}-fridge`);
539
+ if (fridgeTemp !== undefined) {
540
+ await matterAPI.updateAccessoryState(fridgeEndpointUuid, 'temperatureMeasurement', { measuredValue: Math.round(fridgeTemp * 100) });
541
+ }
542
+ // Update fridge ice maker boolean state
543
+ if (iceMakerStatus) {
544
+ await matterAPI.updateAccessoryState(fridgeEndpointUuid, 'booleanState', { stateValue: Number.parseInt(iceMakerStatus) !== 0 });
545
+ }
546
+ // Update fridge filter resource monitoring
547
+ if (filterStatus) {
548
+ const needsReplacement = Number.parseInt(filterStatus) === 1;
549
+ await matterAPI.updateAccessoryState(fridgeEndpointUuid, 'resourceMonitoring', {
550
+ condition: needsReplacement ? 0 : 100,
551
+ changeIndication: needsReplacement ? 2 : 0, // 2=Critical, 0=OK
552
+ });
553
+ }
554
+ // Update freezer compartment endpoint
555
+ const freezerEndpointUuid = matterAPI.uuid.generate(`${this.device.applianceId}-freezer`);
556
+ if (freezerTemp !== undefined) {
557
+ await matterAPI.updateAccessoryState(freezerEndpointUuid, 'temperatureMeasurement', { measuredValue: Math.round(freezerTemp * 100) });
558
+ }
559
+ // Update freezer turbo freeze boolean state
560
+ if (turboFreezeStatus) {
561
+ await matterAPI.updateAccessoryState(freezerEndpointUuid, 'booleanState', { stateValue: Number.parseInt(turboFreezeStatus) !== 0 });
562
+ }
563
+ }
564
+ this.SensorUpdateInProgress = false;
565
+ }
566
+ catch (error) {
567
+ this.SensorUpdateInProgress = false;
568
+ await this.errorLog(`Failed to refresh device status: ${error?.message ?? error}`);
569
+ }
350
570
  }
351
571
  }
352
572
  //# sourceMappingURL=refrigerator.js.map