@homebridge-plugins/homebridge-smarthq 0.5.0-beta.3 → 0.5.0-beta.30

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