devices.esphome.io

Tongou TO-Q-SYS-JWT DIN Rail Switch and Meter

Tongou TO-Q-SYS-JWT DIN Rail Switch and Meter

Device Type: switch
Electrical Standard: global
Board: bk72xx
Difficulty: Disassembly required, 3/5

Product Image

Device Overview

The device is a DIN-rail power meter with a relay inside. Some aspects to consider:

  • This is not an IEC/EN 60898 compliant circuit breaker. The relay inside is able to break the circuit and is rated for 60A (markings on the relay, 50A rating on the device box), however, it's not meant to be used a circuit breaker - use smart MCBs instead if you need one (Tongou has smart MCBs as well);
  • It only breaks the line connected to the L terminal - the N terminal is simply attached to a piece of metal spanning the body of the device;
  • The relay inside is bistable as it maintains its position without power applied. If you set the power-on mode via the MCU, the old state will be maintained until the MCU is powered on and only then will it set the state based on the policy that was set (always on, always off, restore previous);
  • The MCU inside is HC32F460 and is accessible via UART1 of the CBU board (the same UART that is used for flashing). It runs the firmware that complies with the standard Tuya MCU protocol;
  • The MCU firmware actually interfaces with the metering sensors and so calibration is not required.
  • Some MCU settings are only possible to configure remotely (like the prepaid switch ones), some are only possible to set via the physical buttons (like the period of time for which the measured parameters must stay above/below the limits so that the configured action gets triggered) and some are possible to set remotely and via on-device buttons (like the limits themselves).

Device-disassembled Internal-mains-attachments MCU

UART Buffer Size Adjustment

The MCU sends Tuya data points quickly and needs 300+ bytes of UART buffer space. The CBU chip running Esphome is not able to process incoming bytes fast enough so many data points get discarded with the default buffer size of 64 bytes.

As it is not possible to override this setting easily, this config just incorporates a downstream change to alter the buffer size.

Therefore, the attached config relies on a version of ArduinoCore-API based on upstream 1.5.1 with two patches:

Flashing Instructions

The PSU inside that provides power to the control board has isolation from the mains unlike in some other products: a multimeter will show MOhms if you attach the neutral wire of the control board and the N terminal. However, avoid trying to reflash the device when it's powered from mains - this is too dangerous for your hardware and well-being.

The main board with the CBU chip is easily detachable from the main board but pins are very small (not the usual 2.54mm or 1.27mm dupont pins). When detached you can use something like PCBite hands-free probes, crafted PIZZAbite probes or simply various kinds of hooks (e.g. 1 or 2).

CBU needs 4 pins to be flashed:

  • RX1 (connect to TX of your UART)
  • TX1 (connect to RX of your UART)
  • 3.3V
  • GND

CBU-board-1 CBU-board-2

Disassembly and Reassembly

The rivets holding the device together seem to be DIN 7340 type B (d1 = 2mm, L ~= 16mm) hollow rivets. As the rivets are small in diameter it is not possible to remove them without drilling.

