Featured image of post Replacing the Tuya components from a Mercator Ikuü light switch

Replacing the Tuya components from a Mercator Ikuü light switch

TL;DR

  • You can replace the Tuya WB3S with an Espressif ESP-12F (or ESP-12E) and ESPHome.
  • You need to pull EN up to 3.3v, and pull GPIO15 down to ground via a 10k resistor.
  • If you’re on Linux, make sure your user is in the dialout group to write to the ESP-12.
  • Scroll to the end for the YAML.
    • Comment out the appropriate buttons for your model.
    • I’ve confirmed that SSW01G-WIFI uses button 2, and SSW02G-WIFI uses buttons 1 and 3. Presumably SSW03G-WIFI uses all of them, but I don’t have one to confirm.
    • Make sure you change light.toggle into fan.toggle if you decide to declare a button as a fan, etc.

The article

The Mercator Ikuü range of smart devices is another rebranded Tuya line. As a result, controlling the device with Home Assistant was slow due to the calls to the Tuya datacentre — and honestly I just wanted to have devices that are entirely under my control. I started by disassembling the switch and immediately saw the Tuya WB3S on the underside of the top plate.

The disassembled switch: a yellow plastic case with a green circuit board

The switch, opened up. The controlling PCB sits on the underside of the top plate.

Be careful lifting this PCB out, as there are pins connecting it to the 240v plane — make sure you lift it straight. This set of connecting pins is labelled CN1, and is responsible for connecting the controlling MCU to the 240v relays.

