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
425 changes: 231 additions & 194 deletions src/converters/toZigbee.ts

Large diffs are not rendered by default.

98 changes: 40 additions & 58 deletions src/devices/amina.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Zcl} from "zigbee-herdsman";

import * as constants from "../lib/constants";
import * as exposes from "../lib/exposes";
import {logger} from "../lib/logger";
Expand All @@ -15,6 +14,23 @@ const ea = exposes.access;

const manufacturerOptions = {manufacturerCode: 0x143b};

interface AminaControlCluster {
attributes: {
alarms: number;
evStatus: number;
connectStatus: number;
singlePhase: number;
offlineCurrent: number;
offlineSinglePhase: number;
timeToOffline: number;
enableOffline: number;
totalActiveEnergy: number;
lastSessionEnergy: number;
};
commands: never;
commandResponses: never;
}

const aminaControlAttributes = {
cluster: 0xfee7,
alarms: 0x02,
Expand Down Expand Up @@ -55,7 +71,7 @@ const fzLocal = {
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) => {
msg.endpoint.read<"aminaControlCluster", AminaControlCluster>("aminaControlCluster", ["totalActiveEnergy"]).catch((error) => {
logger.error(`Failed to poll energy of '${msg.device.ieeeAddr}' (${error})`, NS);
});
}
Expand All @@ -70,9 +86,11 @@ const fzLocal = {
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);
});
msg.endpoint
.read<"aminaControlCluster", AminaControlCluster>("aminaControlCluster", ["totalActiveEnergy", "lastSessionEnergy"])
.catch((error) => {
logger.error(`Failed to poll energy of '${msg.device.ieeeAddr}' (${error})`, NS);
});
}
}

