Sonoff Zigbee/Tuya Dimmer Module - Unable to dim light

I have recently had an LED rope light wired into the mains via a Zigbee dimmer module and a retractive light switch. I have a Sonoff zigbee dongle plugged into a Raspberry Pi running Home Assistant OS.

I have two automations created through the UI that successfully switch the lights on and off via a Philips zigbee dimmer remote control button press. However the dimming isn’t working via the remote. I can dim up/down the lights with the retractive switch at the wall by pressing and holding the button (the next press and hold swaps from dim up to dim down and vice versa).

The actions that I have available for the light are ‘On’, ‘Off’, ‘Toggle’, ‘Increase brightness’ and ‘Decrease brightness’, I have tried triggering increase and decrease brightness on dim down/up ‘button pressed’ and ‘continuously pressed’ but neither have worked.

The logs show that the button press is firing a dim up/down event and the UI shows the light brightness level going up and down but this is not reflected in the light itself.

Does anyone have anything else I can try?

Any progress on this? I have the same issue… :frowning:

The only progress I’ve made is that I have found this post on github, which references the same model of lights as me (TS110E _TZ3210_ngqk6jia), so I was planning on trying to set them up via zigbee2mqtt. Might be worth a try for you too?

I’m running into the same issue. I ordered this device: blakadder.com entry.
It looks exactly like yours. And same as for you, the on/off works, but the dimmer doesn’t.
I noticed that our device is Zigbee ID: "TS110E"; "_TZ3210_ngqk6jia", but the entry on blakadder shows as Zigbee ID: "TS110F"; "_TYZB01_qezuin6k". So E vs F and a different manufacturer.
Did you have any success with zigbee2mqtt?
I posted this issue on the zhaquirks repository because I’d like to use ZHA: https://github.com/zigpy/zha-device-handlers/issues/1415

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

It looks like the TS110E is supported natively now and the external convertor will break Zigbee2MQTT.

In order to fix this I went to /zigbee2mqtt/configuration.yaml and removed these lines and restarted Zigbee2MQTT, and then deleted TS110E.js:

external_convertors:
  - TS110E.js