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: switchElectrical Standard: globalBoard: bk72xxDifficulty: Disassembly required, 3/5
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).
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:
- The buffer size increase from default 64 bytes to 1024 bytes: https://github.com/dshcherb/ArduinoCore-API/commit/365122ef16e4065a4cc06c4f1db786a5f6d0b7d8
- The printf feature patch from the Libretiny fork of ArduinoCoreAPI: https://github.com/dshcherb/ArduinoCore-API/commit/ce57327c3bbcb70d007f915a1c5c92dd6f16a5f1
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
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 connectiontuya: 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