Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ If any of those commands finish with an error your PR won't pass the tests and w

25.0.0

- Changed the `onEvent` api, see https://github.com/Koenkk/zigbee-herdsman-converters/pull/9650 for more info.
- A `device` argument has been added to `postProcessConvertedFromZigbeeMessage` (https://github.com/Koenkk/zigbee-herdsman-converters/pull/9693)

24.0.0
Expand Down
14 changes: 14 additions & 0 deletions src/converters/fromZigbee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,20 @@ export const lock_programming_event: Fz.Converter = {
};
},
};
export const lock_programming_event_read_pincode: Fz.Converter = {
cluster: "closuresDoorLock",
type: "commandProgrammingEventNotification",
convert: (model, msg, publish, options, meta) => {
if (
msg.data.userid !== undefined &&
(msg.data.programeventsrc === undefined || constants.lockSourceName[msg.data.programeventsrc] !== "rf")
) {
msg.endpoint
.command("closuresDoorLock", "getPinCode", {userid: msg.data.userid}, {})
.catch((error) => logger.error(`Failed to read pincode of '${msg.device.ieeeAddr}' (${error})`, NS));
}
},
};
export const lock: Fz.Converter = {
cluster: "closuresDoorLock",
type: ["attributeReport", "readResponse"],
Expand Down
8 changes: 1 addition & 7 deletions src/devices/adeo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,7 @@ export const definitions: DefinitionWithExtend[] = [
fromZigbee: [fz.command_arm, fz.command_panic],
toZigbee: [],
exposes: [e.action(["panic", "disarm", "arm_partial_zones", "arm_all_zones"])],
onEvent: async (type, data, device) => {
// Since arm command has a response zigbee-herdsman doesn't send a default response.
// This causes the remote to repeat the arm command, so send a default response here.
if (data.type === "commandArm" && data.cluster === "ssIasAce") {
await data.endpoint.defaultResponse(0, 0, 1281, data.meta.zclTransactionSequenceNumber);
}
},
extend: [m.iasArmCommandDefaultResponse()],
},
{
zigbeeModel: ["ZBEK-1"],
Expand Down
43 changes: 24 additions & 19 deletions src/devices/amina.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import {Zcl} from "zigbee-herdsman";

import * as constants from "../lib/constants";
import * as exposes from "../lib/exposes";
import {logger} from "../lib/logger";
import * as m from "../lib/modernExtend";
import * as reporting from "../lib/reporting";
import type {DefinitionWithExtend, Fz, KeyValue, Tz} from "../lib/types";
import * as utils from "../lib/utils";

const NS = "zhc:amina";

const e = exposes.presets;
const ea = exposes.access;

Expand Down Expand Up @@ -46,12 +49,33 @@ const aminaAlarms = [
];

const fzLocal = {
poll_energy: {
cluster: "haElectricalMeasurement",
type: ["attributeReport"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.totalActivePower != null) {
// Device does not support reporting of energy attributes, so we poll them manually when power is updated
msg.endpoint.read("aminaControlCluster", ["totalActiveEnergy"]).catch((error) => {
logger.error(`Failed to poll energy of '${msg.device.ieeeAddr}' (${error})`, NS);
});
}
},
} satisfies Fz.Converter,
ev_status: {
cluster: "aminaControlCluster",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result: KeyValue = {};

if (msg.type === "attributeReport") {
// Device does not support reporting of energy attributes, so we poll them manually when charging is stopped
if ((msg.data.evStatus & (1 << 2)) === 0) {
msg.endpoint.read("aminaControlCluster", ["totalActiveEnergy", "lastSessionEnergy"]).catch((error) => {
logger.error(`Failed to poll energy of '${msg.device.ieeeAddr}' (${error})`, NS);
});
}
}

if (msg.data.evStatus !== undefined) {
let statusText = "Not Connected";
const evStatus = msg.data.evStatus;
Expand All @@ -73,7 +97,6 @@ const fzLocal = {
}
},
} satisfies Fz.Converter,

alarms: {
cluster: "aminaControlCluster",
type: ["attributeReport", "readResponse"],
Expand Down Expand Up @@ -106,14 +129,12 @@ const tzLocal = {
await entity.read("genLevelCtrl", ["currentLevel"]);
},
} satisfies Tz.Converter,

ev_status: {
key: ["ev_status"],
convertGet: async (entity, key, meta) => {
await entity.read("aminaControlCluster", ["evStatus"], manufacturerOptions);
},
} satisfies Tz.Converter,

alarms: {
key: ["alarms"],
convertGet: async (entity, key, meta) => {
Expand Down Expand Up @@ -308,11 +329,9 @@ export const definitions: DefinitionWithExtend[] = [
entityCategory: "config",
}),
],

endpoint: (device) => {
return {default: 10};
},

configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(10);

Expand Down Expand Up @@ -350,19 +369,5 @@ export const definitions: DefinitionWithExtend[] = [
"lastSessionEnergy",
]);
},

