zigbee-herdsman-converters 26.70.0 → 26.71.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 +23 -0
- package/dist/devices/develco.d.ts.map +1 -1
- package/dist/devices/develco.js +375 -9
- package/dist/devices/develco.js.map +1 -1
- package/dist/devices/giex.js +3 -3
- package/dist/devices/giex.js.map +1 -1
- package/dist/devices/index.d.ts.map +1 -1
- package/dist/devices/index.js +2 -0
- package/dist/devices/index.js.map +1 -1
- package/dist/devices/ls.d.ts.map +1 -1
- package/dist/devices/ls.js +0 -1
- package/dist/devices/ls.js.map +1 -1
- package/dist/devices/lupus.d.ts.map +1 -1
- package/dist/devices/lupus.js +2 -1
- package/dist/devices/lupus.js.map +1 -1
- package/dist/devices/nodieby.d.ts +3 -0
- package/dist/devices/nodieby.d.ts.map +1 -0
- package/dist/devices/nodieby.js +142 -0
- package/dist/devices/nodieby.js.map +1 -0
- package/dist/devices/philips.d.ts.map +1 -1
- package/dist/devices/philips.js +7 -0
- package/dist/devices/philips.js.map +1 -1
- package/dist/devices/shelly.d.ts.map +1 -1
- package/dist/devices/shelly.js +90 -11
- package/dist/devices/shelly.js.map +1 -1
- package/dist/devices/sonoff.d.ts.map +1 -1
- package/dist/devices/sonoff.js +45 -28
- package/dist/devices/sonoff.js.map +1 -1
- package/dist/devices/tuya.d.ts.map +1 -1
- package/dist/devices/tuya.js +77 -14
- package/dist/devices/tuya.js.map +1 -1
- package/dist/lib/types.d.ts +9 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/models-index.json +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [26.71.0](https://github.com/Koenkk/zigbee-herdsman-converters/compare/v26.70.0...v26.71.0) (2026-06-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **add:** 929004294901 ([#12492](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12492)) ([760bd5c](https://github.com/Koenkk/zigbee-herdsman-converters/commit/760bd5c379e6e202178da56655681638bbcdd33f))
|
|
9
|
+
* **add:** ND-01 ([#12490](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12490)) ([f304966](https://github.com/Koenkk/zigbee-herdsman-converters/commit/f304966c941805662293b5cba5d77ba6e95c0784))
|
|
10
|
+
* Develco ZHEMI101: improve integration ([#12493](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12493)) ([d55a8e6](https://github.com/Koenkk/zigbee-herdsman-converters/commit/d55a8e67dbe28127fdbaecc9c3299ef519cc5b2a))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **detect:** Detect `_TZ3210_nuenzetq` as Tuya ZG-2002-RF ([#12494](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12494)) ([38a59ed](https://github.com/Koenkk/zigbee-herdsman-converters/commit/38a59ed2233439c0511d88ad2da0256a03f0f400))
|
|
16
|
+
* Excellux ZG-104PLV: fix expose presence as binary occupancy ([#12505](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12505)) ([1c9f4d3](https://github.com/Koenkk/zigbee-herdsman-converters/commit/1c9f4d316c11d1a09b31d1839a617e2e6e6a80e8))
|
|
17
|
+
* GIEX QT06_2 : fix irrigation time exposes ([#12496](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12496)) ([935a803](https://github.com/Koenkk/zigbee-herdsman-converters/commit/935a8034335731a20bcf09c53ff683c4bb9b8bb7))
|
|
18
|
+
* **ignore:** bump actions/checkout from 6 to 7 ([#12500](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12500)) ([0d8465e](https://github.com/Koenkk/zigbee-herdsman-converters/commit/0d8465ecd2e3c66d2f414c97c9651a2abcd472ba))
|
|
19
|
+
* **ignore:** bump the minor-patch group with 2 updates ([#12501](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12501)) ([9aad955](https://github.com/Koenkk/zigbee-herdsman-converters/commit/9aad9554a91bf2d08ae2134bac83a56d9b0137b3))
|
|
20
|
+
* L&S Lighting 756200643: remove unsupported battery expose https://github.com/Koenkk/zigbee2mqtt/issues/32342 ([be81afc](https://github.com/Koenkk/zigbee-herdsman-converters/commit/be81afc52f77815212641289ca3448ecd09d1865))
|
|
21
|
+
* Lupus 12050: fix electrical measurement values ([#12491](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12491)) ([de89c27](https://github.com/Koenkk/zigbee-herdsman-converters/commit/de89c27512d3b58e9006e908953d533ec970650d))
|
|
22
|
+
* Ronco RM28-LE: improve integration ([#11605](https://github.com/Koenkk/zigbee-herdsman-converters/issues/11605)) ([cbfe9d7](https://github.com/Koenkk/zigbee-herdsman-converters/commit/cbfe9d7a521889e05c43781a20f88e1aa6a79a93))
|
|
23
|
+
* Shelly S4SN-0U61X: discover presence zones ([#12506](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12506)) ([95650af](https://github.com/Koenkk/zigbee-herdsman-converters/commit/95650af81e85b0ebabad17ae45b3a2eaaebce3d3))
|
|
24
|
+
* SONOFF SWV: allow partial water valve composite settings ([#12495](https://github.com/Koenkk/zigbee-herdsman-converters/issues/12495)) ([edaaaa9](https://github.com/Koenkk/zigbee-herdsman-converters/commit/edaaaa9603a63bffd59be3dcc1843b7869a19ccf))
|
|
25
|
+
|
|
3
26
|
## [26.70.0](https://github.com/Koenkk/zigbee-herdsman-converters/compare/v26.69.0...v26.70.0) (2026-06-21)
|
|
4
27
|
|
|
5
28
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"develco.d.ts","sourceRoot":"","sources":["../../src/devices/develco.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAC,oBAAoB,EAAgC,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"develco.d.ts","sourceRoot":"","sources":["../../src/devices/develco.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAC,oBAAoB,EAAgC,MAAM,cAAc,CAAC;AA6nBtF,eAAO,MAAM,WAAW,EAAE,oBAAoB,EAg7B7C,CAAC"}
|
package/dist/devices/develco.js
CHANGED
|
@@ -61,6 +61,277 @@ const develcoLedControlMap = {
|
|
|
61
61
|
2: "motion_only",
|
|
62
62
|
255: "both",
|
|
63
63
|
};
|
|
64
|
+
const zhemi101Uint48Max = 0xffffffffffff;
|
|
65
|
+
// UI hint only. The authoritative max depends on reported multiplier/divisor and is checked in convertSet.
|
|
66
|
+
const zhemi101UnitSummationUiMax = Number.MAX_SAFE_INTEGER;
|
|
67
|
+
const zhemi101UnitOfMeasure = {
|
|
68
|
+
kWh: 0x00,
|
|
69
|
+
cubicMeters: 0x01,
|
|
70
|
+
cubicFeet: 0x02,
|
|
71
|
+
ccf: 0x03,
|
|
72
|
+
usGallons: 0x04,
|
|
73
|
+
imperialGallons: 0x05,
|
|
74
|
+
btu: 0x06,
|
|
75
|
+
liters: 0x07,
|
|
76
|
+
};
|
|
77
|
+
const zhemi101MeteringDeviceType = {
|
|
78
|
+
electricity: 0x00,
|
|
79
|
+
gas: 0x01,
|
|
80
|
+
water: 0x02,
|
|
81
|
+
};
|
|
82
|
+
const zhemi101UnitInfo = {
|
|
83
|
+
[zhemi101UnitOfMeasure.kWh]: { kind: "energy", factorToTarget: 1 },
|
|
84
|
+
[zhemi101UnitOfMeasure.cubicMeters]: { kind: "volume", factorToTarget: 1 },
|
|
85
|
+
[zhemi101UnitOfMeasure.cubicFeet]: { kind: "volume", factorToTarget: 0.028316846592 },
|
|
86
|
+
[zhemi101UnitOfMeasure.ccf]: { kind: "volume", factorToTarget: 2.8316846592 },
|
|
87
|
+
[zhemi101UnitOfMeasure.usGallons]: { kind: "volume", factorToTarget: 0.003785411784 },
|
|
88
|
+
[zhemi101UnitOfMeasure.imperialGallons]: { kind: "volume", factorToTarget: 0.00454609 },
|
|
89
|
+
[zhemi101UnitOfMeasure.btu]: { kind: "energy", factorToTarget: 0.00029307107017222 },
|
|
90
|
+
[zhemi101UnitOfMeasure.liters]: { kind: "volume", factorToTarget: 0.001 },
|
|
91
|
+
};
|
|
92
|
+
const zhemi101MeterConfigCache = new Map();
|
|
93
|
+
const zhemi101ToNumber = (value) => {
|
|
94
|
+
if (typeof value === "bigint")
|
|
95
|
+
return Number(value);
|
|
96
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
97
|
+
return value;
|
|
98
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
99
|
+
const parsed = Number(value);
|
|
100
|
+
if (Number.isFinite(parsed))
|
|
101
|
+
return parsed;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const zhemi101FirstDefined = (...values) => values.find((value) => value !== undefined && value !== null);
|
|
105
|
+
const zhemi101EndpointId = (endpoint) => endpoint.ID ?? endpoint.id ?? endpoint.endpointID ?? "default";
|
|
106
|
+
const zhemi101MeterConfigKey = (entity) => `${entity.device?.ieeeAddr ?? entity.getDevice?.()?.ieeeAddr ?? "unknown"}/${zhemi101EndpointId(entity)}`;
|
|
107
|
+
const zhemi101DecodeInterfaceMode = (value) => {
|
|
108
|
+
if (typeof value === "string")
|
|
109
|
+
return value;
|
|
110
|
+
const numericValue = zhemi101ToNumber(value);
|
|
111
|
+
return numericValue !== undefined && constants.develcoInterfaceMode[numericValue] !== undefined
|
|
112
|
+
? constants.develcoInterfaceMode[numericValue]
|
|
113
|
+
: value;
|
|
114
|
+
};
|
|
115
|
+
const zhemi101ApplyInterfaceMode = (cache, interfaceMode, force) => {
|
|
116
|
+
const modeChanged = cache.interfaceMode !== interfaceMode;
|
|
117
|
+
cache.interfaceMode = interfaceMode;
|
|
118
|
+
if (force || modeChanged) {
|
|
119
|
+
delete cache.multiplier;
|
|
120
|
+
delete cache.divisor;
|
|
121
|
+
delete cache.summationFormatting;
|
|
122
|
+
delete cache.demandFormatting;
|
|
123
|
+
delete cache.unitOfMeasure;
|
|
124
|
+
delete cache.meteringDeviceType;
|
|
125
|
+
}
|
|
126
|
+
if ((force || modeChanged || cache.unitOfMeasure === undefined) && interfaceMode === "electricity") {
|
|
127
|
+
cache.unitOfMeasure = zhemi101UnitOfMeasure.kWh;
|
|
128
|
+
}
|
|
129
|
+
else if ((force || modeChanged || cache.unitOfMeasure === undefined) && (interfaceMode === "gas" || interfaceMode === "water")) {
|
|
130
|
+
cache.unitOfMeasure = zhemi101UnitOfMeasure.cubicMeters;
|
|
131
|
+
}
|
|
132
|
+
if ((force || modeChanged || cache.meteringDeviceType === undefined) && interfaceMode === "electricity") {
|
|
133
|
+
cache.meteringDeviceType = zhemi101MeteringDeviceType.electricity;
|
|
134
|
+
}
|
|
135
|
+
else if ((force || modeChanged || cache.meteringDeviceType === undefined) && interfaceMode === "gas") {
|
|
136
|
+
cache.meteringDeviceType = zhemi101MeteringDeviceType.gas;
|
|
137
|
+
}
|
|
138
|
+
else if ((force || modeChanged || cache.meteringDeviceType === undefined) && interfaceMode === "water") {
|
|
139
|
+
cache.meteringDeviceType = zhemi101MeteringDeviceType.water;
|
|
140
|
+
}
|
|
141
|
+
return cache;
|
|
142
|
+
};
|
|
143
|
+
const zhemi101ApplyStandardMeteringAttributes = (cache, data) => {
|
|
144
|
+
const summationFormatting = zhemi101FirstDefined(data.summaFormatting, data.summationFormatting);
|
|
145
|
+
if (data.unitOfMeasure !== undefined)
|
|
146
|
+
cache.unitOfMeasure = data.unitOfMeasure;
|
|
147
|
+
if (data.multiplier !== undefined)
|
|
148
|
+
cache.multiplier = data.multiplier;
|
|
149
|
+
if (data.divisor !== undefined)
|
|
150
|
+
cache.divisor = data.divisor;
|
|
151
|
+
if (summationFormatting !== undefined)
|
|
152
|
+
cache.summationFormatting = summationFormatting;
|
|
153
|
+
if (data.demandFormatting !== undefined)
|
|
154
|
+
cache.demandFormatting = data.demandFormatting;
|
|
155
|
+
if (data.meteringDeviceType !== undefined)
|
|
156
|
+
cache.meteringDeviceType = data.meteringDeviceType;
|
|
157
|
+
return cache;
|
|
158
|
+
};
|
|
159
|
+
const zhemi101ReadMeteringConfiguration = async (entity) => {
|
|
160
|
+
const key = zhemi101MeterConfigKey(entity);
|
|
161
|
+
const cache = zhemi101MeterConfigCache.get(key) ?? {};
|
|
162
|
+
const data = await entity.read("seMetering", [
|
|
163
|
+
"unitOfMeasure",
|
|
164
|
+
"multiplier",
|
|
165
|
+
"divisor",
|
|
166
|
+
"summaFormatting",
|
|
167
|
+
"demandFormatting",
|
|
168
|
+
"meteringDeviceType",
|
|
169
|
+
]);
|
|
170
|
+
zhemi101MeterConfigCache.set(key, zhemi101ApplyStandardMeteringAttributes(cache, data));
|
|
171
|
+
};
|
|
172
|
+
const zhemi101UpdateMeterConfigFromMessage = (msg, meta) => {
|
|
173
|
+
const key = zhemi101MeterConfigKey(msg.endpoint);
|
|
174
|
+
const cache = zhemi101MeterConfigCache.get(key) ?? {};
|
|
175
|
+
const data = msg.data;
|
|
176
|
+
if (data.develcoInterfaceMode !== undefined) {
|
|
177
|
+
zhemi101ApplyInterfaceMode(cache, zhemi101DecodeInterfaceMode(data.develcoInterfaceMode), false);
|
|
178
|
+
}
|
|
179
|
+
else if (cache.interfaceMode === undefined && meta.state?.interface_mode !== undefined) {
|
|
180
|
+
zhemi101ApplyInterfaceMode(cache, meta.state.interface_mode, false);
|
|
181
|
+
}
|
|
182
|
+
zhemi101MeterConfigCache.set(key, zhemi101ApplyStandardMeteringAttributes(cache, data));
|
|
183
|
+
return zhemi101MeterConfigCache.get(key) ?? {};
|
|
184
|
+
};
|
|
185
|
+
const zhemi101GetCachedAttribute = (msg, attribute) => {
|
|
186
|
+
try {
|
|
187
|
+
return msg.endpoint.getClusterAttributeValue("seMetering", attribute);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const zhemi101GetCachedEntityAttribute = (entity, attribute) => {
|
|
194
|
+
try {
|
|
195
|
+
return entity.getClusterAttributeValue("seMetering", attribute);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const zhemi101GetMeterConfig = (msg, meta) => {
|
|
202
|
+
const cached = zhemi101MeterConfigCache.get(zhemi101MeterConfigKey(msg.endpoint)) ?? {};
|
|
203
|
+
const interfaceMode = zhemi101FirstDefined(cached.interfaceMode, meta.state?.interface_mode);
|
|
204
|
+
return {
|
|
205
|
+
interfaceMode,
|
|
206
|
+
unitOfMeasure: zhemi101FirstDefined(cached.unitOfMeasure, zhemi101GetCachedAttribute(msg, "unitOfMeasure"), interfaceMode === "electricity"
|
|
207
|
+
? zhemi101UnitOfMeasure.kWh
|
|
208
|
+
: interfaceMode === "gas" || interfaceMode === "water"
|
|
209
|
+
? zhemi101UnitOfMeasure.cubicMeters
|
|
210
|
+
: undefined),
|
|
211
|
+
meteringDeviceType: zhemi101FirstDefined(cached.meteringDeviceType, zhemi101GetCachedAttribute(msg, "meteringDeviceType"), interfaceMode === "electricity"
|
|
212
|
+
? zhemi101MeteringDeviceType.electricity
|
|
213
|
+
: interfaceMode === "gas"
|
|
214
|
+
? zhemi101MeteringDeviceType.gas
|
|
215
|
+
: interfaceMode === "water"
|
|
216
|
+
? zhemi101MeteringDeviceType.water
|
|
217
|
+
: undefined),
|
|
218
|
+
multiplier: zhemi101FirstDefined(cached.multiplier, zhemi101GetCachedAttribute(msg, "multiplier"), 1),
|
|
219
|
+
divisor: zhemi101FirstDefined(cached.divisor, zhemi101GetCachedAttribute(msg, "divisor"), 1000),
|
|
220
|
+
summationFormatting: zhemi101FirstDefined(cached.summationFormatting, zhemi101GetCachedAttribute(msg, "summaFormatting")),
|
|
221
|
+
demandFormatting: zhemi101FirstDefined(cached.demandFormatting, zhemi101GetCachedAttribute(msg, "demandFormatting")),
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
const zhemi101GetMeterConfigFromEntity = (entity, meta) => {
|
|
225
|
+
const cached = zhemi101MeterConfigCache.get(zhemi101MeterConfigKey(entity)) ?? {};
|
|
226
|
+
const interfaceMode = zhemi101FirstDefined(cached.interfaceMode, meta.state?.interface_mode);
|
|
227
|
+
return {
|
|
228
|
+
interfaceMode,
|
|
229
|
+
unitOfMeasure: zhemi101FirstDefined(cached.unitOfMeasure, zhemi101GetCachedEntityAttribute(entity, "unitOfMeasure"), interfaceMode === "electricity"
|
|
230
|
+
? zhemi101UnitOfMeasure.kWh
|
|
231
|
+
: interfaceMode === "gas" || interfaceMode === "water"
|
|
232
|
+
? zhemi101UnitOfMeasure.cubicMeters
|
|
233
|
+
: undefined),
|
|
234
|
+
multiplier: zhemi101FirstDefined(cached.multiplier, zhemi101GetCachedEntityAttribute(entity, "multiplier"), 1),
|
|
235
|
+
divisor: zhemi101FirstDefined(cached.divisor, zhemi101GetCachedEntityAttribute(entity, "divisor"), 1000),
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
const zhemi101Scale = (config) => {
|
|
239
|
+
return zhemi101Scaling(config).scale;
|
|
240
|
+
};
|
|
241
|
+
const zhemi101Scaling = (config) => {
|
|
242
|
+
const multiplier = zhemi101ToNumber(config.multiplier);
|
|
243
|
+
const divisor = zhemi101ToNumber(config.divisor);
|
|
244
|
+
const effectiveMultiplier = multiplier ?? 1;
|
|
245
|
+
const effectiveDivisor = divisor ?? 1000;
|
|
246
|
+
const scale = effectiveMultiplier !== 0 && effectiveDivisor !== 0 ? effectiveMultiplier / effectiveDivisor : 1;
|
|
247
|
+
return { multiplier: effectiveMultiplier, divisor: effectiveDivisor, scale };
|
|
248
|
+
};
|
|
249
|
+
const zhemi101DecimalsFromDivisor = (divisor) => {
|
|
250
|
+
const value = zhemi101ToNumber(divisor);
|
|
251
|
+
if (value === undefined || value <= 0)
|
|
252
|
+
return 3;
|
|
253
|
+
const log10 = Math.log10(value);
|
|
254
|
+
return Number.isInteger(log10) ? log10 : 3;
|
|
255
|
+
};
|
|
256
|
+
const zhemi101DecimalsFromFormatting = (formatting, fallback) => {
|
|
257
|
+
const value = zhemi101ToNumber(formatting);
|
|
258
|
+
return value === undefined ? fallback : value & 0x07;
|
|
259
|
+
};
|
|
260
|
+
const zhemi101NormalizeMeterValue = (rawValue, config, isDemand) => {
|
|
261
|
+
const raw = zhemi101ToNumber(rawValue);
|
|
262
|
+
if (raw === undefined || raw === zhemi101Uint48Max)
|
|
263
|
+
return undefined;
|
|
264
|
+
const unitInfo = zhemi101UnitInfo[zhemi101ToNumber(config.unitOfMeasure) ?? -1];
|
|
265
|
+
if (unitInfo === undefined)
|
|
266
|
+
return undefined;
|
|
267
|
+
const normalized = raw * zhemi101Scale(config) * unitInfo.factorToTarget;
|
|
268
|
+
const sourceDecimals = zhemi101DecimalsFromFormatting(isDemand ? config.demandFormatting : config.summationFormatting, zhemi101DecimalsFromDivisor(config.divisor));
|
|
269
|
+
const extraDecimals = unitInfo.factorToTarget === 1 ? 0 : unitInfo.factorToTarget < 1 ? Math.ceil(-Math.log10(unitInfo.factorToTarget)) + 2 : 2;
|
|
270
|
+
return utils.precisionRound(normalized, Math.min(9, sourceDecimals + extraDecimals));
|
|
271
|
+
};
|
|
272
|
+
const zhemi101FormatRational = (numerator, denominator) => {
|
|
273
|
+
const integer = numerator / denominator;
|
|
274
|
+
const remainder = numerator % denominator;
|
|
275
|
+
if (remainder === 0n)
|
|
276
|
+
return integer.toString();
|
|
277
|
+
let fraction = "";
|
|
278
|
+
let scaledRemainder = remainder;
|
|
279
|
+
for (let i = 0; i < 12 && scaledRemainder !== 0n; i++) {
|
|
280
|
+
scaledRemainder *= 10n;
|
|
281
|
+
fraction += (scaledRemainder / denominator).toString();
|
|
282
|
+
scaledRemainder %= denominator;
|
|
283
|
+
}
|
|
284
|
+
return `${integer.toString()}.${fraction.replace(/0+$/, "")}`;
|
|
285
|
+
};
|
|
286
|
+
const zhemi101FormatUnitSummationMax = (config, unitInfo) => {
|
|
287
|
+
const { multiplier, divisor, scale } = zhemi101Scaling(config);
|
|
288
|
+
if (unitInfo.factorToTarget === 1 && Number.isInteger(multiplier) && Number.isInteger(divisor) && multiplier > 0 && divisor > 0) {
|
|
289
|
+
return zhemi101FormatRational(BigInt(zhemi101Uint48Max) * BigInt(multiplier), BigInt(divisor));
|
|
290
|
+
}
|
|
291
|
+
return (zhemi101Uint48Max * scale * unitInfo.factorToTarget).toLocaleString("en-US", {
|
|
292
|
+
maximumFractionDigits: 12,
|
|
293
|
+
useGrouping: false,
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
const zhemi101DenormalizeUnitSummation = (value, config) => {
|
|
297
|
+
const normalizedValue = zhemi101ToNumber(value);
|
|
298
|
+
if (normalizedValue === undefined || normalizedValue < 0) {
|
|
299
|
+
throw new Error(`Invalid unit_summation value: ${value}`);
|
|
300
|
+
}
|
|
301
|
+
const unitInfo = zhemi101UnitInfo[zhemi101ToNumber(config.unitOfMeasure) ?? -1];
|
|
302
|
+
if (unitInfo === undefined) {
|
|
303
|
+
throw new Error("Cannot set unit_summation because unit of measure is not known yet");
|
|
304
|
+
}
|
|
305
|
+
const maxUnitSummation = zhemi101Uint48Max * zhemi101Scale(config) * unitInfo.factorToTarget;
|
|
306
|
+
if (normalizedValue > maxUnitSummation) {
|
|
307
|
+
throw new Error(`unit_summation cannot exceed ${zhemi101FormatUnitSummationMax(config, unitInfo)} with the current multiplier ${zhemi101Scaling(config).multiplier} and divisor ${zhemi101Scaling(config).divisor}`);
|
|
308
|
+
}
|
|
309
|
+
const rawValue = normalizedValue / unitInfo.factorToTarget / zhemi101Scale(config);
|
|
310
|
+
const roundedRawValue = Math.round(rawValue);
|
|
311
|
+
if (!Number.isFinite(rawValue) || roundedRawValue < 0 || roundedRawValue > zhemi101Uint48Max) {
|
|
312
|
+
throw new Error(`unit_summation cannot exceed ${zhemi101FormatUnitSummationMax(config, unitInfo)} with the current multiplier ${zhemi101Scaling(config).multiplier} and divisor ${zhemi101Scaling(config).divisor}`);
|
|
313
|
+
}
|
|
314
|
+
return roundedRawValue;
|
|
315
|
+
};
|
|
316
|
+
const zhemi101MeterKind = (config) => {
|
|
317
|
+
if (config.interfaceMode === "electricity")
|
|
318
|
+
return "energy";
|
|
319
|
+
if (config.interfaceMode === "gas")
|
|
320
|
+
return "gas";
|
|
321
|
+
if (config.interfaceMode === "water")
|
|
322
|
+
return "water";
|
|
323
|
+
const meteringDeviceType = zhemi101ToNumber(config.meteringDeviceType);
|
|
324
|
+
if (meteringDeviceType !== undefined) {
|
|
325
|
+
const deviceType = meteringDeviceType & 0x07;
|
|
326
|
+
if (deviceType === zhemi101MeteringDeviceType.electricity)
|
|
327
|
+
return "energy";
|
|
328
|
+
if (deviceType === zhemi101MeteringDeviceType.gas)
|
|
329
|
+
return "gas";
|
|
330
|
+
if (deviceType === zhemi101MeteringDeviceType.water)
|
|
331
|
+
return "water";
|
|
332
|
+
}
|
|
333
|
+
return zhemi101UnitInfo[zhemi101ToNumber(config.unitOfMeasure) ?? -1]?.kind === "energy" ? "energy" : undefined;
|
|
334
|
+
};
|
|
64
335
|
// develco specific converters
|
|
65
336
|
const develco = {
|
|
66
337
|
fz: {
|
|
@@ -111,6 +382,52 @@ const develco = {
|
|
|
111
382
|
return result;
|
|
112
383
|
},
|
|
113
384
|
},
|
|
385
|
+
metering_zhemi101: {
|
|
386
|
+
cluster: "seMetering",
|
|
387
|
+
type: ["attributeReport", "readResponse"],
|
|
388
|
+
convert: (model, msg, publish, options, meta) => {
|
|
389
|
+
zhemi101UpdateMeterConfigFromMessage(msg, meta);
|
|
390
|
+
const config = zhemi101GetMeterConfig(msg, meta);
|
|
391
|
+
const data = msg.data;
|
|
392
|
+
const payload = {};
|
|
393
|
+
const summationFormatting = zhemi101FirstDefined(data.summaFormatting, data.summationFormatting);
|
|
394
|
+
if (data.unitOfMeasure !== undefined)
|
|
395
|
+
payload.unit_of_measure = data.unitOfMeasure;
|
|
396
|
+
if (data.meteringDeviceType !== undefined)
|
|
397
|
+
payload.metering_device_type = data.meteringDeviceType;
|
|
398
|
+
if (summationFormatting !== undefined)
|
|
399
|
+
payload.summation_formatting = summationFormatting;
|
|
400
|
+
if (data.demandFormatting !== undefined)
|
|
401
|
+
payload.demand_formatting = data.demandFormatting;
|
|
402
|
+
if (data.multiplier !== undefined)
|
|
403
|
+
payload.multiplier = data.multiplier;
|
|
404
|
+
if (data.divisor !== undefined)
|
|
405
|
+
payload.divisor = data.divisor;
|
|
406
|
+
const meterKind = zhemi101MeterKind(config);
|
|
407
|
+
if (data.currentSummDelivered !== undefined) {
|
|
408
|
+
const property = meterKind === "energy" ? "energy" : meterKind === "gas" ? "gas" : meterKind === "water" ? "water_consumed" : undefined;
|
|
409
|
+
const value = zhemi101NormalizeMeterValue(data.currentSummDelivered, config, false);
|
|
410
|
+
if (property !== undefined && value !== undefined)
|
|
411
|
+
payload[utils.postfixWithEndpointName(property, msg, model, meta)] = value;
|
|
412
|
+
}
|
|
413
|
+
if (data.currentSummReceived !== undefined && meterKind === "energy") {
|
|
414
|
+
const value = zhemi101NormalizeMeterValue(data.currentSummReceived, config, false);
|
|
415
|
+
if (value !== undefined)
|
|
416
|
+
payload[utils.postfixWithEndpointName("produced_energy", msg, model, meta)] = value;
|
|
417
|
+
}
|
|
418
|
+
if (data.instantaneousDemand !== undefined) {
|
|
419
|
+
const property = meterKind === "energy" ? "power" : meterKind === "gas" || meterKind === "water" ? "flow" : undefined;
|
|
420
|
+
const value = zhemi101NormalizeMeterValue(data.instantaneousDemand, config, true);
|
|
421
|
+
if (property !== undefined && value !== undefined)
|
|
422
|
+
payload[utils.postfixWithEndpointName(property, msg, model, meta)] = value;
|
|
423
|
+
}
|
|
424
|
+
if (data.status !== undefined) {
|
|
425
|
+
payload.battery_low = (data.status & 2) > 0;
|
|
426
|
+
payload.check_meter = (data.status & 1) > 0;
|
|
427
|
+
}
|
|
428
|
+
return payload;
|
|
429
|
+
},
|
|
430
|
+
},
|
|
114
431
|
fault_status: {
|
|
115
432
|
cluster: "genBinaryInput",
|
|
116
433
|
type: ["attributeReport", "readResponse"],
|
|
@@ -240,6 +557,26 @@ const develco = {
|
|
|
240
557
|
await entity.read("seMetering", ["develcoInterfaceMode"], manufacturerOptions);
|
|
241
558
|
},
|
|
242
559
|
},
|
|
560
|
+
interface_mode_zhemi101: {
|
|
561
|
+
key: ["interface_mode"],
|
|
562
|
+
convertSet: async (entity, key, value, meta) => {
|
|
563
|
+
const payload = { develcoInterfaceMode: utils.getKey(constants.develcoInterfaceMode, value, undefined, Number) };
|
|
564
|
+
await entity.write("seMetering", payload, manufacturerOptions);
|
|
565
|
+
const cache = zhemi101MeterConfigCache.get(zhemi101MeterConfigKey(entity)) ?? {};
|
|
566
|
+
zhemi101MeterConfigCache.set(zhemi101MeterConfigKey(entity), zhemi101ApplyInterfaceMode(cache, value, true));
|
|
567
|
+
await zhemi101ReadMeteringConfiguration(entity);
|
|
568
|
+
return { state: { interface_mode: value } };
|
|
569
|
+
},
|
|
570
|
+
convertGet: async (entity, key, meta) => {
|
|
571
|
+
try {
|
|
572
|
+
await entity.read("seMetering", ["develcoInterfaceMode"], manufacturerOptions);
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
// Some ZHEMI101 firmware returns a manufacturer-specific response that cannot be parsed.
|
|
576
|
+
}
|
|
577
|
+
await zhemi101ReadMeteringConfiguration(entity);
|
|
578
|
+
},
|
|
579
|
+
},
|
|
243
580
|
current_summation: {
|
|
244
581
|
key: ["current_summation"],
|
|
245
582
|
convertSet: async (entity, key, value, meta) => {
|
|
@@ -247,6 +584,15 @@ const develco = {
|
|
|
247
584
|
return { state: { current_summation: value } };
|
|
248
585
|
},
|
|
249
586
|
},
|
|
587
|
+
unit_summation_zhemi101: {
|
|
588
|
+
key: ["unit_summation"],
|
|
589
|
+
convertSet: async (entity, key, value, meta) => {
|
|
590
|
+
await zhemi101ReadMeteringConfiguration(entity);
|
|
591
|
+
const rawSummation = zhemi101DenormalizeUnitSummation(value, zhemi101GetMeterConfigFromEntity(entity, meta));
|
|
592
|
+
await entity.write("seMetering", { develcoCurrentSummation: rawSummation }, manufacturerOptions);
|
|
593
|
+
return { state: { unit_summation: zhemi101ToNumber(value) } };
|
|
594
|
+
},
|
|
595
|
+
},
|
|
250
596
|
led_control: {
|
|
251
597
|
key: ["led_control"],
|
|
252
598
|
convertSet: async (entity, key, value, meta) => {
|
|
@@ -834,9 +1180,9 @@ exports.definitions = [
|
|
|
834
1180
|
zigbeeModel: ["ZHEMI101"],
|
|
835
1181
|
model: "ZHEMI101",
|
|
836
1182
|
vendor: "Develco",
|
|
837
|
-
description: "Energy meter",
|
|
838
|
-
fromZigbee: [develco.fz.
|
|
839
|
-
toZigbee: [develco.tz.pulse_configuration, develco.tz.
|
|
1183
|
+
description: "Energy/gas/water meter interface",
|
|
1184
|
+
fromZigbee: [develco.fz.metering_zhemi101, develco.fz.pulse_configuration, develco.fz.interface_mode],
|
|
1185
|
+
toZigbee: [develco.tz.pulse_configuration, develco.tz.interface_mode_zhemi101, develco.tz.unit_summation_zhemi101],
|
|
840
1186
|
endpoint: (device) => {
|
|
841
1187
|
return { default: 2 };
|
|
842
1188
|
},
|
|
@@ -850,24 +1196,44 @@ exports.definitions = [
|
|
|
850
1196
|
await reporting.bind(endpoint, coordinatorEndpoint, ["seMetering"]);
|
|
851
1197
|
await reporting.instantaneousDemand(endpoint);
|
|
852
1198
|
await reporting.readMeteringMultiplierDivisor(endpoint);
|
|
1199
|
+
await zhemi101ReadMeteringConfiguration(endpoint);
|
|
853
1200
|
},
|
|
854
1201
|
exposes: [
|
|
855
|
-
e.
|
|
856
|
-
e.
|
|
1202
|
+
e.energy().withDescription("Normalized cumulative energy. Source units such as kWh or BTU are converted to kWh."),
|
|
1203
|
+
e.produced_energy().withDescription("Normalized produced energy, when reported by the meter."),
|
|
1204
|
+
e.power().withUnit("kW").withDescription("Normalized instantaneous demand. Source units such as kW or BTU/h are converted to kW."),
|
|
1205
|
+
e
|
|
1206
|
+
.numeric("gas", ea.STATE)
|
|
1207
|
+
.withUnit("m³")
|
|
1208
|
+
.withDescription("Normalized cumulative gas volume. Source units are converted to cubic meters."),
|
|
1209
|
+
e
|
|
1210
|
+
.numeric("water_consumed", ea.STATE)
|
|
1211
|
+
.withUnit("m³")
|
|
1212
|
+
.withDescription("Normalized cumulative water volume. Source units are converted to cubic meters."),
|
|
1213
|
+
e
|
|
1214
|
+
.numeric("flow", ea.STATE)
|
|
1215
|
+
.withUnit("m³/h")
|
|
1216
|
+
.withDescription("Normalized volume flow rate for gas or water. Source units are converted to cubic meters per hour."),
|
|
857
1217
|
e.battery_low(),
|
|
1218
|
+
e.numeric("unit_of_measure", ea.STATE).withDescription("Raw ZigBee Smart Energy UnitOfMeasure attribute."),
|
|
1219
|
+
e.numeric("metering_device_type", ea.STATE).withDescription("Raw ZigBee Smart Energy MeteringDeviceType attribute."),
|
|
1220
|
+
e.numeric("summation_formatting", ea.STATE).withDescription("Raw ZigBee Smart Energy SummationFormatting attribute."),
|
|
1221
|
+
e.numeric("demand_formatting", ea.STATE).withDescription("Raw ZigBee Smart Energy DemandFormatting attribute."),
|
|
1222
|
+
e.numeric("multiplier", ea.STATE).withDescription("Raw ZigBee Smart Energy Multiplier attribute."),
|
|
1223
|
+
e.numeric("divisor", ea.STATE).withDescription("Raw ZigBee Smart Energy Divisor attribute."),
|
|
858
1224
|
e
|
|
859
1225
|
.numeric("pulse_configuration", ea.ALL)
|
|
860
1226
|
.withValueMin(0)
|
|
861
1227
|
.withValueMax(65535)
|
|
862
|
-
.withDescription("Pulses per
|
|
1228
|
+
.withDescription("Pulses per unit. Default 1000 imp/kWh for electricity. Range 0 to 65535"),
|
|
863
1229
|
e
|
|
864
1230
|
.enum("interface_mode", ea.ALL, ["electricity", "gas", "water", "kamstrup-kmp", "linky", "IEC62056-21", "DSMR-2.3", "DSMR-4.0"])
|
|
865
1231
|
.withDescription("Operating mode/probe"),
|
|
866
1232
|
e
|
|
867
|
-
.numeric("
|
|
868
|
-
.withDescription("
|
|
1233
|
+
.numeric("unit_summation", ea.SET)
|
|
1234
|
+
.withDescription("Sets the meter summation in the normalized published unit: kWh for energy meters and m³ for gas/water meters.")
|
|
869
1235
|
.withValueMin(0)
|
|
870
|
-
.withValueMax(
|
|
1236
|
+
.withValueMax(zhemi101UnitSummationUiMax),
|
|
871
1237
|
e.binary("check_meter", ea.STATE, true, false).withDescription("Is true if communication problem with meter is experienced"),
|
|
872
1238
|
],
|
|
873
1239
|
},
|