On the board is the Tuya WB3S, and a Holtek BS813A-1 touch-button sensor (https://www.holtek.com.tw/page/vg/BS81xA-x). It does not appear to contain a TuyaMCU chip. The same board is used for the SSW01G-WIFI, SSW02G-WIFI, and SSW03G-WIFI models — it seems the SSW04G-WIFI is a different board.

Pinout diagram of the Holtek BS813A-1 in an 8SOP-A package

The Holtek BS813A-1 touch sensor pinout.

The way this Holtek sensor works is, while a touch is sensed by the Key1 leg, it will pull Kout1 down to zero volts. This is momentary and returns to 3.3v once the touch is removed. The MCU listens for the appropriate pin to be pulled low, and triggers a relay in response.

The controller PCB, showing the WB3S module and the CN1 header with a red arrow pointing at one end

The controller board. The red arrow marks where I (incorrectly) oriented my CN1 numbering from.

The red arrow is where I (incorrectly) oriented my CN1 naming from. I’ll refer to the CN1 pins in reading order; pins 1-5 on the top row, pins 6-10 on the bottom. After writing this, I realised that this is almost certainly backwards: the bottom-right pin — what I call pin 10 — has a square base, indicating it should have been pin 1. Oh well.

Here’s the pinout of the Tuya WB3S, from https://docs.libretiny.eu/boards/wb3s/:

Pinout diagram of the Tuya WB3S module

The Tuya WB3S pinout, via LibreTiny.

Note, the grey numbers in the WB3S pinout are simply positions; the pin names are burgundy (i.e., P23 is in position 2). I can see PCB traces at positions 15, 14, 13, 12, 10, 9 (GND), and 8 (3.3v). Positions 17-22 are visibly unsoldered.

  • WB3S GND (position 9) goes to CN1 Pins 1 & 2
  • WB3S 3.3v (position 8) goes to CN1 Pin 6
  • WB3S P6 (position 7) goes to CN1 Pin 7, via resistor R7, and on to LED1
  • WB3S P7 (position 10) goes to CN1 pin 9, via resistor R14, and on to LED3
  • WB3S P8 (position 14) goes to capacitor C2 and onto CN1 pins 1 & 2
  • WB3S P9 (position 13) goes to CN1 pin 8, via resistor R13 and LED2 (empty on this model)
  • WB3S P10 (position 15) goes to resistor R2, which is empty on this model
  • WB3S P24 (position 6) goes to resistor R11, which goes on to LED4 (plate lighting)

And the inverse to cross-check myself: on CN1, I can see upper PCB traces at 1, 2, 6, 7 and 8 — plus an underside trace to 9.

  • CN1 Pin 1 goes to WB3S GND (position 9)
  • CN1 Pin 2 goes to WB3S GND (position 9)
  • CN1 Pin 6 goes to WB3S 3.3v (position 8)
  • CN1 Pin 7 goes to WB3S P6 (position 7) via resistor R7, which then goes to LED1
  • CN1 Pin 8 goes to WB3S P9 (position 13) — this also traces to R13, which is empty on this model, and appears to be for the absent middle switch LED2
  • CN1 Pin 9 goes to WB3S P7 (position 10) via resistor R14, and onward to LED3

I double-checked this by powering up the WB3S chip and checking the voltage on the pins as I hit the buttons. All seems correct so far. Before I desoldered the WB3S on the board, I connected RX and TX and dumped the Tuya firmware, and decoded this with the OpenBeken flash tool from https://github.com/openshwprojects/BK7231GUIFlashTool. Here are the interpreted results:

Device configuration, as extracted from Tuya:
- Button (channel 1) on P24 [pinout position 6]
- Relay  (channel 2) on P7  [pinout position 10]
- Button (channel 2) on P8  [pinout position 14]
- Relay  (channel 1) on P6  [pinout position 7]

Device seems to be using WB3S module, which is using BK7231T.
And the Tuya section starts, as usual, at 2023424

These results didn’t align with my original tracing, so it was really lucky I did this. Although I had correctly identified the relays in position 7 & 10, I had misunderstood how the buttons work. It was thanks to this discrepancy that I started testing the levels while touching the touch sensors, and discovered that button-presses cause the voltage to drop on P8 (position 14) and P24 (position 6). This led me to read up on how the Holtek chip works, helping me to understand what’s going on here!

So, indeed we have two buttons and two relays. The buttons are held HIGH while untouched, and momentarily drop to LOW when touch is detected. In turn, these state changes trigger toggles to the relay outputs. I can finally start writing YAML!

I’m replacing this Tuya WB3S with an Espressif ESP12-F, which is largely a perfect drop-in replacement:

ESP-12 pinout diagram

The ESP-12 pinout.

Translating from the WB3S, it appears:

  • P24 (position 6 on the WB3S) maps to GPIO12 on the ESP12
  • P7 (position 10 on the WB3S) maps to GPIO15 on the ESP12
  • P8 (position 14 on the WB3S) maps to GPIO5 on the ESP12
  • P6 (position 7 on the WB3S) maps to GPIO13 on the ESP12
  • P10 (position 15 on the WB3S) maps to RX on the ESP12
  • Additionally, EN needs to be pulled up to 3.3v, and GPIO15 needs to be pulled down to ground via a 10k resistor.

This is my first time programming an ESP12 (my previous ESPHome devices have been ESP32’s), and I did find them a little more difficult — I ended up bumping into a permissions problem, and thanks to this post I was able to resolve the error failed to execute 'open' on 'SerialPort': Failed to open serial port. The solution was either to add myself to the dialout group, or set the ACL on ttyUSB0 (only a transient workaround):

sudo setfacl -m u:USERNAME:rw /dev/ttyUSB0
sudo usermod -aG dialout USERNAME

Desoldering the SMT WB3S was enough of a challenge that I asked a friend for help, and he did the first couple for me while I waited for a hot plate to arrive from AliExpress. Then I did the third one and only slightly cooked my board.

The PCB with an ESP-12F soldered in place of the WB3S, with a resistor bodged across two pads

The ESP-12F in place, complete with the 10k pull-down bodge resistor.

Result! Not only is this 100% locally-controlled and forever disconnected from the Tuya Cloud, but the response time is ridiculously fast now. What used to be 200~2000ms response time (yes really, depending on network congestion), is now closer to 20ms (unmeasurably fast with my zero instruments).

The YAML snippet

The YAML for the one-gang, two-gang, and three-gang versions is below. Note this is partial YAML; other important parts such as enabling the API and OTA are left out, but I’ll include them below for new folks.

esp8266:
  board: esp01_1m

binary_sensor:
  # Button 1 - not present on the SSW01G
  - platform: gpio
    id: button_1
    pin:
      number: GPIO12
      inverted: true
      mode: INPUT_PULLUP
    on_press:
      then:
        - light.toggle: switch_1
    internal: True

  # Button 2 - not present on the SSW02G
  - platform: gpio
    id: button_1
    pin:
      number: RX
      inverted: true
      mode: INPUT_PULLUP
    on_press:
      then:
        - light.toggle: switch_2
    internal: True

  # Button 3 - not present on the SSW01G
  - platform: gpio
    id: button_3
    pin:
      number: GPIO5
      inverted: true
      mode: INPUT_PULLUP
    on_press:
      then:
        - light.toggle: switch_3
    internal: True

output:
  # Relay 1 - not present on the SSW01G
  - platform: gpio
    id: relay_1
    pin: GPIO13

  # Relay 2 - not present on the SSW02G
  - platform: gpio
    id: relay_2
    pin: GPIO4

  # Relay 3 - not present on the SSW01G
  - platform: gpio
    id: relay_3
    pin: GPIO15

# Expose the relays as lights and/or fans
light:
  # Light 1 - not present on the SSW01G
  - platform: binary
    name: Switch 1
    output: relay_1
    id: switch_1

  # Light 2 - not present on the SSW02G
  - platform: binary
    name: Switch 2
    output: relay_2
    id: switch_2

  # Light 3 - not present on the SSW01G
  - platform: binary
    name: Switch 3
    output: relay_3
    id: switch_3

# Example of having a fan on button 3 instead - if you do this, you must change
# the earlier matching button declaration from light.toggle to fan.toggle.
fan:
  - platform: binary
    # Button 3
    name: Ensuite Fan
    output: relay_3
    id: switch_3

That’s it! Upload that fragment into your normal ESPHome setup, and you should be in business.

In case you’re new to this, below is my standard YAML — this would go at the very top of your YAML code block within your ESPHome device declaration, above the previous stuff. This enables a variety of important bits, plus adds helpful sensors for WiFi info and uptime.

esphome:
  name: mercator-ikuu
  friendly_name: "Mercator Ikuu Smart Switch"

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: # for you to fill in

# Enable over-the-air updates; requires you to have a saved secret
ota:
  - platform: esphome
    password: !secret ota_password

# Your wifi connection details; requires you to have saved secrets
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # Enable open fallback hotspot if wifi connection fails
  ap:
    {}

# Enables a helpful web portal if wifi connection fails
captive_portal:

# Allows you to browse to the device and have a direct view of what's happening
web_server:

# Displays wifi signal strength and device uptime
sensor:
  - platform: wifi_signal
    name: Wifi Signal Strength
    update_interval: 60s
  - platform: uptime
    name: Uptime

# Displays network IP address, connected SSID and BSSID, and MAC
text_sensor:
  - platform: wifi_info
    ip_address:
      name: IP
    ssid:
      name: SSID
    bssid:
      name: BSSID
    mac_address:
      name: MAC