A 2mm hole can be drilled but there is enough plastic to make a D = 2.5mm hole with a P = 0.5mm pitch in it for an M3-0.5 x 18mm screw (18mm being the length of the screw and the width of the device). Get a metric tap and die set that has M3x0.5 in it and carefully prepare the holes (it's better to detach the plastic case parts when doing that).

Data Points

Provided that the UART buffer size is increased, you should see the data points during the Tuya module initialization.

[05:08:59][C][tuya:041]: Tuya:
[05:08:59][C][tuya:058]: Datapoint 1: int value (value: 38)
[05:08:59][C][tuya:058]: Datapoint 125: int value (value: 38)
[05:08:59][C][tuya:054]: Datapoint 6: raw (value: 00.00.00.00.00.00.00.00 (8))
[05:08:59][C][tuya:056]: Datapoint 11: switch (value: OFF)
[05:08:59][C][tuya:058]: Datapoint 13: int value (value: 0)
[05:08:59][C][tuya:056]: Datapoint 16: switch (value: OFF)
[05:08:59][C][tuya:062]: Datapoint 101: enum (value: 0)
[05:08:59][C][tuya:062]: Datapoint 102: enum (value: 2)
[05:08:59][C][tuya:062]: Datapoint 103: enum (value: 1)
[05:08:59][C][tuya:062]: Datapoint 104: enum (value: 2)
[05:08:59][C][tuya:062]: Datapoint 105: enum (value: 0)
[05:08:59][C][tuya:062]: Datapoint 107: enum (value: 2)
[05:08:59][C][tuya:062]: Datapoint 109: enum (value: 0)
[05:08:59][C][tuya:062]: Datapoint 110: enum (value: 0)
[05:08:59][C][tuya:056]: Datapoint 112: switch (value: ON)
[05:08:59][C][tuya:058]: Datapoint 114: int value (value: 50)
[05:08:59][C][tuya:058]: Datapoint 115: int value (value: 280)
[05:08:59][C][tuya:058]: Datapoint 116: int value (value: 165)
[05:08:59][C][tuya:058]: Datapoint 118: int value (value: 1000)
[05:08:59][C][tuya:058]: Datapoint 119: int value (value: 2000)
[05:08:59][C][tuya:058]: Datapoint 120: int value (value: 10)
[05:08:59][C][tuya:058]: Datapoint 131: int value (value: 247)
[05:08:59][C][tuya:058]: Datapoint 137: int value (value: 600)
[05:08:59][C][tuya:056]: Datapoint 111: switch (value: OFF)
[05:08:59][C][tuya:056]: Datapoint 141: switch (value: OFF)
[05:08:59][C][tuya:058]: Datapoint 140: int value (value: 5)
[05:08:59][C][tuya:062]: Datapoint 142: enum (value: 2)
[05:08:59][C][tuya:060]: Datapoint 138: string value (value: )
[05:08:59][C][tuya:074]: Product: '{"p":"kmnzgh4yn8pa0lqh","v":"1.1.3","m":2}'

The meaning of the data points is explained in the YAML config.

Configuration

The YAML config below has been tested with the MCU firmware 1.1.3.

esphome:
name: to-q-sys-jwt
name_add_mac_suffix: true
platformio_options:
# This is a mandatory section at the time of writing: it will get the UART buffer size increased.
platform_packages:
- framework-arduino-api @ https://github.com/dshcherb/ArduinoCore-API/archive/refs/tags/2025.1.4.zip
bk72xx:
board: cbu
logger:
web_server:
captive_portal:
mdns:
api:
encryption:
key: !secret api_key
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
uart:
rx_pin: RX1
tx_pin: TX1
baud_rate: 115200
# This does not work with libretiny unless a define in ArduinoCore-API is updated
# to increase the buffer size, see https://github.com/dshcherb/ArduinoCore-API/releases/tag/2025.1.4
rx_buffer_size: 1024
time:
- platform: homeassistant
id: homeassistant_time
# Register the Tuya MCU connection
tuya:
id: tuya_component
time_id: homeassistant_time
on_datapoint_update:
- sensor_datapoint: 6
datapoint_type: raw
then:
- lambda: |-
// volts with one digit after decimal point.
id(voltage_sensor).publish_state((x[0] << 8 | x[1]) * 0.1);
// amperes with 3 digits after decimal point.
id(current_sensor).publish_state(((x[2] << 16 | x[3] << 8) | x[4]) * 0.001);
// kilo watts with 3 digits after decimal point.
id(power_sensor).publish_state(((x[5] << 16 | x[6] << 8) | x[7]) * 0.001);
number:
- platform: "tuya"
name: "Under-voltage Threshold"
number_datapoint: 116
device_class: voltage
unit_of_measurement: "V"
min_value: 100
max_value: 280
step: 1
- platform: "tuya"
name: "Over-voltage Threshold"
number_datapoint: 115
device_class: voltage
unit_of_measurement: "V"
min_value: 100
max_value: 280
step: 1
- platform: "tuya"
name: "Over-current Threshold"
number_datapoint: 114
device_class: current
unit_of_measurement: "A"
min_value: 1
max_value: 50
step: 0.1
- platform: "tuya"
name: "Temperature Threshold"
number_datapoint: 118
unit_of_measurement: "°C"
device_class: temperature
# In 100s °C
min_value: -25
max_value: 100
multiply: 10.0
step: 0.1
- platform: "tuya"
name: "Over-Power Threshold"
number_datapoint: 119
device_class: power
unit_of_measurement: "W"
min_value: 5
# The value in the original app is 26000, however, the device is rated to
# switch up to 50 Amps and 240 V AC (the relay inside is marked as 60A).
# So it's better to cap the threshold at 240 * 50 = 12000 W.
max_value: 12000
step: 1
- platform: "tuya"
name: "Screen Brightness"
number_datapoint: 140
min_value: 1
max_value: 5
step: 1
- platform: "tuya"
# Called "Actively Report Time" in the developer API. This is actually an interval
# for the MCU to report all data points to the main module over UART. The original
# app limit is 60 seconds but lower values are possible. It may take some time for the
# Tuya component to handle incoming bytes for all data points so use with care.
name: "Reporting Interval"
number_datapoint: 137
unit_of_measurement: "S"
min_value: 5 # 60
max_value: 600
step: 1
- platform: "tuya"
name: "Balance Shortage Threshold"
device_class: energy
number_datapoint: 120
unit_of_measurement: "kW·h"
min_value: 1
max_value: 500
step: 1
- platform: "tuya"
id: charge_electricity
# The electricity allowance for the "prepayment switch" mode.
# Charging will not work if the prepayment switch is off (this logic
# is built into the MCU firmware).
name: "Charge Electricity"
number_datapoint: 14
device_class: energy_storage
unit_of_measurement: "kW·h"
min_value: 0
max_value: 9999 # 999999
datapoint_hidden:
datapoint_type: int
initial_value: 0
restore_value: false
step: 0.1
multiply: 100
select:
- platform: "tuya"
name: "Under-voltage Action"
enum_datapoint: 103
optimistic: true
options:
0: 'Ignore'
1: 'Alarm'
2: 'Trip'
- platform: "tuya"
name: "Over-voltage Action"
enum_datapoint: 102
optimistic: true
options:
0: 'Ignore'
1: 'Alarm'
2: 'Trip'
- platform: "tuya"
name: "Over-current Action"
enum_datapoint: 104
optimistic: true
options:
0: 'Ignore'
1: 'Alarm'
2: 'Trip'
- platform: "tuya"
name: "Over-power Action"
enum_datapoint: 105
optimistic: true
options:
0: 'Ignore'
1: 'Alarm'
2: 'Trip'
- platform: "tuya"
name: "Over-temperature Action"
enum_datapoint: 107
optimistic: true
options:
0: 'Ignore'
1: 'Alarm'
2: 'Trip'
- platform: "tuya"
# What to do when the device is running out of the electricity budget.
# It is not possible to trip on shortage: in fact, it does not make sense
# because there's still budget. In the pre-paid mode the relay will be tripped
# when the balance is zeroed out.
name: "Balance Shortage Action"
enum_datapoint: 101
optimistic: true
options:
0: 'Ignore'
1: 'Alarm'
- platform: "tuya"
name: "Relay Power-on State"
enum_datapoint: 142
optimistic: true
options:
0: 'Power Off'
1: 'Power On'
2: 'Last State'
text_sensor:
- platform: libretiny
version:
name: LibreTiny Version
- platform: "tuya"
# Seems to indicate whether the module is online (Wi-Fi connected) per the MCU
# based on the feedback from the main module.
name: "Online Status"
sensor_datapoint: 109
filters:
# The Tuya DP is an enum which is converted to human-readable strings.
- map:
- 1 -> Offline
- 0 -> Online
- platform: "tuya"
name: "Last MCU Event"
sensor_datapoint: 110
filters:
# The Tuya DP is an enum which is converted to human-readable strings denoting
# the status of the last event processed by the MCU. Clearing it manually just
# results in the MCU resetting it back to what it was so this is a sensor.
- map:
- 0 -> Normal
- 1 -> Over Current Trip
- 2 -> Over Power Trip
- 3 -> High Temp Trip
- 4 -> Over Voltage Trip
- 5 -> Under Voltage Trip
- 6 -> Over Current Alarm
- 7 -> Over Power Alarm
- 8 -> High Temp Alarm
- 9 -> Over Voltage Alarm
- 10 -> Under Voltage Alarm
- 11 -> Remote ON
- 12 -> Remote OFF
- 13 -> Manual ON
- 14 -> Manual OFF
# Actually irrelevant for this device because this power meter does not
# have current leakage detection.
- 15 -> Leakage Trip
- 16 -> Leakage Alarm
- 17 -> Restore Default
- 18 -> Automatic Closing
- 19 -> Electricity Shortage
# On MCU firmware 1.1.3 (latest at the time of writing) it seems like this
# alarm is not cleared by the MCU even if the balance is topped up past the
# shortage threshold. Doing a local or remote on/off will clear it but it's
# not always practical. Setting the DP to Normal manually is overridden by
# the MCU.
# Product: '{"p":"kmnzgh4yn8pa0lqh","v":"1.1.3","m":2}'
- 20 -> Electricity Shortage Alarm
sensor:
- platform: "tuya"
# Also known as Total Forward Energy
name: "Total Electricity Consumption"
sensor_datapoint: 1
device_class: "energy"
unit_of_measurement: "kW·h"
state_class: "measurement"
filters:
# The values stored in the MCU need to be processed for display.
- multiply: 0.01
accuracy_decimals: 2
- platform: "tuya"
name: "Forward Electricity"
sensor_datapoint: 125
device_class: "energy"
unit_of_measurement: "kW·h"
state_class: "measurement"
icon: "mdi:meter-electric-outline"
accuracy_decimals: 2
filters:
# The values stored in the MCU need to be processed for display.
- multiply: 0.01
- platform: "tuya"
name: "Real-time Temperature"
sensor_datapoint: 131
state_class: "measurement"
unit_of_measurement: "°C"
device_class: "temperature"
accuracy_decimals: 1
filters:
# The values stored in the MCU need to be processed for display.
- multiply: 0.1
icon: "mdi:temperature-celcius"
- platform: "tuya"
name: "Electricity Balance"
sensor_datapoint: 13
state_class: "measurement"
unit_of_measurement: "kW·h"
device_class: "energy"
accuracy_decimals: 2
filters:
# The values stored in the MCU need to be processed for display.
- multiply: 0.01
icon: "mdi:home-battery-outline"
- platform: "template"
id: current_sensor
name: "Real-time Current"
unit_of_measurement: "A"
state_class: "measurement"
device_class: "current"
icon: "mdi:current-ac"
accuracy_decimals: 3
- platform: "template"
id: power_sensor
name: "Real-time Power"
unit_of_measurement: "kW"
state_class: "measurement"
device_class: "power"
icon: "mdi:lightning-bolt"
accuracy_decimals: 3
- platform: "template"
id: voltage_sensor
name: "Real-time Voltage"
unit_of_measurement: "V"
state_class: "measurement"
device_class: "voltage"
icon: "mdi:flash-triangle"
accuracy_decimals: 1
switch:
- platform: "tuya"
# Off - circuit is "open", i.e. phase is broken, neutral wire is always connected in hardware.
# On - circuit is "closed", i.e. phase is connected via the built-in relay.
# The relay will be turned off (circuit is broken) whenever DP 11 (Prepayment switch) is
# off. The "Prepayment switch" has to do with how this device is marketed: i.e. for rental
# properties where somebody pays for electricity used.
name: "Circuit Breaker Switch"
switch_datapoint: 16
icon: "mdi:restart"
device_class: outlet
- platform: "tuya"
# Automatic Reclosing refers to a feature that closes the circuit
# (i.e. allows the current to flow through the relay) automatically
# upon coming back to normal conditions.
# Auto-closing is available only when the device recovers from
# under/over-voltage conditions, not other parameters due to safety
# reasons.
name: "Automatic Closing"
switch_datapoint: 112
restore_mode: DISABLED
- platform: "tuya"
# Reset the MCU to default settings.
name: "Reset MCU to Defaults"
switch_datapoint: 113
- platform: "tuya"
# Report MCU state changes (including electricity measurements) at the
# predefined reporting interval (see DP 137).
name: "Report State Changes"
switch_datapoint: 111
restore_mode: DISABLED
- platform: "tuya"
# Whether to use the energy balance datapoint or not to determine if the
# relay should be on or off (the actual behavior will also depend on
# whether DP 101 is set to alarm or break the circuit). If this mode
# is disabled, the on-device switch button will not be limited if balance
# is zero.
name: "Prepayment Switch Mode"
switch_datapoint: 11
- platform: "tuya"
id: clear_electricity_switch
# Clear the remaining electricity budget.
name: "Clear Remaining Electricity"
switch_datapoint: 12
internal: true
- platform: "tuya"
name: "Screen On"
# Enable or disable the on-device screen. Does not block buttons
# on the device: i.e. one can still trip the switch.
switch_datapoint: 141
inverted: true
button:
- platform: template
# Clearing the balance in the "Prepaid switch mode" will result in the relay
# breaking the circuit. Use with care. If the balance is zero when the
# prepaid mode is turned on, the relay will be tripped as well.
name: "Clear Balance"
icon: "mdi:checkbox-blank-off-outline"
on_press:
- switch.turn_on: clear_electricity_switch
- delay: 1s
# The flag isn't cleared automatically by the MCU so it's done manually.
- switch.turn_off: clear_electricity_switch
Edit this page on GitHub