diff --git a/src/devices/eglo.ts b/src/devices/eglo.ts index 9aaf1491160e9..8ded4350c5ba3 100644 --- a/src/devices/eglo.ts +++ b/src/devices/eglo.ts @@ -269,8 +269,8 @@ export const definitions: DefinitionWithExtend[] = [ "recall_2", "recall_2_long", ]), - e.numeric("action_group", ea.STATE), - e.numeric("action_level", ea.STATE), + e.action_group(), + e.action_level(), e.numeric("action_color_temperature", ea.STATE), ], }, diff --git a/src/devices/iluminize.ts b/src/devices/iluminize.ts index 9c0bcb325d399..b5539435d99b2 100644 --- a/src/devices/iluminize.ts +++ b/src/devices/iluminize.ts @@ -6,7 +6,6 @@ import * as sunricher from "../lib/sunricher"; import type {DefinitionWithExtend} from "../lib/types"; const e = exposes.presets; -const ea = exposes.access; export const definitions: DefinitionWithExtend[] = [ { @@ -247,10 +246,10 @@ export const definitions: DefinitionWithExtend[] = [ "enhanced_move_to_hue_and_saturation", "hue_stop", ]), - e.numeric("action_group", ea.STATE).withDescription("Shows the zigbee2mqtt group bound to the active data point EP(1-4)."), - e.numeric("action_transition_time", ea.STATE), - e.numeric("action_step_size", ea.STATE), - e.numeric("action_rate", ea.STATE), + e.action_group(), + e.action_step_size(), + e.action_transition_time(), + e.action_rate(), ], toZigbee: [], meta: {multiEndpoint: true}, diff --git a/src/devices/ls.ts b/src/devices/ls.ts index bffd62e3e0edd..f1b6dc451986c 100644 --- a/src/devices/ls.ts +++ b/src/devices/ls.ts @@ -34,7 +34,7 @@ const fzLocal = { const lsModernExtend = { groupIdExpose(): ModernExtend { const result: ModernExtend = { - exposes: [e.numeric("action_group", ea.STATE).withDescription("Group where the action was triggered on")], + exposes: [e.action_group()], isModernExtend: true, }; diff --git a/src/devices/shelly.ts b/src/devices/shelly.ts index 3805ca3130fd6..f31acc2407e27 100644 --- a/src/devices/shelly.ts +++ b/src/devices/shelly.ts @@ -2123,12 +2123,6 @@ export const definitions: DefinitionWithExtend[] = [ model: "SBRC-005B-B", vendor: "Shelly", description: "BLU Remote Control ZB", - exposes: [ - e.action(["on", "off", "brightness_step_up", "brightness_step_down"]), - e.numeric("action_group", ea.STATE).withDescription("Group ID associated with the action command."), - e.numeric("action_step_size", ea.STATE).withDescription("Step size value used for brightness step actions."), - e.numeric("action_transition_time", ea.STATE).withDescription("Transition time in seconds for the action."), - ], extend: [ m.battery(), m.commandsOnOff({commands: ["on", "off"]}), diff --git a/src/devices/tuya.ts b/src/devices/tuya.ts index 52085be8e49d6..a151d110b18c8 100644 --- a/src/devices/tuya.ts +++ b/src/devices/tuya.ts @@ -13507,10 +13507,10 @@ export const definitions: DefinitionWithExtend[] = [ "rotate_right", ]), e.numeric("action_brightness_delta", ea.STATE).withValueMin(-255).withValueMax(255), - e.numeric("action_step_size", ea.STATE).withValueMin(0).withValueMax(255), e.numeric("action_color_temperature_delta", ea.STATE).withValueMin(-65535).withValueMax(65535), - e.numeric("action_transition_time", ea.STATE).withUnit("s"), - e.numeric("action_rate", ea.STATE).withValueMin(0).withValueMax(255), + e.action_step_size(), + e.action_transition_time(), + e.action_rate(), e.battery(), e .enum("operation_mode", ea.ALL, ["command", "event"]) @@ -23882,7 +23882,7 @@ export const definitions: DefinitionWithExtend[] = [ .withValueMax(4) .withCategory("diagnostic") .withDescription("Button number from last action"), - e.numeric("action_group", ea.STATE).withCategory("diagnostic").withDescription("Group ID from last action"), + e.action_group(), e.enum("bind_all_scene", ea.SET, ["bind"]).withCategory("config").withDescription("Bind all buttons to Scene mode (red LED)"), e.enum("bind_all_light", ea.SET, ["bind"]).withCategory("config").withDescription("Bind all buttons to Light mode (green LED)"), e.enum("bind_all_curtain", ea.SET, ["bind"]).withCategory("config").withDescription("Bind all buttons to Curtain mode (blue LED)"), diff --git a/src/index.ts b/src/index.ts index b152fa5864fab..aad181b2abdb2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -355,6 +355,25 @@ function processExtensions(definition: DefinitionWithExtend): Definition { allExposes.push(exposesLib.presets.action(uniqueActions)); } + // De-duplicate and sort action parameters + const actionParamOrder = ["action_group", "action_level", "action_step_size", "action_transition_time", "action_rate"]; + const actionParamExposes = new Map(); + + for (const e of allExposes) { + if (typeof e !== "function" && actionParamOrder.includes(e.name)) { + actionParamExposes.set(e.name, e); + } + } + + allExposes = allExposes.filter((expose) => !actionParamOrder.includes(expose.name)); + + for (const p of actionParamOrder) { + const e = actionParamExposes.get(p); + if (e) { + allExposes.push(e); + } + } + let configure: Configure | undefined; if (configures.length !== 0) { diff --git a/src/lib/exposes.ts b/src/lib/exposes.ts index e984fbfe66f17..87a474c72dcfc 100644 --- a/src/lib/exposes.ts +++ b/src/lib/exposes.ts @@ -967,7 +967,42 @@ export const presets = { new Enum("action", access.STATE, values).withDescription("Triggered action (e.g. a button click)").withCategory("diagnostic"), action_duration: () => new Numeric("action_duration", access.STATE).withUnit("s").withDescription("Triggered action duration in seconds").withCategory("diagnostic"), - action_group: () => new Numeric("action_group", access.STATE).withDescription("Group where the action was triggered on"), + action_group: () => + new Numeric("action_group", access.STATE) + .withDescription("Target group of the action") + .withValueMin(0) + .withValueMax(65535) + .withValueStep(1) + .withCategory("diagnostic"), + action_level: () => + new Numeric("action_level", access.STATE) + .withDescription("Target brightness of Move to level command") + .withValueMin(0) + .withValueMax(255) + .withValueStep(1) + .withCategory("diagnostic"), + action_rate: () => + new Numeric("action_rate", access.STATE) + .withDescription("Rate parameter of brightness/color Move commands") + .withValueMin(0) + .withValueMax(255) + .withValueStep(1) + .withCategory("diagnostic"), + action_step_size: () => + new Numeric("action_step_size", access.STATE) + .withDescription("Step size parameter of brightness/color Step commands") + .withValueMin(0) + .withValueMax(255) + .withValueStep(1) + .withCategory("diagnostic"), + action_transition_time: () => + new Numeric("action_transition_time", access.STATE) + .withDescription("Transition parameter of level control commands") + .withValueMin(0) + .withValueMax(6553.5) + .withValueStep(0.1) + .withUnit("s") + .withCategory("diagnostic"), angle: (name: string) => new Numeric(name, access.STATE).withValueMin(-360).withValueMax(360).withUnit("°"), angle_axis: (name: string) => new Numeric(name, access.STATE).withValueMin(-90).withValueMax(90).withUnit("°"), aqi: () => new Numeric("aqi", access.STATE).withDescription("Air quality index"), diff --git a/src/lib/modernExtend.ts b/src/lib/modernExtend.ts index 470c9f3d2e54b..e0d115e68934d 100644 --- a/src/lib/modernExtend.ts +++ b/src/lib/modernExtend.ts @@ -654,7 +654,7 @@ export function commandsOnOff(args: CommandsOnOffArgs = {}): ModernExtend { if (endpointNames) { actions = commands.flatMap((c) => endpointNames.map((e) => `${c}_${e}`)); } - const exposes: Expose[] = [e.enum("action", ea.STATE, actions).withDescription("Triggered action (e.g. a button click)")]; + const exposes: Expose[] = [e.action(actions), e.action_group()]; const actionPayloadLookup: KeyValueString = { commandOn: "on", @@ -1365,7 +1365,7 @@ export interface CommandsLevelCtrl { export function commandsLevelCtrl(args: CommandsLevelCtrl = {}): ModernExtend { const { commands = [ - "brightness_move_to_level", + "brightness_move_to_level", // with on off "parameter" for all "brightness_move_up", "brightness_move_down", "brightness_step_up", @@ -1379,9 +1379,21 @@ export function commandsLevelCtrl(args: CommandsLevelCtrl = {}): ModernExtend { if (endpointNames) { actions = commands.flatMap((c) => endpointNames.map((e) => `${c}_${e}`)); } - const exposes: Expose[] = [ - e.enum("action", ea.STATE, actions).withDescription("Triggered action (e.g. a button click)").withCategory("diagnostic"), - ]; + const exposes: Expose[] = [e.action(actions), e.action_group()]; + + if (commands.includes("brightness_move_to_level")) { + exposes.push(e.action_level()); + exposes.push(e.action_transition_time()); + } + + if (commands.includes("brightness_step_up") || commands.includes("brightness_step_down")) { + exposes.push(e.action_step_size()); + exposes.push(e.action_transition_time()); + } + + if (commands.includes("brightness_move_up") || commands.includes("brightness_move_down")) { + exposes.push(e.action_rate()); + } const fromZigbee = [fz.command_move_to_level, fz.command_move, fz.command_step, fz.command_stop]; @@ -1439,6 +1451,7 @@ export function commandsColorCtrl(args: CommandsColorCtrl = {}): ModernExtend { "move_to_saturation", "move_to_hue", "stop_move_step", + // TODO: "move_to_color", "move_to_color_temp", "color_step", more... ], bind = true, endpointNames = undefined, @@ -1447,9 +1460,40 @@ export function commandsColorCtrl(args: CommandsColorCtrl = {}): ModernExtend { if (endpointNames) { actions = commands.flatMap((c) => endpointNames.map((e) => `${c}_${e}`)); } - const exposes: Expose[] = [ - e.enum("action", ea.STATE, actions).withDescription("Triggered action (e.g. a button click)").withCategory("diagnostic"), - ]; + const exposes: Expose[] = [e.action(actions), e.action_group()]; + + // TODO: e.action_hue, e.action_enhanced_hue, e.action_direction, e.action_saturation, + // e.action_color, e.action_stepx, e.action_stepy, e.action_colortemp, e.action_minimum, e.action_maximum etc. + if ( + commands.includes("enhanced_move_to_hue_and_saturation") || + commands.includes("move_to_hue_and_saturation") || + commands.includes("move_to_saturation") || + commands.includes("move_to_hue") + ) { + exposes.push(e.action_transition_time()); + } + + if ( + commands.includes("color_temperature_step_up") || + commands.includes("color_temperature_step_down") || + commands.includes("color_hue_step_up") || + commands.includes("color_hue_step_down") || + commands.includes("color_saturation_step_up") || + commands.includes("color_saturation_step_down") + ) { + exposes.push(e.action_step_size()); + exposes.push(e.action_transition_time()); + } + + if ( + commands.includes("color_temperature_move_up") || + commands.includes("color_temperature_move_down") || + commands.includes("color_temperature_move") || + commands.includes("color_move") || + commands.includes("hue_move") + ) { + exposes.push(e.action_rate()); + } const fromZigbee = [ fz.command_move_color_temperature, @@ -1629,9 +1673,7 @@ export function commandsWindowCovering(args: CommandsWindowCoveringArgs = {}): M if (endpointNames) { actions = commands.flatMap((c) => endpointNames.map((e) => `${c}_${e}`)); } - const exposes: Expose[] = [ - e.enum("action", ea.STATE, actions).withDescription("Triggered action (e.g. a button click)").withCategory("diagnostic"), - ]; + const exposes: Expose[] = [e.action(actions), e.action_group()]; const actionPayloadLookup: KeyValueString = { commandUpOpen: "open", @@ -2572,7 +2614,7 @@ export function commandsScenes(args: CommandsScenesArgs = {}) { if (endpointNames) { actions = commands.flatMap((c) => endpointNames.map((e) => `${c}_${e}`)); } - const exposesArray = [e.enum("action", ea.STATE, actions).withDescription("Triggered scene action (e.g. recall a scene)")]; + const exposesArray = [e.action(actions), e.action_group()]; const actionPayloadLookup: {[key: string]: string} = { commandRecall: "recall", @@ -3037,7 +3079,7 @@ export function actionEnumLookup< let actions = Object.keys(lookup).flatMap((a) => (args.endpointNames ? args.endpointNames.map((e) => `${a}_${e}`) : [a])); // allows direct external input to be used by other extends in the same device if (args.extraActions) actions = actions.concat(args.extraActions); - const expose = e.enum("action", ea.STATE, actions).withDescription("Triggered action (e.g. a button click)").withCategory("diagnostic"); + const expose = e.action(actions); const fromZigbee = [ { @@ -3061,7 +3103,7 @@ export function actionEnumLookup< } satisfies Fz.Converter, ]; - return {exposes: [expose], fromZigbee, isModernExtend: true}; + return {exposes: [expose, e.action_group()], fromZigbee, isModernExtend: true}; } export interface QuirkAddEndpointClusterArgs {