Expand Down Expand Up @@ -121,7 +139,7 @@ const tzLocal = {
charge_limit: {
key: ["charge_limit"],
convertSet: async (entity, key, value, meta) => {
const payload = {level: value, transtime: 0};
const payload = {level: value as number, transtime: 0};
await entity.command("genLevelCtrl", "moveToLevel", payload, utils.getOptions(meta.mapped, entity));
},

Expand All @@ -132,13 +150,13 @@ const tzLocal = {
ev_status: {
key: ["ev_status"],
convertGet: async (entity, key, meta) => {
await entity.read("aminaControlCluster", ["evStatus"], manufacturerOptions);
await entity.read<"aminaControlCluster", AminaControlCluster>("aminaControlCluster", ["evStatus"], manufacturerOptions);
},
} satisfies Tz.Converter,
alarms: {
key: ["alarms"],
convertGet: async (entity, key, meta) => {
await entity.read("aminaControlCluster", ["alarms"], manufacturerOptions);
await entity.read<"aminaControlCluster", AminaControlCluster>("aminaControlCluster", ["alarms"], manufacturerOptions);
},
} satisfies Tz.Converter,
};
Expand All @@ -155,6 +173,10 @@ export const definitions: DefinitionWithExtend[] = [
exposes: [
e.text("ev_status", ea.STATE_GET).withDescription("Current charging status"),
e.list("alarms", ea.STATE_GET, e.enum("alarm", ea.STATE_GET, aminaAlarms)).withDescription("List of active alarms"),
e.binary("ev_connected", ea.STATE, true, false).withDescription("An EV is connected to the charger"),
e.binary("charging", ea.STATE, true, false).withDescription("Power is being delivered to the EV"),
e.binary("derated", ea.STATE, true, false).withDescription("Charging derated due to high temperature"),
e.binary("alarm_active", ea.STATE, true, false).withDescription("An active alarm is present"),
],
extend: [
m.deviceAddCustomCluster("aminaControlCluster", {
Expand Down Expand Up @@ -205,7 +227,7 @@ export const definitions: DefinitionWithExtend[] = [
access: "STATE_GET",
}),

m.numeric({
m.numeric<"aminaControlCluster", AminaControlCluster>({
name: "total_active_energy",
cluster: "aminaControlCluster",
attribute: "totalActiveEnergy",
Expand All @@ -217,7 +239,7 @@ export const definitions: DefinitionWithExtend[] = [
access: "STATE_GET",
}),

m.numeric({
m.numeric<"aminaControlCluster", AminaControlCluster>({
name: "last_session_energy",
cluster: "aminaControlCluster",
attribute: "lastSessionEnergy",
Expand All @@ -229,53 +251,13 @@ export const definitions: DefinitionWithExtend[] = [
access: "STATE_GET",
}),

m.binary({
name: "ev_connected",
cluster: "aminaControlCluster",
attribute: "evConnected",
description: "An EV is connected to the charger",
valueOn: [true, 1],
valueOff: [false, 0],
access: "STATE",
}),

m.binary({
name: "charging",
cluster: "aminaControlCluster",
attribute: "charging",
description: "Power is being delivered to the EV",
valueOn: [true, 1],
valueOff: [false, 0],
access: "STATE",
}),

m.binary({
name: "derated",
cluster: "aminaControlCluster",
attribute: "derated",
description: "Charging derated due to high temperature",
valueOn: [true, 1],
valueOff: [false, 0],
access: "STATE",
}),

m.binary({
name: "alarm_active",
cluster: "aminaControlCluster",
attribute: "alarmActive",
description: "An active alarm is present",
valueOn: [true, 1],
valueOff: [false, 0],
access: "STATE",
}),

m.electricityMeter({
cluster: "electrical",
acFrequency: true,
threePhase: true,
}),

m.binary({
m.binary<"aminaControlCluster", AminaControlCluster>({
name: "single_phase",
cluster: "aminaControlCluster",
attribute: "singlePhase",
Expand All @@ -285,7 +267,7 @@ export const definitions: DefinitionWithExtend[] = [
entityCategory: "config",
}),

m.binary({
m.binary<"aminaControlCluster", AminaControlCluster>({
name: "enable_offline",
cluster: "aminaControlCluster",
attribute: "enableOffline",
Expand All @@ -295,7 +277,7 @@ export const definitions: DefinitionWithExtend[] = [
entityCategory: "config",
}),

m.numeric({
m.numeric<"aminaControlCluster", AminaControlCluster>({
name: "time_to_offline",
cluster: "aminaControlCluster",
attribute: "timeToOffline",
Expand All @@ -307,7 +289,7 @@ export const definitions: DefinitionWithExtend[] = [
entityCategory: "config",
}),

m.numeric({
m.numeric<"aminaControlCluster", AminaControlCluster>({
name: "offline_current",
cluster: "aminaControlCluster",
attribute: "offlineCurrent",
Expand All @@ -319,7 +301,7 @@ export const definitions: DefinitionWithExtend[] = [
entityCategory: "config",
}),

m.binary({
m.binary<"aminaControlCluster", AminaControlCluster>({
name: "offline_single_phase",
cluster: "aminaControlCluster",
attribute: "offlineSinglePhase",
Expand All @@ -338,7 +320,7 @@ export const definitions: DefinitionWithExtend[] = [
const binds = ["genBasic", "genLevelCtrl", "aminaControlCluster"];
await reporting.bind(endpoint, coordinatorEndpoint, binds);

await endpoint.configureReporting("aminaControlCluster", [
await endpoint.configureReporting<"aminaControlCluster", AminaControlCluster>("aminaControlCluster", [
{
attribute: "evStatus",
minimumReportInterval: 0,
Expand All @@ -347,7 +329,7 @@ export const definitions: DefinitionWithExtend[] = [
},
]);

await endpoint.configureReporting("aminaControlCluster", [
await endpoint.configureReporting<"aminaControlCluster", AminaControlCluster>("aminaControlCluster", [
{
attribute: "alarms",
minimumReportInterval: 0,
Expand All @@ -356,7 +338,7 @@ export const definitions: DefinitionWithExtend[] = [
},
]);

await endpoint.read("aminaControlCluster", [
await endpoint.read<"aminaControlCluster", AminaControlCluster>("aminaControlCluster", [
"alarms",
"evStatus",
"connectStatus",
Expand Down
5 changes: 3 additions & 2 deletions src/devices/aurora_lighting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ const tzLocal = {
const state = value.toLowerCase();
utils.validateValue(state, ["toggle", "off", "on"]);
const endpoint = meta.device.getEndpoint(3);
await endpoint.command("genOnOff", state, {});
await endpoint.command("genOnOff", state as "toggle" | "off" | "on", {});
return {state: {backlight_led: state.toUpperCase()}};
},
} satisfies Tz.Converter,
backlight_brightness: {
key: ["brightness"],
options: [exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
await entity.command("genLevelCtrl", "moveToLevel", {level: value, transtime: 0}, utils.getOptions(meta.mapped, entity));
await entity.command("genLevelCtrl", "moveToLevel", {level: value as number, transtime: 0}, utils.getOptions(meta.mapped, entity));
return {state: {brightness: value}};
},
convertGet: async (entity, key, meta) => {
Expand Down Expand Up @@ -75,6 +75,7 @@ const batteryRotaryDimmer = (...endpointsIds: number[]) => ({
for (const c of endpoint.configuredReportings) {
await endpoint.configureReporting(c.cluster.name, [
{
// @ts-expect-error dynamic, assumed correct since already applied
attribute: c.attribute.name,
minimumReportInterval: c.minimumReportInterval,
maximumReportInterval: c.maximumReportInterval,
Expand Down
24 changes: 14 additions & 10 deletions src/devices/bacchus.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type {RawClusterAttributes} from "zigbee-herdsman/dist/controller/tstype";
import type {TPartialClusterAttributes} from "zigbee-herdsman/dist/zspec/zcl/definition/clusters-types";
import {access as ea} from "../lib/exposes";
import * as m from "../lib/modernExtend";
import * as reporting from "../lib/reporting";
import type {Configure, DefinitionWithExtend, Fz, ModernExtend, Tz} from "../lib/types";

import {assertNumber, getEndpointName, isString, precisionRound, validateValue} from "../lib/utils";

const defaultReporting = {min: 0, max: 3600, change: 0};
const electicityReporting = {min: 0, max: 30, change: 1};
const defaultReportingOnOff = {min: 0, max: 3600, change: 0, attribute: "onOff"};
const defaultReportingOOS = {min: 0, max: 3600, change: 0, attribute: "outOfService"};
const defaultReportingOnOff = {min: 0, max: 3600, change: 0, attribute: "onOff" as const};
const defaultReportingOOS = {min: 0, max: 3600, change: 0, attribute: "outOfService" as const};

const time_to_str_min = (time: number) => {
const date = new Date(null);
Expand All @@ -20,7 +21,7 @@ const str_min_to_time = (strMin: string) => {
return Number(strMin.substring(0, 2)) * 60 * 60 + Number(strMin.substring(3, 5)) * 60;
};

function timeHHMM(args: m.TextArgs): ModernExtend {
function timeHHMM(args: m.TextArgs<"genTime">): ModernExtend {
const {name, cluster, attribute, zigbeeCommandOptions, endpointName} = args;
const attributeKey = isString(attribute) ? attribute : attribute.ID;
const access = ea[args.access ?? "ALL"];
Expand All @@ -45,7 +46,9 @@ function timeHHMM(args: m.TextArgs): ModernExtend {
access & ea.SET
? async (entity, key, value, meta) => {
const value_str = str_min_to_time(value.toString());
const payload = isString(attribute) ? {[attribute]: value_str} : {[attribute.ID]: {value_str, type: attribute.type}};
const payload: TPartialClusterAttributes<"genTime"> | RawClusterAttributes = isString(attribute)
? {[attribute]: value_str}
: {[attribute.ID]: {value: value_str, type: attribute.type}};
await m.determineEndpoint(entity, meta, cluster).write(cluster, payload, zigbeeCommandOptions);
return {state: {[key]: value}};
}
Expand All @@ -65,11 +68,10 @@ function timeHHMM(args: m.TextArgs): ModernExtend {
return {...mExtend, fromZigbee, toZigbee, configure, isModernExtend: true};
}

function binaryWithOnOffCommand(args: m.BinaryArgs): ModernExtend {
function binaryWithOnOffCommand(args: m.BinaryArgs<"genOnOff", undefined>): ModernExtend {
const {name, cluster, attribute, zigbeeCommandOptions, endpointName, reporting} = args;
const attributeKey = isString(attribute) ? attribute : attribute.ID;
const access = ea[args.access ?? "ALL"];

const mExtend = m.binary(args);

const toZigbee: Tz.Converter[] = [
Expand All @@ -80,7 +82,9 @@ function binaryWithOnOffCommand(args: m.BinaryArgs): ModernExtend {
? async (entity, key, value, meta) => {
const state = isString(meta.message[key]) ? meta.message[key].toLowerCase() : null;
validateValue(state, ["toggle", "off", "on"]);
await m.determineEndpoint(entity, meta, cluster).command(cluster, state, {}, zigbeeCommandOptions);
await m
.determineEndpoint(entity, meta, cluster)
.command(cluster, state as "toggle" | "off" | "on", {}, zigbeeCommandOptions);
await m.determineEndpoint(entity, meta, cluster).read(cluster, [attributeKey], zigbeeCommandOptions);
return {state: {[key]: value}};
}
Expand All @@ -102,7 +106,7 @@ function binaryWithOnOffCommand(args: m.BinaryArgs): ModernExtend {
return {...mExtend, toZigbee, configure, isModernExtend: true};
}

function energy(args: m.NumericArgs): ModernExtend {
function energy(args: m.NumericArgs<"seMetering">): ModernExtend {
const {name, cluster, attribute, zigbeeCommandOptions, reporting, scale, precision, endpointNames} = args;
const attributeKey = isString(attribute) ? attribute : attribute.ID;
const access = ea[args.access ?? "ALL"];
Expand Down Expand Up @@ -142,7 +146,7 @@ function energy(args: m.NumericArgs): ModernExtend {
}
assertNumber(payloadValue);
if (precision != null) payloadValue = precisionRound(payloadValue, precision);
const payload = isString(attribute)
const payload: TPartialClusterAttributes<"genTime"> | RawClusterAttributes = isString(attribute)
? {[attribute]: payloadValue}
: {[attribute.ID]: {value: payloadValue, type: attribute.type}};
await m.determineEndpoint(entity, meta, cluster).write(cluster, payload, zigbeeCommandOptions);
Expand Down
Loading
Loading