I finally had success by using zigbee2mqtt. I followed this youtube video and used this external convertor. Here are the steps I did.
Setup:
- Uninstall ZHA
- Install and start Zigbee2mqtt
- Start Zigbee2mqtt
- In Zigbee2mqtt’s Configuration tab change
port: /dev/ttyACM0
to /dev/ttyUSB0
- Install and start Mosquitto broker
- Create an non-administrator user for mqtt
- Paste the details into the Zigbee2mqtt Configuration tab under
mqtt:
e.g.:
mqtt:
user: mqtt_user
password: mqtt_pass
- Save the configuration and reload Zigbee2mqtt
- Open Zigbee2mqtt Web UI
Converter:
- If not already installed, install a code editing add-on like Studio Code Server
- In the
zigbee2mqtt
folder create a TS110E.js
file and enter the following code:
const fz = require("zigbee-herdsman-converters/converters/fromZigbee");
const tz = require("zigbee-herdsman-converters/converters/toZigbee");
const exposes = require("zigbee-herdsman-converters/lib/exposes");
const reporting = require("zigbee-herdsman-converters/lib/reporting");
const extend = require("zigbee-herdsman-converters/lib/extend");
const e = exposes.presets;
const ea = exposes.access;
const utils = require("zigbee-herdsman-converters/lib/utils");
const tuya = require("zigbee-herdsman-converters/lib/tuya");
const DataType = {
uint8: 0x20,
uint16: 0x21,
};
const fzLocal = {
ts110e_brightness: {
cluster: "genLevelCtrl",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.hasOwnProperty("61440")) {
let minBrightness = meta.state.hasOwnProperty("brightness_min")
? meta.state.brightness_min
: 0;
let maxBrightness = meta.state.hasOwnProperty("brightness_max")
? meta.state.brightness_max
: 1000;
let level = utils.mapNumberRange(
msg.data["61440"],
minBrightness,
maxBrightness,
0,
255
);
meta.logger.debug(`TS110E: FZ-Brightness level changed to: ${level}`);
return { brightness: level };
}
},
},
brightness_min: {
cluster: "genLevelCtrl",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.hasOwnProperty("64515")) {
return { brightness_min: msg.data["64515"] };
}
},
},
brightness_max: {
cluster: "genLevelCtrl",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.hasOwnProperty("64516")) {
return { brightness_max: msg.data["64516"] };
}
},
},
led_type: {
cluster: "genLevelCtrl",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const lookup1 = { 0: "led", 1: "incandescent", 2: "halogen" };
if (msg.data.hasOwnProperty("64514")) {
const property = utils.postfixWithEndpointName("led_type", msg, model);
return { [property]: lookup1[msg.data["64514"]] };
}
},
},
};
const tzLocal = {
light_onoff_brightness: {
key: ["brightness"],
options: [exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
const minBrightness = meta.state.hasOwnProperty("brightness_min")
? meta.state.brightness_min
: 0;
const maxBrightness = meta.state.hasOwnProperty("brightness_max")
? meta.state.brightness_max
: 1000;
const level = utils.mapNumberRange(
value,
0,
255,
minBrightness,
maxBrightness
);
meta.logger.debug(`TS110E: TZ-Brightness level changed to: ${level}`);
const switchState = value > 0 ? "ON" : "OFF";
// await tz.on_off.convertSet(entity, "state", switchState, meta);
await entity.command(
"genOnOff",
1,
{},
utils.getOptions(meta.mapped, entity)
);
await utils.sleep(1); // To-Think: why is this needed?
await entity.command(
"genLevelCtrl",
"moveToLevelTuya",
{ level, transtime: 0 },
utils.getOptions(meta.mapped, entity)
);
},
convertGet: async (entity, key, meta) => {
if (key === "brightness") {
await entity.read("genLevelCtrl", [61440]);
} else if (key === "state") {
await tz.on_off.convertGet(entity, key, meta);
}
},
},
ts110e_brightness_min: {
key: ["brightness_min"],
convertSet: async (entity, key, value, meta) => {
let payload = { 64515: { value: value, type: DataType.uint16 } };
await entity.write(
"genLevelCtrl",
payload,
utils.getOptions(meta.mapped, entity)
);
},
convertGet: async (entity, key, meta) => {
await entity.read("genLevelCtrl", [64515]);
},
},
ts110e_brightness_max: {
key: ["brightness_max"],
convertSet: async (entity, key, value, meta) => {
let payload = { 64516: { value: value, type: DataType.uint16 } };
await entity.write(
"genLevelCtrl",
payload,
utils.getOptions(meta.mapped, entity)
);
},
convertGet: async (entity, key, meta) => {
await entity.read("genLevelCtrl", [64516]);
},
},
ts110e_led_type: {
key: ["led_type"],
convertSet: async (entity, key, value, meta) => {
value = value.toLowerCase();
const lookup1 = { led: 0, incandescent: 1, halogen: 2 };
newValue = parseInt(lookup1[value]);
payload = { 64514: { value: newValue, type: DataType.uint8 } };
await entity.write(
"genLevelCtrl",
payload,
utils.getOptions(meta.mapped, entity)
);
},
convertGet: async (entity, key, meta) => {
await entity.read("genLevelCtrl", [64514]);
},
},
};
const definition = {
fingerprint: [{ modelID: "TS110E", manufacturerName: "_TZ3210_ngqk6jia" }],
model: "TS110E_dimmer",
vendor: "TuYa",
description: "Smart dimmer module with neutral",
fromZigbee: [
fz.on_off,
fz.moes_power_on_behavior,
fzLocal.ts110e_brightness,
fzLocal.brightness_min,
fzLocal.brightness_max,
fzLocal.led_type,
],
toZigbee: [
tz.on_off,
tz.moes_power_on_behavior,
tzLocal.light_onoff_brightness,
tzLocal.ts110e_brightness_min,
tzLocal.ts110e_brightness_max,
tzLocal.ts110e_led_type,
],
exposes: [
e.light_brightness(),
exposes.numeric("brightness_min", ea.ALL),
exposes.numeric("brightness_max", ea.ALL),
exposes
.enum("led_type", ea.ALL, ["led", "incandescent", "halogen"])
.withDescription("Controls the type led"),
exposes
.enum("power_on_behavior", ea.ALL, ["off", "previous", "on"])
.withDescription("Controls the behaviour when the device is powered on"),
],
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(1);
await extend
.light_onoff_brightness()
.configure(device, coordinatorEndpoint, logger);
await reporting.bind(endpoint, coordinatorEndpoint, [
"genOnOff",
"genLevelCtrl",
]);
await reporting.onOff(endpoint);
},
};
module.exports = definition;
- Save and restart Zigbee2mqtt
Pairing:
- Click ‘Permit join (all)’ button in the top menu
- Single press and then press and hold for 10 seconds on the light switch until the light starts flashing (you might have to do this twice)
- The light should be picked up by Zigbee2mqtt and should list the Manufacturer as ‘TuYa’
- Click the name of the light and go the Exposes tab
- Set
brightness_min
to 200
and click the refresh icon
- Set
brightness_max
to 500
and click the refresh icon
- Set
led_type
to led
and click the refresh icon
- Set
power_on_behavior
to previous
and click the refresh icon
I then use a custom card on my dashboard to control it:
title: Lights
type: entities
entities:
- type: custom:slider-entity-row
entity: light.living_room_light
name: Coving Lights