onEvent: async (type, data, device) => {
if (type === "message" && data.type === "attributeReport" && data.cluster === "haElectricalMeasurement" && data.data.totalActivePower) {
// Device does not support reporting of energy attributes, so we poll them manually when power is updated
await data.endpoint.read("aminaControlCluster", ["totalActiveEnergy"]);
}

if (type === "message" && data.type === "attributeReport" && data.cluster === "aminaControlCluster" && data.data.evStatus) {
// Device does not support reporting of energy attributes, so we poll them manually when charging is stopped
if ((data.data.evStatus & (1 << 2)) === 0) {
await data.endpoint.read("aminaControlCluster", ["totalActiveEnergy", "lastSessionEnergy"]);
}
}
},
},
];
8 changes: 4 additions & 4 deletions src/devices/aurora_lighting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ const batteryRotaryDimmer = (...endpointsIds: number[]) => ({
await disableBatteryRotaryDimmerReporting(endpoint);
}
}) satisfies Configure,
onEvent: (async (type, data, device) => {
onEvent: (async (event) => {
// The rotary dimmer devices appear to lose the configured reportings when they
// re-announce themselves which they do roughly every 6 hours.
if (type === "deviceAnnounce") {
for (const endpoint of device.endpoints) {
if (event.type === "deviceAnnounce") {
for (const endpoint of event.data.device.endpoints) {
// First disable the default reportings (for the dimmer endpoints only)
if ([1, 2].includes(endpoint.ID)) {
await disableBatteryRotaryDimmerReporting(endpoint);
Expand All @@ -84,7 +84,7 @@ const batteryRotaryDimmer = (...endpointsIds: number[]) => ({
}
}
}
}) satisfies OnEvent,
}) satisfies OnEvent.Handler,
});

export const definitions: DefinitionWithExtend[] = [
Expand Down
14 changes: 2 additions & 12 deletions src/devices/avatto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,7 @@ export const definitions: DefinitionWithExtend[] = [
model: "ZWSH16",
vendor: "AVATTO",
description: "Smart Temperature and Humidity Detector",
fromZigbee: [tuya.fz.datapoints],
toZigbee: [tuya.tz.datapoints],
onEvent: tuya.onEventSetTime,
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await endpoint.command("manuSpecificTuya", "mcuVersionRequest", {seq: 0x0002});
},
extend: [tuya.modernExtend.tuyaBase({dp: true, timeStart: "2000", mcuVersionRequestOnConfigure: true})],
exposes: [e.battery(), e.temperature(), e.humidity(), tuya.exposes.temperatureUnit(), tuya.exposes.batteryState()],
meta: {
tuyaDatapoints: [
Expand All @@ -37,11 +30,8 @@ export const definitions: DefinitionWithExtend[] = [
model: "ME168_AVATTO",
vendor: "AVATTO",
description: "Thermostatic radiator valve",
fromZigbee: [tuya.fz.datapoints],
toZigbee: [tuya.tz.datapoints],
extend: [tuya.modernExtend.tuyaBase({dp: true, timeStart: "2000"})],
whiteLabel: [tuya.whitelabel("Girier", "ME168_Girier", "Thermostatic radiator valve", ["_TZE200_cxakecfo", "_TZE200_4aijvczq"])],
onEvent: tuya.onEventSetTime,
configure: tuya.configureMagicPacket,
ota: true,
exposes: [
e.battery(),
Expand Down
45 changes: 22 additions & 23 deletions src/devices/busch_jaeger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,29 +119,28 @@ export const definitions: DefinitionWithExtend[] = [
fz.command_stop,
fz.command_recall,
],
options: [
e
.numeric("state_poll_interval", ea.SET)
.withValueMin(-1)
.withDescription(
"This device does not support state reporting so it is polled instead. The default poll interval is 60 seconds, set to -1 to disable.",
),
],
toZigbee: [tz.light_onoff_brightness, tz.light_brightness_step, tz.light_brightness_move],
onEvent: (type, data, device, options) => {
const switchEndpoint = device.getEndpoint(0x12);
if (switchEndpoint == null) {
return;
}
// This device doesn't support reporting.
// Therefore we read the on/off state every 60 seconds.
// This is the same way as the Hue bridge does it.
const poll = async () => {
await switchEndpoint.read("genOnOff", ["onOff"]);
await switchEndpoint.read("genLevelCtrl", ["currentLevel"]);
};

utils.onEventPoll(type, data, device, options, "state", 60, poll);
},
extend: [
// This device doesn't support reporting. Therefore we read the on/off state every 60 seconds.
// This is the same was as the Hue bridge does it.
m.poll({
key: "state",
option: e
.numeric("state_poll_interval", ea.SET)
.withValueMin(-1)
.withDescription(
"This device does not support state reporting so it is polled instead. The default poll interval is 60 seconds, set to -1 to disable.",
),
defaultIntervalSeconds: 60,
poll: async (device) => {
const switchEndpoint = device.getEndpoint(0x12);
if (switchEndpoint == null) {
return;
}
await switchEndpoint.read("genOnOff", ["onOff"]);
await switchEndpoint.read("genLevelCtrl", ["currentLevel"]);
},
}),
],
},
];
18 changes: 1 addition & 17 deletions src/devices/centralite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as constants from "../lib/constants";
import * as exposes from "../lib/exposes";
import * as m from "../lib/modernExtend";
import * as reporting from "../lib/reporting";
import * as globalStore from "../lib/store";
import type {DefinitionWithExtend, Fz} from "../lib/types";

const e = exposes.presets;
Expand Down Expand Up @@ -201,22 +200,7 @@ export const definitions: DefinitionWithExtend[] = [
await reporting.temperature(endpoint);
await reporting.batteryVoltage(endpoint);
},
onEvent: async (type, data, device) => {
if (
type === "message" &&
data.type === "commandGetPanelStatus" &&
data.cluster === "ssIasAce" &&
globalStore.hasValue(device.getEndpoint(1), "panelStatus")
) {
const payload = {
panelstatus: globalStore.getValue(device.getEndpoint(1), "panelStatus"),
secondsremain: 0x00,
audiblenotif: 0x00,
alarmstatus: 0x00,
};
await device.getEndpoint(1).commandResponse("ssIasAce", "getPanelStatusRsp", payload, {}, data.meta.zclTransactionSequenceNumber);
}
},
extend: [m.iasGetPanelStatusResponse()],
},
{
zigbeeModel: ["3420"],
Expand Down
35 changes: 14 additions & 21 deletions src/devices/danfoss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import * as fz from "../converters/fromZigbee";
import * as tz from "../converters/toZigbee";
import * as constants from "../lib/constants";
import * as exposes from "../lib/exposes";
import * as m from "../lib/modernExtend";
import * as reporting from "../lib/reporting";
import * as globalStore from "../lib/store";
import type {DefinitionWithExtend, Zh} from "../lib/types";
import * as utils from "../lib/utils";

Expand Down Expand Up @@ -345,26 +345,19 @@ export const definitions: DefinitionWithExtend[] = [
// So, we need to write time during configure (same as for HEIMAN devices)
await setTime(device);
},
onEvent: async (type, data, device) => {
if (type === "stop") {
clearInterval(globalStore.getValue(device, "interval"));
globalStore.clearValue(device, "interval");
} else if (["deviceAnnounce", "start"].includes(type)) {
// The device might have lost its time, so reset it. It would be more proper to check if
// the danfossSystemStatusCode has bit 10 of the SW error code attribute (0x4000) in the
// diagnostics cluster (0x0b05) is set to indicate time lost, but setting it once too many
// times shouldn't hurt.
await setTime(device);

if (!globalStore.hasValue(device, "interval")) {
// Set up a timer to refresh the time once a week to mitigate timer drift, as described
// in the Danfoss documentation. Be careful to not bump this timer past the signed 32-bit
// integer limit of setInterval, which is roughly 24.8 days.
const interval = setInterval(async () => await setTime(device), 10080000);
globalStore.putValue(device, "interval", interval);
}
}
},
extend: [
m.poll({
key: "time_sync",
defaultIntervalSeconds: 60 * 60 * 24,
poll: async (device) => {
// The device might have lost its time, so reset it. It would be more proper to check if
// the danfossSystemStatusCode has bit 10 of the SW error code attribute (0x4000) in the
// diagnostics cluster (0x0b05) is set to indicate time lost, but setting it once too many
// times shouldn't hurt.
await setTime(device);
},
}),
],
},
{
fingerprint: [
Expand Down
23 changes: 10 additions & 13 deletions src/devices/datek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,16 @@ export const definitions: DefinitionWithExtend[] = [
model: "0402946",
vendor: "Datek",
description: "Zigbee module for ID lock",
fromZigbee: [fz.lock, fz.battery, fz.lock_operation_event, fz.lock_programming_event, fz.idlock, fz.idlock_fw, fz.lock_pin_code_response],
fromZigbee: [
fz.lock,
fz.battery,
fz.lock_operation_event,
fz.lock_programming_event,
fz.idlock,
fz.idlock_fw,
fz.lock_pin_code_response,
fz.lock_programming_event_read_pincode,
],
toZigbee: [
tz.lock,
tz.lock_sound_volume,
Expand Down Expand Up @@ -168,18 +177,6 @@ export const definitions: DefinitionWithExtend[] = [
await endpoint.read("closuresDoorLock", [0x4000, 0x4001, 0x4003, 0x4004, 0x4005], options);
await endpoint.read("genBasic", [0x5000], options);
},
onEvent: async (type, data, device) => {
// When we receive a code updated message, lets read the new value
if (
data.type === "commandProgrammingEventNotification" &&
data.cluster === "closuresDoorLock" &&
data.data &&
data.data.userid !== undefined &&
(data.data.programeventsrc === undefined || constants.lockSourceName[data.data.programeventsrc] !== "rf")
) {
await device.endpoints[0].command("closuresDoorLock", "getPinCode", {userid: data.data.userid}, {});
}
},
exposes: [
e.lock(),
e.battery(),
Expand Down
Loading