- Log in to post comments
We recently upgraded one of our properties to 3 phase, and it has an existing single phase PV system with a Solax X1 Hybrid (Gen 2+ inverter.
Hybrid inverters use a power meter installed in the switchboard to allow them to offset the household consumption, drawing power from the batteries/solar as required.
The single phase inverter shipped with a Chint DDSU666 single phase modbus meter, which worked well for the single phase installation.
When we upgraded to 3 phase, I install the Chint DTSU666 3 phase modbus meter, hoping it would be supported by the inverter, but unfortunately it wasn't.
My next port of call was to use the PowerScraper software that I wrote to micromanage the power, but I got into a weird situation where the inverter would not spill the excess solar into the grid when the battery was full - it was as if the battery control registered controlled grid power rather than the battery power.
I finally settled on utilising an ESP32 to talk to the 3 phase meter, and emulate the single phase meter. I used an ESP32 development board, and a couple of TTL to RS485 modules to interface to the meter and inverter. On the meter side, I have a physical 120Ohm resistor terminating the RS485 line. On each of the modules, I have shorted the empty R0 pads with solder to enable the onboard terminating resistor. The interfaces were glued to the back of the ESP32 module, and enamel wire jumpers were added to provide power and data.


Looking at the requests coming from the inverter, it was hammering register 0x0B (meter type), and not any of the power registers. Passing the value 10 (0x000a) that I got from the DTSU666 didn't work.
I couldn't find any documentation online for what value is returned for the DDSU666, so I ended up adding code to increment the value each time it was accessed, and to save that value once a power register was accessed. This gave me the value 166 (0x00A6) as the first value that the inverter would accept. I later verified this with a spare DDSU666 I had at home.
Giving that value to the inverter made it happy, and it then continued on to request power from the power register (0x2004).
Here is the ESPHome snippet I used to proxy the DTSU666 3 phase data data and emulate the DDSU666 single phase meter:
# Define globals: one for the meter type that auto-increments on read,
# and one to store the meter type when any of the other registers are read.
globals:
- id: emulated_meter_type
type: uint16_t
restore_value: no
initial_value: '166'
# - id: last_emulated_meter_type
# type: uint16_t
# restore_value: no
# initial_value: '0'
# Expose both globals to Home Assistant
# number:
# - platform: template
# name: "Emulated Meter Type"
# id: emulated_meter_type_number
# min_value: 0
# max_value: 65535
# step: 1
# lambda: |-
# return id(emulated_meter_type);
# set_action:
# - lambda: |-
# id(emulated_meter_type) = x;
# - platform: template
# name: "Last Emulated Meter Type"
# id: last_emulated_meter_type_number
# min_value: 0
# max_value: 65535
# step: 1
# lambda: |-
# return id(last_emulated_meter_type);
# set_action:
# - lambda: |-
# id(last_emulated_meter_type) = x;
# --- UART for DTSU666 three‑phase meter (client) ---
uart:
- id: uart_dtsu666
rx_pin: GPIO19
tx_pin: GPIO18
baud_rate: 9600
parity: EVEN
stop_bits: 1
# --- UART for DDSU666 single‑phase emulation (server) ---
- id: uart_ddsu666
rx_pin: GPIO23
tx_pin: GPIO22
baud_rate: 9600
parity: NONE
stop_bits: 1
# --- Modbus bus definitions ---
modbus:
- id: modbus_dtsu666
uart_id: uart_dtsu666
- id: modbus_ddsu666
uart_id: uart_ddsu666
role: server
# --- Modbus controller for DTSU666 (client mode) ---
modbus_controller:
- id: dtsu666
modbus_id: modbus_dtsu666
address: 1
update_interval: 0.5s
# =====================================================
# DDSU666 Single‑Phase Meter Emulation via Modbus Server
# =====================================================
# This allows an external Modbus client to query the ESPHome node as if it were a DDSU666 meter.
- id: ddsu666
modbus_id: modbus_ddsu666
address: 1 # Set the emulated DDSU meter's Modbus address
server_registers:
- address: 0x0B
value_type: U_WORD
read_lambda: |-
// Increment the emulated meter type each time this register is read.
uint16_t current = id(emulated_meter_type);
// id(emulated_meter_type) = current + 1;
return current;
- address: 0x2000
value_type: FP32
read_lambda: |-
// Update the last_meter_type global when this register is read.
// id(last_emulated_meter_type) = id(emulated_meter_type);
// Emulated Voltage from aggregated sensor (already scaled)
return id(emulated_voltage).state;
- address: 0x2002
value_type: FP32
read_lambda: |-
// id(last_emulated_meter_type) = id(emulated_meter_type);
// Emulated Current from aggregated sensor
return id(emulated_current).state;
- address: 0x2004
value_type: FP32
read_lambda: |-
// id(last_emulated_meter_type) = id(emulated_meter_type);
// Emulated Active Power from aggregated sensor
return id(emulated_active_power).state / 1000.0;
- address: 0x2006
value_type: FP32
read_lambda: |-
// id(last_emulated_meter_type) = id(emulated_meter_type);
// Emulated Reactive Power from aggregated sensor
return id(emulated_reactive_power).state;
- address: 0x200A
value_type: FP32
read_lambda: |-
// id(last_emulated_meter_type) = id(emulated_meter_type);
// Emulated Power Factor from aggregated sensor
return id(emulated_pf).state;
- address: 0x4000
value_type: FP32
read_lambda: |-
// id(last_emulated_meter_type) = id(emulated_meter_type);
// Emulated Net Forward Energy from aggregated sensor
return id(emulated_forward_energy).state;
- address: 0x400A
value_type: FP32
read_lambda: |-
// id(last_emulated_meter_type) = id(emulated_meter_type);
// Emulated Net Reverse Energy from aggregated sensor
return id(emulated_reverse_energy).state;
# =====================================================
# DTSU666 Three‑Phase Meter Sensors
# =====================================================
sensor:
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Meter Type"
id: meter_type
register_type: holding
address: 0x0B
register_count: 1
value_type: U_WORD
## Voltages (Electrical Parameters)
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Line Voltage Uab"
register_type: holding
address: 0x2000
register_count: 2
value_type: FP32
unit_of_measurement: "V"
filters:
- multiply: 0.1
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Line Voltage Ubc"
register_type: holding
address: 0x2002
register_count: 2
value_type: FP32
unit_of_measurement: "V"
filters:
- multiply: 0.1
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Line Voltage Uca"
register_type: holding
address: 0x2004
register_count: 2
value_type: FP32
unit_of_measurement: "V"
filters:
- multiply: 0.1
# Phase voltages (used for aggregation)
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Phase Voltage Ua"
id: phase_voltage_a
register_type: holding
address: 0x2006
register_count: 2
value_type: FP32
unit_of_measurement: "V"
filters:
- multiply: 0.1
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Phase Voltage Ub"
id: phase_voltage_b
register_type: holding
address: 0x2008
register_count: 2
value_type: FP32
unit_of_measurement: "V"
filters:
- multiply: 0.1
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Phase Voltage Uc"
id: phase_voltage_c
register_type: holding
address: 0x200A
register_count: 2
value_type: FP32
unit_of_measurement: "V"
filters:
- multiply: 0.1
## Currents (Electrical Parameters)
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Phase Current Ia"
id: phase_current_a
register_type: holding
address: 0x200C
register_count: 2
value_type: FP32
unit_of_measurement: "A"
filters:
- multiply: 0.001
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Phase Current Ib"
id: phase_current_b
register_type: holding
address: 0x200E
register_count: 2
value_type: FP32
unit_of_measurement: "A"
filters:
- multiply: 0.001
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Phase Current Ic"
id: phase_current_c
register_type: holding
address: 0x2010
register_count: 2
value_type: FP32
unit_of_measurement: "A"
filters:
- multiply: 0.001
## Active Power and Reactive Power
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Combined Active Power Pt"
id: combined_active_power
register_type: holding
address: 0x2012
register_count: 2
value_type: FP32
unit_of_measurement: "W"
filters:
- multiply: 0.1
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Combined Reactive Power Qt"
id: combined_reactive_power
register_type: holding
address: 0x201A
register_count: 2
value_type: FP32
unit_of_measurement: "var"
filters:
- multiply: 0.1
## Power Factor
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Combined Power Factor PFt"
id: combined_pf
register_type: holding
address: 0x202A
register_count: 2
value_type: FP32
filters:
- multiply: 0.001
## Frequency
- platform: modbus_controller
modbus_controller_id: dtsu666
id: frequency
name: "Frequency"
register_type: holding
address: 0x2044
register_count: 2
value_type: FP32
unit_of_measurement: "Hz"
filters:
- multiply: 0.01
## Energy Data: Forward Active Energy
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Net Forward Active Energy"
id: net_forward_energy
register_type: holding
address: 0x1026
register_count: 2
value_type: FP32
unit_of_measurement: "kWh"
## Energy Data: Reverse Active Energy
- platform: modbus_controller
modbus_controller_id: dtsu666
name: "Net Reverse Active Energy"
id: net_reverse_energy
register_type: holding
address: 0x1030
register_count: 2
value_type: FP32
unit_of_measurement: "kWh"
# =====================================================
# Template Sensors for Aggregated (Single‑Phase) Values
# These values are computed from the three‑phase readings.
# =====================================================
- platform: template
name: "Emulated Voltage"
id: emulated_voltage
unit_of_measurement: "V"
lambda: |-
// Average the three phase voltages
return (id(phase_voltage_a).state + id(phase_voltage_b).state + id(phase_voltage_c).state) / 3.0;
update_interval: 0.5s
- platform: template
name: "Emulated Current"
id: emulated_current
unit_of_measurement: "A"
lambda: |-
// Average the three phase currents
return (id(phase_current_a).state + id(phase_current_b).state + id(phase_current_c).state) / 3.0;
update_interval: 0.5s
- platform: template
name: "Emulated Active Power"
id: emulated_active_power
unit_of_measurement: "W"
lambda: |-
// Use the combined active power directly from the meter
return id(combined_active_power).state;
update_interval: 0.5s
- platform: template
name: "Emulated Reactive Power"
id: emulated_reactive_power
unit_of_measurement: "var"
lambda: |-
return id(combined_reactive_power).state;
update_interval: 0.5s
- platform: template
name: "Emulated Power Factor"
id: emulated_pf
lambda: |-
return id(combined_pf).state;
update_interval: 0.5s
- platform: template
name: "Emulated Frequency"
id: emulated_frequency
unit_of_measurement: "Hz"
lambda: |-
return id(frequency).state;
update_interval: 0.5s
- platform: template
name: "Emulated Forward Energy"
id: emulated_forward_energy
unit_of_measurement: "kWh"
lambda: |-
return id(net_forward_energy).state;
update_interval: 0.5s
- platform: template
name: "Emulated Reverse Energy"
id: emulated_reverse_energy
unit_of_measurement: "kWh"
lambda: |-
return id(net_reverse_energy).state;
update_interval: 0.5s
References
Chint DDSU666 Single Phase Energy Meter Modbus Registers
Chint DTSU 3 Phase Energy Meter Modbus Registers