zigbee-herdsman-converters 25.71.0 → 25.73.0
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/CHANGELOG.md +37 -0
- package/dist/devices/ikea.d.ts.map +1 -1
- package/dist/devices/ikea.js +21 -3
- package/dist/devices/ikea.js.map +1 -1
- package/dist/devices/lumi.d.ts.map +1 -1
- package/dist/devices/lumi.js +78 -10
- package/dist/devices/lumi.js.map +1 -1
- package/dist/devices/multir.d.ts.map +1 -1
- package/dist/devices/multir.js +15 -6
- package/dist/devices/multir.js.map +1 -1
- package/dist/devices/namron.d.ts.map +1 -1
- package/dist/devices/namron.js +100 -0
- package/dist/devices/namron.js.map +1 -1
- package/dist/devices/ozsmartthings.js +1 -1
- package/dist/devices/ozsmartthings.js.map +1 -1
- package/dist/devices/philips.d.ts.map +1 -1
- package/dist/devices/philips.js +186 -0
- package/dist/devices/philips.js.map +1 -1
- package/dist/devices/shinasystem.d.ts.map +1 -1
- package/dist/devices/shinasystem.js +99 -0
- package/dist/devices/shinasystem.js.map +1 -1
- package/dist/devices/slacky_diy.d.ts.map +1 -1
- package/dist/devices/slacky_diy.js +9 -1
- package/dist/devices/slacky_diy.js.map +1 -1
- package/dist/devices/smarli.d.ts.map +1 -1
- package/dist/devices/smarli.js +51 -0
- package/dist/devices/smarli.js.map +1 -1
- package/dist/devices/third_reality.d.ts.map +1 -1
- package/dist/devices/third_reality.js +27 -24
- package/dist/devices/third_reality.js.map +1 -1
- package/dist/devices/tuya.d.ts.map +1 -1
- package/dist/devices/tuya.js +129 -99
- package/dist/devices/tuya.js.map +1 -1
- package/dist/devices/woolley.d.ts.map +1 -1
- package/dist/devices/woolley.js +12 -0
- package/dist/devices/woolley.js.map +1 -1
- package/dist/lib/ikea.d.ts +5 -0
- package/dist/lib/ikea.d.ts.map +1 -1
- package/dist/lib/ikea.js +70 -1
- package/dist/lib/ikea.js.map +1 -1
- package/dist/lib/lumi.d.ts +42 -12
- package/dist/lib/lumi.d.ts.map +1 -1
- package/dist/lib/lumi.js +401 -116
- package/dist/lib/lumi.js.map +1 -1
- package/dist/models-index.json +1 -1
- package/package.json +1 -1
package/dist/lib/lumi.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.toZigbee = exports.fromZigbee = exports.modernExtend = exports.lumiModernExtend = exports.manufacturerCode = exports.trv = exports.presence = exports.numericAttributes2Payload = exports.buffer2DataObject = void 0;
|
|
37
|
+
exports.w100EnsureDefaults = w100EnsureDefaults;
|
|
37
38
|
const node_buffer_1 = require("node:buffer");
|
|
38
39
|
const fz = __importStar(require("../converters/fromZigbee"));
|
|
39
40
|
const exposes = __importStar(require("./exposes"));
|
|
@@ -425,6 +426,9 @@ msg, meta, model, options, dataObject) => {
|
|
|
425
426
|
if (["QBKG25LM", "QBKG33LM", "QBKG34LM"].includes(model.model)) {
|
|
426
427
|
payload.state_right = value === 1 ? "ON" : "OFF";
|
|
427
428
|
}
|
|
429
|
+
else if (["TH-S04D"].includes(model.model)) {
|
|
430
|
+
payload.battery = value;
|
|
431
|
+
}
|
|
428
432
|
else if (["WSDCGQ01LM", "WSDCGQ11LM"].includes(model.model)) {
|
|
429
433
|
(0, utils_1.assertNumber)(value);
|
|
430
434
|
payload.pressure = value / 100.0;
|
|
@@ -2744,6 +2748,79 @@ const feederDaysLookup = {
|
|
|
2744
2748
|
85: "mon-wed-fri-sun",
|
|
2745
2749
|
42: "tue-thu-sat",
|
|
2746
2750
|
};
|
|
2751
|
+
const w100Defaults = {
|
|
2752
|
+
system_mode: "off",
|
|
2753
|
+
occupied_heating_setpoint: 15,
|
|
2754
|
+
fan_mode: "auto",
|
|
2755
|
+
unused: "0",
|
|
2756
|
+
thermostat_mode: "OFF",
|
|
2757
|
+
min_target_temp: 5,
|
|
2758
|
+
max_target_temp: 30,
|
|
2759
|
+
};
|
|
2760
|
+
function w100EnsureDefaults(meta) {
|
|
2761
|
+
const state = meta.state || {};
|
|
2762
|
+
const normalized = {
|
|
2763
|
+
system_mode: state.system_mode ?? w100Defaults.system_mode,
|
|
2764
|
+
occupied_heating_setpoint: state.occupied_heating_setpoint ?? w100Defaults.occupied_heating_setpoint,
|
|
2765
|
+
fan_mode: state.fan_mode ?? w100Defaults.fan_mode,
|
|
2766
|
+
unused: state.unused ?? w100Defaults.unused,
|
|
2767
|
+
thermostat_mode: state.thermostat_mode ?? w100Defaults.thermostat_mode,
|
|
2768
|
+
};
|
|
2769
|
+
meta.state = {
|
|
2770
|
+
...state,
|
|
2771
|
+
...normalized,
|
|
2772
|
+
};
|
|
2773
|
+
if (meta.device) {
|
|
2774
|
+
if (!meta.device.meta)
|
|
2775
|
+
meta.device.meta = {};
|
|
2776
|
+
if (!meta.device.meta.initialized) {
|
|
2777
|
+
meta.device.meta.initialized = true;
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
return normalized;
|
|
2781
|
+
}
|
|
2782
|
+
async function w100SendPMTSD(entity, pmtsd, meta) {
|
|
2783
|
+
const { p, m, t, s, d } = pmtsd;
|
|
2784
|
+
const pmtsdStr = `P${p}_M${m}_T${t}_S${s}_D${d}`;
|
|
2785
|
+
const pmtsdBytes = Array.from(pmtsdStr).map((c) => c.charCodeAt(0));
|
|
2786
|
+
const pmtsdLen = pmtsdBytes.length;
|
|
2787
|
+
const fixedHeader = [
|
|
2788
|
+
0xaa,
|
|
2789
|
+
0x71,
|
|
2790
|
+
0x1f,
|
|
2791
|
+
0x44,
|
|
2792
|
+
0x00,
|
|
2793
|
+
0x00,
|
|
2794
|
+
0x05,
|
|
2795
|
+
0x41,
|
|
2796
|
+
0x1c,
|
|
2797
|
+
0x00,
|
|
2798
|
+
0x00,
|
|
2799
|
+
0x54,
|
|
2800
|
+
0xef,
|
|
2801
|
+
0x44,
|
|
2802
|
+
0x80,
|
|
2803
|
+
0x71,
|
|
2804
|
+
0x1a,
|
|
2805
|
+
0x08,
|
|
2806
|
+
0x00,
|
|
2807
|
+
0x08,
|
|
2808
|
+
0x44,
|
|
2809
|
+
pmtsdLen,
|
|
2810
|
+
];
|
|
2811
|
+
const counter = Math.floor(Math.random() * 256);
|
|
2812
|
+
fixedHeader[4] = counter;
|
|
2813
|
+
const fullPayload = [...fixedHeader, ...pmtsdBytes];
|
|
2814
|
+
const checksum = fullPayload.reduce((sum, b) => sum + b, 0) & 0xff;
|
|
2815
|
+
fixedHeader[5] = checksum;
|
|
2816
|
+
await entity.write(64704, { 65522: { value: node_buffer_1.Buffer.from(fullPayload), type: 65 } }, { manufacturerCode: 4447, disableDefaultResponse: true });
|
|
2817
|
+
logger_1.logger.info(`Aqara W100: PMTSD frame sent: ${pmtsdStr}`, NS);
|
|
2818
|
+
if (meta.device) {
|
|
2819
|
+
if (!meta.device.meta)
|
|
2820
|
+
meta.device.meta = {};
|
|
2821
|
+
meta.device.meta.lastPMTSDSend = Date.now();
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2747
2824
|
exports.fromZigbee = {
|
|
2748
2825
|
// lumi generic
|
|
2749
2826
|
lumi_basic: {
|
|
@@ -4039,82 +4116,179 @@ exports.fromZigbee = {
|
|
|
4039
4116
|
return result;
|
|
4040
4117
|
},
|
|
4041
4118
|
},
|
|
4042
|
-
|
|
4119
|
+
w100_specific: {
|
|
4043
4120
|
cluster: "manuSpecificLumi",
|
|
4044
4121
|
type: ["attributeReport", "readResponse"],
|
|
4045
|
-
convert: (model, msg, publish, options, meta) => {
|
|
4046
|
-
const
|
|
4047
|
-
if (
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4122
|
+
convert: async (model, msg, publish, options, meta) => {
|
|
4123
|
+
const result = {};
|
|
4124
|
+
if (msg.data[65522]) {
|
|
4125
|
+
const base = w100EnsureDefaults(meta);
|
|
4126
|
+
Object.assign(result, base);
|
|
4127
|
+
const attr = msg.data[65522];
|
|
4128
|
+
if (!attr || !node_buffer_1.Buffer.isBuffer(attr)) {
|
|
4129
|
+
// defaults assigned
|
|
4130
|
+
}
|
|
4131
|
+
else {
|
|
4132
|
+
const endsWith = node_buffer_1.Buffer.from([0x08, 0x00, 0x08, 0x44]);
|
|
4133
|
+
const endsWithData = node_buffer_1.Buffer.from([0x08, 0x44]);
|
|
4134
|
+
if (attr.length >= 4 && attr.slice(-4).equals(endsWith)) {
|
|
4135
|
+
logger_1.logger.info(`Aqara W100: PMTSD request detected from device ${meta.device.ieeeAddr}`, NS);
|
|
4136
|
+
let modeValue = 0;
|
|
4137
|
+
if (base.system_mode === "off") {
|
|
4138
|
+
modeValue = meta.device.meta.lastActiveMode ?? 0;
|
|
4139
|
+
}
|
|
4140
|
+
else {
|
|
4141
|
+
const modeMap = { cool: 0, heat: 1, auto: 2 };
|
|
4142
|
+
modeValue = modeMap[base.system_mode] ?? 0;
|
|
4143
|
+
}
|
|
4144
|
+
const convertToNumber = (key, value) => {
|
|
4145
|
+
if (typeof value !== "string")
|
|
4146
|
+
return value;
|
|
4147
|
+
if (key === "fan_mode") {
|
|
4148
|
+
const speedMap = { auto: 0, low: 1, medium: 2, high: 3 };
|
|
4149
|
+
return speedMap[value.toLowerCase()] ?? 0;
|
|
4150
|
+
}
|
|
4151
|
+
if (key === "unused")
|
|
4152
|
+
return Number.parseInt(value, 10);
|
|
4153
|
+
return value;
|
|
4154
|
+
};
|
|
4155
|
+
const pmtsdValues = {
|
|
4156
|
+
p: base.system_mode === "off" ? 1 : 0,
|
|
4157
|
+
m: modeValue,
|
|
4158
|
+
t: typeof base.occupied_heating_setpoint === "string"
|
|
4159
|
+
? Number.parseFloat(base.occupied_heating_setpoint)
|
|
4160
|
+
: base.occupied_heating_setpoint,
|
|
4161
|
+
s: convertToNumber("fan_mode", base.fan_mode) ?? 0,
|
|
4162
|
+
d: convertToNumber("unused", base.unused) ?? 0,
|
|
4163
|
+
};
|
|
4164
|
+
try {
|
|
4165
|
+
const endpoint = meta.device.getEndpoint(1);
|
|
4166
|
+
await w100SendPMTSD(endpoint, pmtsdValues, meta);
|
|
4167
|
+
}
|
|
4168
|
+
catch (error) {
|
|
4169
|
+
logger_1.logger.error(`Aqara W100: Failed to send PMTSD frame: ${error.message}`, NS);
|
|
4170
|
+
}
|
|
4171
|
+
result.action = "W100_PMTSD_request";
|
|
4172
|
+
}
|
|
4173
|
+
else {
|
|
4174
|
+
const idx = attr.indexOf(endsWithData);
|
|
4175
|
+
if (idx !== -1 && idx + 2 < attr.length) {
|
|
4176
|
+
const payloadLen = attr[idx + 2];
|
|
4177
|
+
const payloadStart = idx + 3;
|
|
4178
|
+
const payloadEnd = payloadStart + payloadLen;
|
|
4179
|
+
if (payloadEnd <= attr.length) {
|
|
4180
|
+
const payloadBytes = attr.slice(payloadStart, payloadEnd);
|
|
4181
|
+
let payloadAscii;
|
|
4182
|
+
try {
|
|
4183
|
+
payloadAscii = payloadBytes.toString("ascii");
|
|
4184
|
+
}
|
|
4185
|
+
catch {
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
const stateUpdate = {};
|
|
4189
|
+
const partsForCombined = [];
|
|
4190
|
+
const pairs = payloadAscii.split("_");
|
|
4191
|
+
const pmtsd = {
|
|
4192
|
+
p: base.system_mode === "off" ? 1 : 0,
|
|
4193
|
+
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
|
|
4194
|
+
m: meta.device.meta?.lastActiveMode ?? 0,
|
|
4195
|
+
t: 15,
|
|
4196
|
+
s: 0,
|
|
4197
|
+
d: 0,
|
|
4198
|
+
};
|
|
4199
|
+
if (base.system_mode !== "off") {
|
|
4200
|
+
const modeMap = { cool: 0, heat: 1, auto: 2 };
|
|
4201
|
+
pmtsd.m = modeMap[base.system_mode] ?? 0;
|
|
4202
|
+
}
|
|
4203
|
+
pairs.forEach((pair) => {
|
|
4204
|
+
if (pair.length >= 2) {
|
|
4205
|
+
const key = pair[0].toLowerCase();
|
|
4206
|
+
const value = pair.slice(1);
|
|
4207
|
+
let newKey;
|
|
4208
|
+
let stateKey;
|
|
4209
|
+
let displayValue = value;
|
|
4210
|
+
let processedValue = value;
|
|
4211
|
+
switch (key) {
|
|
4212
|
+
case "p":
|
|
4213
|
+
newKey = "PW";
|
|
4214
|
+
processedValue = Number.parseInt(value, 10);
|
|
4215
|
+
pmtsd.p = processedValue;
|
|
4216
|
+
break;
|
|
4217
|
+
case "m":
|
|
4218
|
+
newKey = "MW";
|
|
4219
|
+
processedValue = Number.parseInt(value, 10);
|
|
4220
|
+
pmtsd.m = processedValue;
|
|
4221
|
+
break;
|
|
4222
|
+
case "t":
|
|
4223
|
+
newKey = "TW";
|
|
4224
|
+
stateKey = "occupied_heating_setpoint";
|
|
4225
|
+
processedValue = Number.parseFloat(value);
|
|
4226
|
+
pmtsd.t = processedValue;
|
|
4227
|
+
displayValue = processedValue;
|
|
4228
|
+
break;
|
|
4229
|
+
case "s":
|
|
4230
|
+
newKey = "SW";
|
|
4231
|
+
stateKey = "fan_mode";
|
|
4232
|
+
processedValue = Number.parseInt(value, 10);
|
|
4233
|
+
pmtsd.s = processedValue;
|
|
4234
|
+
displayValue = ["auto", "low", "medium", "high"][processedValue];
|
|
4235
|
+
break;
|
|
4236
|
+
case "d":
|
|
4237
|
+
newKey = "DW";
|
|
4238
|
+
stateKey = "unused";
|
|
4239
|
+
processedValue = Number.parseInt(value, 10);
|
|
4240
|
+
pmtsd.d = processedValue;
|
|
4241
|
+
displayValue = String(processedValue);
|
|
4242
|
+
break;
|
|
4243
|
+
default:
|
|
4244
|
+
newKey = `${key.toUpperCase()}W`;
|
|
4245
|
+
}
|
|
4246
|
+
result[newKey] = value;
|
|
4247
|
+
if (stateKey) {
|
|
4248
|
+
stateUpdate[stateKey] = displayValue;
|
|
4249
|
+
result[stateKey] = displayValue;
|
|
4250
|
+
}
|
|
4251
|
+
partsForCombined.push(`${newKey}${value}`);
|
|
4252
|
+
}
|
|
4253
|
+
});
|
|
4254
|
+
const modeDisplay = ["cool", "heat", "auto"][pmtsd.m] || "cool";
|
|
4255
|
+
const systemMode = pmtsd.p === 1 ? "off" : modeDisplay;
|
|
4256
|
+
if (pmtsd.p === 0 && pmtsd.m !== undefined) {
|
|
4257
|
+
if (!meta.device.meta)
|
|
4258
|
+
meta.device.meta = {};
|
|
4259
|
+
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
|
|
4260
|
+
meta.device.meta.lastActiveMode = pmtsd.m;
|
|
4261
|
+
}
|
|
4262
|
+
stateUpdate.system_mode = systemMode;
|
|
4263
|
+
result.system_mode = systemMode;
|
|
4264
|
+
const date = new Date();
|
|
4265
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
4266
|
+
const formattedDate = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
4267
|
+
const combinedString = partsForCombined.length
|
|
4268
|
+
? `${formattedDate}_${partsForCombined.join("_")}`
|
|
4269
|
+
: `${formattedDate}`;
|
|
4270
|
+
result.PMTSD_from_W100_Data = combinedString;
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4053
4275
|
}
|
|
4276
|
+
return result;
|
|
4054
4277
|
},
|
|
4055
4278
|
},
|
|
4056
|
-
|
|
4057
|
-
cluster: "
|
|
4279
|
+
w100_temperature: {
|
|
4280
|
+
cluster: "msTemperatureMeasurement",
|
|
4058
4281
|
type: ["attributeReport", "readResponse"],
|
|
4059
4282
|
convert: (model, msg, publish, options, meta) => {
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
const
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
const payloadStart = idx + 3;
|
|
4069
|
-
const payloadEnd = payloadStart + payloadLen;
|
|
4070
|
-
if (payloadEnd > data.length)
|
|
4071
|
-
return;
|
|
4072
|
-
const payloadBytes = data.slice(payloadStart, payloadEnd);
|
|
4073
|
-
let payloadAscii;
|
|
4074
|
-
try {
|
|
4075
|
-
payloadAscii = payloadBytes.toString("ascii");
|
|
4283
|
+
// W100 needs a custom converter to:
|
|
4284
|
+
// 1. Expose both 'temperature' and 'local_temperature' (needed for climate)
|
|
4285
|
+
// 2. Initialize device defaults (w100EnsureDefaults) needed for other converters
|
|
4286
|
+
const result = fz.temperature.convert(model, msg, publish, options, meta);
|
|
4287
|
+
if (result) {
|
|
4288
|
+
const temperature = Object.values(result)[0];
|
|
4289
|
+
const defaults = w100EnsureDefaults(meta);
|
|
4290
|
+
return { temperature, local_temperature: temperature, ...defaults };
|
|
4076
4291
|
}
|
|
4077
|
-
catch {
|
|
4078
|
-
return;
|
|
4079
|
-
}
|
|
4080
|
-
const result = {};
|
|
4081
|
-
const partsForCombined = [];
|
|
4082
|
-
const pairs = payloadAscii.split("_");
|
|
4083
|
-
pairs.forEach((p) => {
|
|
4084
|
-
if (p.length >= 2) {
|
|
4085
|
-
const key = p[0];
|
|
4086
|
-
const value = p.slice(1);
|
|
4087
|
-
let newKey;
|
|
4088
|
-
switch (key) {
|
|
4089
|
-
case "p":
|
|
4090
|
-
newKey = "PW";
|
|
4091
|
-
break;
|
|
4092
|
-
case "m":
|
|
4093
|
-
newKey = "MW";
|
|
4094
|
-
break;
|
|
4095
|
-
case "t":
|
|
4096
|
-
newKey = "TW";
|
|
4097
|
-
break;
|
|
4098
|
-
case "s":
|
|
4099
|
-
newKey = "SW";
|
|
4100
|
-
break;
|
|
4101
|
-
case "d":
|
|
4102
|
-
newKey = "DW";
|
|
4103
|
-
break;
|
|
4104
|
-
default:
|
|
4105
|
-
newKey = `${key.toUpperCase()}W`;
|
|
4106
|
-
}
|
|
4107
|
-
result[newKey] = value;
|
|
4108
|
-
partsForCombined.push(`${newKey}${value}`);
|
|
4109
|
-
}
|
|
4110
|
-
});
|
|
4111
|
-
const ts = Date.now();
|
|
4112
|
-
const combinedString = partsForCombined.length ? `${ts}_${partsForCombined.join("_")}` : `${ts}`;
|
|
4113
|
-
logger_1.logger.info(`Decoded PMTSD: ${JSON.stringify(result)} from ${meta.device.ieeeAddr}`, NS);
|
|
4114
|
-
return {
|
|
4115
|
-
...result,
|
|
4116
|
-
data: combinedString,
|
|
4117
|
-
};
|
|
4118
4292
|
},
|
|
4119
4293
|
},
|
|
4120
4294
|
};
|
|
@@ -5661,57 +5835,168 @@ exports.toZigbee = {
|
|
|
5661
5835
|
throw new Error(`Not supported: '${key}'`);
|
|
5662
5836
|
},
|
|
5663
5837
|
},
|
|
5664
|
-
|
|
5665
|
-
key: ["
|
|
5838
|
+
w100_pmtsd: {
|
|
5839
|
+
key: ["system_mode", "occupied_heating_setpoint", "fan_mode", "unused", "pmtsd_to_w100"],
|
|
5666
5840
|
convertSet: async (entity, key, value, meta) => {
|
|
5667
|
-
const
|
|
5668
|
-
const
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5841
|
+
const MIN_SEND_INTERVAL_MS = 5000;
|
|
5842
|
+
const base = w100EnsureDefaults(meta);
|
|
5843
|
+
let initialP = 0;
|
|
5844
|
+
let initialM = 0;
|
|
5845
|
+
if (base.system_mode === "off") {
|
|
5846
|
+
initialP = 1;
|
|
5847
|
+
initialM = meta.device?.meta?.lastActiveMode ?? 0;
|
|
5848
|
+
}
|
|
5849
|
+
else {
|
|
5850
|
+
initialP = 0;
|
|
5851
|
+
const modeMap = { cool: 0, heat: 1, auto: 2 };
|
|
5852
|
+
initialM = modeMap[base.system_mode] ?? 0;
|
|
5853
|
+
}
|
|
5854
|
+
let initialS = 0;
|
|
5855
|
+
const speedMap = { auto: 0, low: 1, medium: 2, high: 3 };
|
|
5856
|
+
initialS = speedMap[base.fan_mode] ?? 0;
|
|
5857
|
+
const pmtsd = {
|
|
5858
|
+
p: initialP,
|
|
5859
|
+
m: initialM,
|
|
5860
|
+
t: typeof base.occupied_heating_setpoint === "string"
|
|
5861
|
+
? Number.parseFloat(base.occupied_heating_setpoint)
|
|
5862
|
+
: base.occupied_heating_setpoint,
|
|
5863
|
+
s: initialS,
|
|
5864
|
+
d: typeof base.unused === "string" ? Number.parseInt(base.unused, 10) : base.unused,
|
|
5865
|
+
};
|
|
5866
|
+
if (typeof pmtsd.t === "string")
|
|
5867
|
+
pmtsd.t = Number.parseFloat(pmtsd.t);
|
|
5868
|
+
if (typeof pmtsd.d === "string")
|
|
5869
|
+
pmtsd.d = Number.parseInt(pmtsd.d, 10);
|
|
5870
|
+
let hasChanged = false;
|
|
5871
|
+
if (key === "pmtsd_to_w100") {
|
|
5872
|
+
const val = value;
|
|
5873
|
+
if (val.P !== undefined)
|
|
5874
|
+
pmtsd.p = Number(val.P);
|
|
5875
|
+
if (val.M !== undefined)
|
|
5876
|
+
pmtsd.m = Number(val.M);
|
|
5877
|
+
if (val.T !== undefined)
|
|
5878
|
+
pmtsd.t = Number(val.T);
|
|
5879
|
+
if (val.S !== undefined)
|
|
5880
|
+
pmtsd.s = Number(val.S);
|
|
5881
|
+
if (val.D !== undefined)
|
|
5882
|
+
pmtsd.d = Number(val.D);
|
|
5883
|
+
hasChanged = true;
|
|
5884
|
+
}
|
|
5885
|
+
else {
|
|
5886
|
+
if (meta.state?.thermostat_mode !== "ON" && ["system_mode", "occupied_heating_setpoint", "fan_mode"].includes(key)) {
|
|
5887
|
+
logger_1.logger.warning(`Aqara W100: Ignoring ${key} command - thermostat_mode is not ON`, NS);
|
|
5888
|
+
return { state: {} };
|
|
5889
|
+
}
|
|
5890
|
+
switch (key) {
|
|
5891
|
+
case "system_mode": {
|
|
5892
|
+
if (value === "off") {
|
|
5893
|
+
if (pmtsd.p === 0 && pmtsd.m !== undefined) {
|
|
5894
|
+
if (!meta.device.meta)
|
|
5895
|
+
meta.device.meta = {};
|
|
5896
|
+
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
|
|
5897
|
+
meta.device.meta.lastActiveMode = pmtsd.m;
|
|
5898
|
+
}
|
|
5899
|
+
if (pmtsd.p !== 1) {
|
|
5900
|
+
pmtsd.p = 1;
|
|
5901
|
+
hasChanged = true;
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
else {
|
|
5905
|
+
if (pmtsd.p !== 0) {
|
|
5906
|
+
pmtsd.p = 0;
|
|
5907
|
+
hasChanged = true;
|
|
5908
|
+
}
|
|
5909
|
+
const modeMap = { cool: 0, heat: 1, auto: 2 };
|
|
5910
|
+
const numValue = typeof value === "string" ? modeMap[value.toLowerCase()] : Number(value);
|
|
5911
|
+
if (numValue !== undefined && [0, 1, 2].includes(numValue)) {
|
|
5912
|
+
if (pmtsd.m !== numValue) {
|
|
5913
|
+
pmtsd.m = numValue;
|
|
5914
|
+
hasChanged = true;
|
|
5915
|
+
if (!meta.device.meta)
|
|
5916
|
+
meta.device.meta = {};
|
|
5917
|
+
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
|
|
5918
|
+
meta.device.meta.lastActiveMode = numValue;
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5922
|
+
break;
|
|
5923
|
+
}
|
|
5924
|
+
case "occupied_heating_setpoint": {
|
|
5925
|
+
const temp = Number.parseFloat(value);
|
|
5926
|
+
const minTarget = meta.options?.min_target_temp !== undefined ? Number(meta.options.min_target_temp) : w100Defaults.min_target_temp;
|
|
5927
|
+
const maxTarget = meta.options?.max_target_temp !== undefined ? Number(meta.options.max_target_temp) : w100Defaults.max_target_temp;
|
|
5928
|
+
if (Number.isNaN(temp) || temp < minTarget || temp > maxTarget)
|
|
5929
|
+
throw new Error(`occupied_heating_setpoint must be between ${minTarget} and ${maxTarget}`);
|
|
5930
|
+
if (pmtsd.t !== temp) {
|
|
5931
|
+
pmtsd.t = temp;
|
|
5932
|
+
hasChanged = true;
|
|
5933
|
+
}
|
|
5934
|
+
break;
|
|
5935
|
+
}
|
|
5936
|
+
case "fan_mode": {
|
|
5937
|
+
let numValue;
|
|
5938
|
+
if (typeof value === "string") {
|
|
5939
|
+
const speedMap = { auto: 0, low: 1, medium: 2, high: 3 };
|
|
5940
|
+
numValue = speedMap[value.toLowerCase()];
|
|
5941
|
+
}
|
|
5942
|
+
else {
|
|
5943
|
+
numValue = Number(value);
|
|
5944
|
+
}
|
|
5945
|
+
if (pmtsd.s !== numValue) {
|
|
5946
|
+
pmtsd.s = numValue;
|
|
5947
|
+
hasChanged = true;
|
|
5948
|
+
}
|
|
5949
|
+
break;
|
|
5950
|
+
}
|
|
5951
|
+
case "unused": {
|
|
5952
|
+
const numValue = typeof value === "string" ? Number.parseInt(value, 10) : Number(value);
|
|
5953
|
+
if (pmtsd.d !== numValue) {
|
|
5954
|
+
pmtsd.d = numValue;
|
|
5955
|
+
hasChanged = true;
|
|
5956
|
+
}
|
|
5957
|
+
break;
|
|
5958
|
+
}
|
|
5959
|
+
}
|
|
5960
|
+
}
|
|
5961
|
+
const modeDisplay = ["cool", "heat", "auto"][pmtsd.m] || "cool";
|
|
5962
|
+
const speedDisplay = ["auto", "low", "medium", "high"][pmtsd.s] || "auto";
|
|
5963
|
+
const stateUpdate = {
|
|
5964
|
+
state: {
|
|
5965
|
+
occupied_heating_setpoint: pmtsd.t,
|
|
5966
|
+
fan_mode: speedDisplay,
|
|
5967
|
+
system_mode: pmtsd.p === 1 ? "off" : modeDisplay,
|
|
5968
|
+
unused: String(pmtsd.d),
|
|
5969
|
+
},
|
|
5970
|
+
};
|
|
5971
|
+
const { p, m, t, s, d } = pmtsd;
|
|
5972
|
+
if (p === undefined || m === undefined || t === undefined || s === undefined || d === undefined)
|
|
5973
|
+
return stateUpdate;
|
|
5974
|
+
const now = Date.now();
|
|
5975
|
+
if (meta.device) {
|
|
5976
|
+
if (!meta.device.meta)
|
|
5977
|
+
meta.device.meta = {};
|
|
5978
|
+
}
|
|
5979
|
+
const lastSendTime = meta.device?.meta?.lastPMTSDSend || 0;
|
|
5980
|
+
const timeElapsed = now - lastSendTime;
|
|
5981
|
+
const shouldSend = hasChanged || timeElapsed >= MIN_SEND_INTERVAL_MS;
|
|
5982
|
+
if (!shouldSend)
|
|
5983
|
+
return stateUpdate;
|
|
5984
|
+
await w100SendPMTSD(entity, pmtsd, meta);
|
|
5985
|
+
return stateUpdate;
|
|
5986
|
+
},
|
|
5987
|
+
convertGet: async (entity, key, meta) => {
|
|
5988
|
+
await entity.read("manuSpecificLumi", [65522], { manufacturerCode: 4447 });
|
|
5989
|
+
},
|
|
5990
|
+
},
|
|
5991
|
+
w100_mode: {
|
|
5992
|
+
key: ["thermostat_mode"],
|
|
5707
5993
|
convertSet: async (entity, key, value, meta) => {
|
|
5708
5994
|
const deviceMac = meta.device.ieeeAddr.replace(/^0x/, "").toLowerCase();
|
|
5709
5995
|
const hubMac = "54ef4480711a";
|
|
5710
5996
|
function cleanMac(mac, expectedLen) {
|
|
5711
5997
|
const cleaned = mac.replace(/[:-]/g, "");
|
|
5712
|
-
if (cleaned.length !== expectedLen)
|
|
5998
|
+
if (cleaned.length !== expectedLen)
|
|
5713
5999
|
throw new Error(`MAC must be ${expectedLen} hex digits`);
|
|
5714
|
-
}
|
|
5715
6000
|
return cleaned;
|
|
5716
6001
|
}
|
|
5717
6002
|
const dev = node_buffer_1.Buffer.from(cleanMac(deviceMac, 16), "hex");
|
|
@@ -5735,13 +6020,13 @@ exports.toZigbee = {
|
|
|
5735
6020
|
const seq = node_buffer_1.Buffer.from([Math.floor(Math.random() * 256)]);
|
|
5736
6021
|
const control = node_buffer_1.Buffer.from([0x18]);
|
|
5737
6022
|
frame = node_buffer_1.Buffer.concat([prefix, frameId, seq, control, dev]);
|
|
5738
|
-
if (frame.length < 34)
|
|
6023
|
+
if (frame.length < 34)
|
|
5739
6024
|
frame = node_buffer_1.Buffer.concat([frame, node_buffer_1.Buffer.alloc(34 - frame.length, 0x00)]);
|
|
5740
|
-
}
|
|
5741
6025
|
}
|
|
5742
6026
|
await entity.write(64704, { 65522: { value: frame, type: 0x41 } }, { manufacturerCode: 4447, disableDefaultResponse: true });
|
|
5743
|
-
logger_1.logger.info(`
|
|
5744
|
-
|
|
6027
|
+
logger_1.logger.info(`Aqara W100: thermostat_mode set to ${value}`, NS);
|
|
6028
|
+
const defaults = w100EnsureDefaults(meta);
|
|
6029
|
+
return { state: { ...defaults, thermostat_mode: value } };
|
|
5745
6030
|
},
|
|
5746
6031
|
},
|
|
5747
6032
|
};
|