Skip to content
Merged
Changes from 1 commit
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
76 changes: 73 additions & 3 deletions src/devices/dresden_elektronik.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
import * as fz from "../converters/fromZigbee";
import * as tz from "../converters/toZigbee";
import * as exposes from "../lib/exposes";
import * as m from "../lib/modernExtend";
import type {DefinitionWithExtend} from "../lib/types";
import type {DefinitionWithExtend, Expose, Tz, Zh} from "../lib/types";

const e = exposes.presets;

const tzLocal = {
flsm_color_hs: {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use

export const light_color: Tz.Converter = {
instead?

Copy link
Contributor Author

@fst-dresden-elektronik fst-dresden-elektronik Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because when using tz.light_color the device did not change color. I had to build this custom solution for the device to accept color changes.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think because it doesn't support x/y, can you try with just h/s: m.light({endpointNames: ["l1", "l2", "l3", "l4", "l5"], color: {modes: ["hs"]}})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The challenge with this product is that the endpoints are dynamic. The device has DIP switches that configure which endpoint is exposed. As far as I understand, the extend field and the m.light function are static, so I had to work around this using the exposes and configure fields. In the end, this worked perfectly and corrected the previous “simple” implementation, which was incomplete (it only exposed five static lights, but the actual behavior depends on the DIP switch configuration).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is a different way to implement this? I am new to zigbee2MQTT.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we can identify using fingerprint (so a different definition is used for every mode): example:

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi everybody, the main challange are the configurations which can be made in the web based FLS-M Config Builder. This tool generates a JSON which is uploaded to the device.

From my understanding the finger prints would only work for a static set of configurations like in the Gledopto case? The FLS-M also has fixed configurations which can be set by the dip switches, but isn't limited to these:

We'd need finger prints for completely arbitrary configurations created with above tool.

This is why the PR uses the actual simple descriptors to create the exposed representation in z2m. (I think the color capabilities should also be considered to differentiate between color/color temperatur lights).

So the question is what is the best way to support arbitrary light endpoints when we don't know the finger print a priori?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that can be done through the dynamic expose (which is already in this PR). Can you provide the debug log when changing the color when using light_color instead of flsm_color_hs? I'm curious why it doesn't work.

See this on how to enable debug logging.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why the custom converter was used is that the FLS-M advertises support for enhanced hue, but does not actually support it. The logs also confirm this, as the standard tz.light_color uses enhancedMoveToHueAndSaturation, whereas the custom solution uses moveToHueAndSaturation.
I looked through some other configurations and found the meta tag with the supportsEnhancedHue attribute. I then applied it to the configuration to work around the need for the custom converter. I made a new commit with the updated configuration.
Additionally, here are the logs for the two converters.

tz.light_color:

[2026-01-14 09:24:45] debug:    z2m:mqtt: Received MQTT message on 'zigbee2mqtt/0x404ccafffe42963c/set' with data '{"color_light_1":{"hue":34,"saturation":67}}'
[2026-01-14 09:24:45] debug:    z2m: Publishing 'set' 'color' to 'FLS-M'
[2026-01-14 09:24:45] debug:    zh:controller:endpoint: ZCL command 0x404ccafffe42963c/1 lightingColorCtrl.enhancedMoveToHueAndSaturation({"transtime":0,"enhancehue":6189,"saturation":170,"optionsMask":0,"optionsOverride":0}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":false,"direction":0,"reservedBits":0,"writeUndiv":false})
[2026-01-14 09:24:45] debug:    zh:deconz: ZCL request sent with transactionSequenceNumber.: 11
[2026-01-14 09:24:45] debug:    zh:deconz: command.response: undefined, zcl.disableDefaultResponse: false, z2m.disableResponse: false, request.timeout: 10000
[2026-01-14 09:24:45] debug:    zh:deconz:driver: Request APS-DATA.request: dest: 0x496f EP:1 seq: 105 requestId: 13
[2026-01-14 09:24:45] debug:    zh:deconz:frameparser: Response APS-DATA.request APS request id: 13 status: Success
[2026-01-14 09:24:45] debug:    zh:deconz:driver: Request APS-DATA.confirm seq: 106
[2026-01-14 09:24:45] debug:    zh:deconz:frameparser: APS-DATA.confirm  destAddr: 0x496f APS request id: 13 confirm status: Success 0x00
[2026-01-14 09:24:45] debug:    zh:deconz:driver: Request APS-DATA.indication seq: 107
[2026-01-14 09:24:45] debug:    zh:deconz:frameparser: Response APS-DATA.indication seq: 107 srcAddr: 0x496f destAddr: 0x0 profile id: 0x0104 cluster id: 0x0300 lqi: 255
[2026-01-14 09:24:45] debug:    zh:controller: Received payload: clusterID=768, address=18799, groupID=0, endpoint=1, destinationEndpoint=1, wasBroadcast=false, linkQuality=255, frame={"header":{"frameControl":{"frameType":0,"manufacturerSpecific":false,"direction":1,"disableDefaultResponse":true,"reservedBits":0},"transactionSequenceNumber":11,"commandIdentifier":11},"payload":{"cmdId":67,"statusCode":129},"command":{"ID":11,"name":"defaultRsp","parameters":[{"name":"cmdId","type":32},{"name":"statusCode","type":32}]}}
[2026-01-14 09:24:45] debug:    zh:controller: Failure default response from '18799': clusterID=768 cmdId=67 status=UNSUP_COMMAND
[2026-01-14 09:24:45] info:     z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/FLS-M', payload '{"brightness":34,"brightness_light_1":90,"color":{"hue":34,"saturation":57},"color_light_1":{"hue":34,"saturation":82},"color_mode":"hs","color_mode_light_1":"hs","color_temp":280,"color_temp_light_1":357,"last_seen":"2026-01-14T08:24:45.909Z","linkquality":255,"state":"ON","state_light_1":"ON","update":{"installed_version":16843014,"latest_version":16843014,"state":"idle"}}'
[2026-01-14 09:24:45] debug:    zh:deconz: Response received transactionSequenceNumber: 11
[2026-01-14 09:24:45] debug:    zhc:light: Missing colorTempPhysicalMin and/or colorTempPhysicalMax for endpoint 0x404ccafffe42963c!
[2026-01-14 09:24:45] info:     z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/FLS-M', payload '{"brightness":34,"brightness_light_1":90,"color":{"hue":34,"saturation":57},"color_light_1":{"hue":34,"saturation":67},"color_mode":"hs","color_mode_light_1":"hs","color_temp":280,"color_temp_light_1":309,"last_seen":"2026-01-14T08:24:45.909Z","linkquality":255,"state":"ON","state_light_1":"ON","update":{"installed_version":16843014,"latest_version":16843014,"state":"idle"}}'

tzLocal.flsm_color_hs:

[2026-01-14 09:30:28] debug:    z2m:mqtt: Received MQTT message on 'zigbee2mqtt/0x404ccafffe42963c/set' with data '{"color_light_1":{"hue":34,"saturation":54}}'
[2026-01-14 09:30:28] debug:    z2m: Publishing 'set' 'color' to 'FLS-M'
[2026-01-14 09:30:28] debug:    zh:controller:endpoint: ZCL command 0x404ccafffe42963c/1 lightingColorCtrl.moveToHueAndSaturation({"hue":24,"saturation":137,"transtime":0}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":false,"direction":0,"reservedBits":0,"writeUndiv":false})
[2026-01-14 09:30:28] debug:    zh:deconz: ZCL request sent with transactionSequenceNumber.: 2
[2026-01-14 09:30:28] debug:    zh:deconz: command.response: undefined, zcl.disableDefaultResponse: false, z2m.disableResponse: false, request.timeout: 10000
[2026-01-14 09:30:28] debug:    zh:deconz:driver: Request APS-DATA.request: dest: 0x496f EP:1 seq: 1 requestId: 3
[2026-01-14 09:30:28] debug:    zh:deconz:frameparser: Response APS-DATA.request APS request id: 3 status: Success
[2026-01-14 09:30:28] debug:    zh:deconz:driver: Request APS-DATA.confirm seq: 3
[2026-01-14 09:30:28] debug:    zh:deconz:frameparser: APS-DATA.confirm  destAddr: 0x496f APS request id: 3 confirm status: Success 0x00
[2026-01-14 09:30:28] debug:    zh:deconz:driver: Request APS-DATA.indication seq: 4
[2026-01-14 09:30:28] debug:    zh:deconz:frameparser: Response APS-DATA.indication seq: 4 srcAddr: 0x496f destAddr: 0x0 profile id: 0x0104 cluster id: 0x0300 lqi: 255
[2026-01-14 09:30:28] debug:    zh:controller: Received payload: clusterID=768, address=18799, groupID=0, endpoint=1, destinationEndpoint=1, wasBroadcast=false, linkQuality=255, frame={"header":{"frameControl":{"frameType":0,"manufacturerSpecific":false,"direction":1,"disableDefaultResponse":true,"reservedBits":0},"transactionSequenceNumber":2,"commandIdentifier":11},"payload":{"cmdId":6,"statusCode":0},"command":{"ID":11,"name":"defaultRsp","parameters":[{"name":"cmdId","type":32},{"name":"statusCode","type":32}]}}
[2026-01-14 09:30:28] info:     z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/FLS-M', payload '{"brightness":34,"brightness_light_1":90,"color":{"hue":34,"saturation":57},"color_light_1":{"hue":34,"saturation":67},"color_mode":"hs","color_mode_light_1":"hs","color_temp":280,"color_temp_light_1":309,"last_seen":"2026-01-14T08:30:28.752Z","linkquality":255,"state":"ON","state_light_1":"ON","update":{"installed_version":16843014,"latest_version":16843014,"state":"idle"}}'
[2026-01-14 09:30:28] debug:    zh:deconz: Response received transactionSequenceNumber: 2
[2026-01-14 09:30:31] debug:    zh:deconz:driver: Request APS-DATA.indication seq: 35
[2026-01-14 09:30:31] debug:    zh:deconz:frameparser: Response APS-DATA.indication seq: 35 srcAddr: 0x496f destAddr: 0x0 profile id: 0x0104 cluster id: 0x0300 lqi: 255
[2026-01-14 09:30:31] debug:    zh:controller: Received payload: clusterID=768, address=18799, groupID=0, endpoint=1, destinationEndpoint=1, wasBroadcast=false, linkQuality=255, frame={"header":{"frameControl":{"frameType":0,"manufacturerSpecific":false,"direction":1,"disableDefaultResponse":false,"reservedBits":0},"transactionSequenceNumber":26,"commandIdentifier":10},"payload":[{"attrId":0,"dataType":32,"attrData":24},{"attrId":1,"dataType":32,"attrData":145}],"command":{"ID":10,"name":"report","parameters":[{"name":"attrId","type":33},{"name":"dataType","type":32},{"name":"attrData","type":1000}]}}
[2026-01-14 09:30:31] debug:    zh:controller:endpoint: ZCL command 0x404ccafffe42963c/1 lightingColorCtrl.defaultRsp({"cmdId":10,"statusCode":0}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"transactionSequenceNumber":26,"writeUndiv":false})
[2026-01-14 09:30:31] debug:    zh:deconz: ZCL request sent with transactionSequenceNumber.: 26
[2026-01-14 09:30:31] debug:    zh:deconz: command.response: undefined, zcl.disableDefaultResponse: true, z2m.disableResponse: false, request.timeout: 10000
[2026-01-14 09:30:31] debug:    z2m: Received Zigbee message from 'FLS-M', type 'attributeReport', cluster 'lightingColorCtrl', data '{"currentHue":24,"currentSaturation":145}' from endpoint 1 with groupID 0
[2026-01-14 09:30:31] debug:    zhc:light: Missing colorTempPhysicalMin and/or colorTempPhysicalMax for endpoint 0x404ccafffe42963c!
[2026-01-14 09:30:31] info:     z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/FLS-M', payload '{"brightness":34,"brightness_light_1":90,"color":{"hue":34,"saturation":57},"color_light_1":{"hue":34,"saturation":67},"color_mode":"hs","color_mode_light_1":"hs","color_temp":280,"color_temp_light_1":309,"last_seen":"2026-01-14T08:30:31.741Z","linkquality":255,"state":"ON","state_light_1":"ON","update":{"installed_version":16843014,"latest_version":16843014,"state":"idle"}}'
[2026-01-14 09:30:31] debug:    zh:deconz:driver: Request APS-DATA.request: dest: 0x496f EP:1 seq: 36 requestId: 4
[2026-01-14 09:30:31] debug:    zh:deconz:frameparser: Response APS-DATA.request APS request id: 4 status: Success
[2026-01-14 09:30:31] debug:    zh:deconz:driver: Request APS-DATA.confirm seq: 38
[2026-01-14 09:30:31] debug:    zh:deconz:frameparser: APS-DATA.confirm  destAddr: 0x496f APS request id: 4 confirm status: Success 0x00
[2026-01-14 09:30:33] debug:    zh:deconz:driver: Request APS-DATA.indication seq: 58
[2026-01-14 09:30:33] debug:    zh:deconz:frameparser: Response APS-DATA.indication seq: 58 srcAddr: 0x496f destAddr: 0x0 profile id: 0x0104 cluster id: 0x0300 lqi: 255
[2026-01-14 09:30:33] debug:    zh:controller: Received payload: clusterID=768, address=18799, groupID=0, endpoint=1, destinationEndpoint=1, wasBroadcast=false, linkQuality=255, frame={"header":{"frameControl":{"frameType":0,"manufacturerSpecific":false,"direction":1,"disableDefaultResponse":false,"reservedBits":0},"transactionSequenceNumber":27,"commandIdentifier":10},"payload":[{"attrId":0,"dataType":32,"attrData":24},{"attrId":1,"dataType":32,"attrData":137}],"command":{"ID":10,"name":"report","parameters":[{"name":"attrId","type":33},{"name":"dataType","type":32},{"name":"attrData","type":1000}]}}
[2026-01-14 09:30:33] debug:    zh:controller:endpoint: ZCL command 0x404ccafffe42963c/1 lightingColorCtrl.defaultRsp({"cmdId":10,"statusCode":0}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"transactionSequenceNumber":27,"writeUndiv":false})
[2026-01-14 09:30:33] debug:    zh:deconz: ZCL request sent with transactionSequenceNumber.: 27
[2026-01-14 09:30:33] debug:    zh:deconz: command.response: undefined, zcl.disableDefaultResponse: true, z2m.disableResponse: false, request.timeout: 10000
[2026-01-14 09:30:33] debug:    z2m: Received Zigbee message from 'FLS-M', type 'attributeReport', cluster 'lightingColorCtrl', data '{"currentHue":24,"currentSaturation":137}' from endpoint 1 with groupID 0
[2026-01-14 09:30:33] debug:    zhc:light: Missing colorTempPhysicalMin and/or colorTempPhysicalMax for endpoint 0x404ccafffe42963c!
[2026-01-14 09:30:33] info:     z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/FLS-M', payload '{"brightness":34,"brightness_light_1":90,"color":{"hue":34,"saturation":54},"color_light_1":{"hue":34,"saturation":67},"color_mode":"hs","color_mode_light_1":"hs","color_temp":272,"color_temp_light_1":309,"last_seen":"2026-01-14T08:30:33.742Z","linkquality":255,"state":"ON","state_light_1":"ON","update":{"installed_version":16843014,"latest_version":16843014,"state":"idle"}}'
[2026-01-14 09:30:33] debug:    zh:deconz:driver: Request APS-DATA.request: dest: 0x496f EP:1 seq: 59 requestId: 5
[2026-01-14 09:30:33] debug:    zh:deconz:frameparser: Response APS-DATA.request APS request id: 5 status: Success
[2026-01-14 09:30:33] debug:    zh:deconz:driver: Request APS-DATA.confirm seq: 61
[2026-01-14 09:30:33] debug:    zh:deconz:frameparser: APS-DATA.confirm  destAddr: 0x496f APS request id: 5 confirm status: Success 0x00

key: ["color"],
convertSet: async (entity: Zh.Endpoint | Zh.Group, key: string, value: unknown, meta: Tz.Meta): Promise<void> => {
if (typeof value !== "object" || value === null) {
return;
}

const v = value as {hue?: number; saturation?: number};

if (typeof v.hue !== "number" || typeof v.saturation !== "number") {
return;
}

const hue = Math.max(0, Math.min(254, Math.round((v.hue / 360) * 254)));
const saturation = Math.max(0, Math.min(254, Math.round((v.saturation / 100) * 254)));

await (entity as Zh.Endpoint).command("lightingColorCtrl", "moveToHueAndSaturation", {hue, saturation, transtime: 0}, {});
},
},
};

export const definitions: DefinitionWithExtend[] = [
{
Expand All @@ -17,8 +44,51 @@ export const definitions: DefinitionWithExtend[] = [
zigbeeModel: ["FLS-M"],
model: "FLS-M",
vendor: "Dresden Elektronik",
description: "Universal led controller",
extend: [m.deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4, l5: 5}}), m.light({endpointNames: ["l1", "l2", "l3", "l4", "l5"]})],
description: "Universal LED controller (dynamic endpoints)",
ota: true,
endpoint: (device) => {
const result: {[name: string]: number} = {};

for (const ep of device.endpoints) {
if (ep.inputClusters.includes(0x0006) || ep.inputClusters.includes(0x0008) || ep.inputClusters.includes(0x0300)) {
result[`light_${ep.ID}`] = ep.ID;
}
}

return result;
},
exposes: (device): Expose[] => {
const result: Expose[] = [];

if (!("endpoints" in device)) return result;

for (const ep of device.endpoints) {
const name = `light_${ep.ID}`;

if (ep.inputClusters.includes(0x0300)) {
result.push(e.light_colorhs().withBrightness().withColorTemp([140, 625]).withEndpoint(name));
} else if (ep.inputClusters.includes(0x0008)) {
result.push(e.light_brightness().withEndpoint(name));
}
}

return result;
},

configure: async (device, coordinatorEndpoint, logger) => {
for (const ep of device.endpoints) {
if (ep.inputClusters.includes(0x0300)) {
await ep.bind("lightingColorCtrl", coordinatorEndpoint);
} else if (ep.inputClusters.includes(0x0008)) {
await ep.bind("genLevelCtrl", coordinatorEndpoint);
} else if (ep.inputClusters.includes(0x0006)) {
await ep.bind("genOnOff", coordinatorEndpoint);
}
}
},

fromZigbee: [fz.on_off, fz.brightness, fz.color_colortemp],
toZigbee: [tz.on_off, tz.light_onoff_brightness, tzLocal.flsm_color_hs, tz.light_colortemp],
},
{
zigbeeModel: ["FLS-CT"],
Expand Down