@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.
- package/MATTER.md +304 -0
- package/dist/devices/advantium.js.map +1 -1
- package/dist/devices/airConditioner.js.map +1 -1
- package/dist/devices/beverageCenter.js.map +1 -1
- package/dist/devices/clothesDryer.js.map +1 -1
- package/dist/devices/clothesWasher.js.map +1 -1
- package/dist/devices/coffeeMaker.js.map +1 -1
- package/dist/devices/device.d.ts +83 -21
- package/dist/devices/device.d.ts.map +1 -1
- package/dist/devices/device.js +216 -70
- package/dist/devices/device.js.map +1 -1
- package/dist/devices/dishwasher.js.map +1 -1
- package/dist/devices/hood.js.map +1 -1
- package/dist/devices/microwave.js.map +1 -1
- package/dist/devices/oven.js.map +1 -1
- package/dist/devices/refrigerator.d.ts +37 -0
- package/dist/devices/refrigerator.d.ts.map +1 -1
- package/dist/devices/refrigerator.js +392 -172
- package/dist/devices/refrigerator.js.map +1 -1
- package/dist/devices/waterFilter.js.map +1 -1
- package/dist/devices/waterHeater.js.map +1 -1
- package/dist/devices/waterSoftener.js.map +1 -1
- package/dist/platform.d.ts +15 -1
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +82 -32
- package/dist/platform.js.map +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/SmartHQPlatform.html +15 -9
- package/docs/interfaces/DeviceOptions.html +2 -2
- package/docs/interfaces/SmartHQPlatformConfig.html +2 -2
- package/docs/interfaces/SmartHqContext.html +2 -2
- package/docs/interfaces/SmartHqERDResponse.html +2 -2
- package/docs/interfaces/credentials.html +2 -2
- package/docs/interfaces/devicesConfig.html +3 -2
- package/docs/interfaces/options.html +2 -2
- package/docs/variables/API_URL.html +1 -1
- package/docs/variables/ERD_CODES.html +1 -1
- package/docs/variables/ERD_TYPES.html +1 -1
- package/docs/variables/KEEPALIVE_TIMEOUT.html +1 -1
- package/docs/variables/LOGIN_URL.html +1 -1
- package/docs/variables/OAUTH2_CLIENT_ID.html +1 -1
- package/docs/variables/OAUTH2_CLIENT_SECRET.html +1 -1
- package/docs/variables/OAUTH2_REDIRECT_URI.html +1 -1
- package/docs/variables/PLATFORM_NAME.html +1 -1
- package/docs/variables/PLUGIN_NAME.html +1 -1
- package/docs/variables/SECURE_URL.html +1 -1
- package/docs/variables/default.html +1 -1
- 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
|
-
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
//
|
|
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;
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
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 () =>
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
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 () =>
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
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 () =>
|
|
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
|
|
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;
|
|
238
|
+
return false;
|
|
101
239
|
}
|
|
102
240
|
try {
|
|
103
|
-
const
|
|
104
|
-
const status = Number.parseInt(
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
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
|
|
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
|
|
150
|
-
|
|
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 })
|
|
286
|
+
.setProps({ minValue: 0, maxValue: 7.2, minStep: 0.5 })
|
|
171
287
|
.onGet(async () => {
|
|
172
|
-
const
|
|
173
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
227
|
-
|
|
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 })
|
|
311
|
+
.setProps({ minValue: -21, maxValue: -3.3, minStep: 0.5 })
|
|
248
312
|
.onGet(async () => {
|
|
249
|
-
const
|
|
250
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|