- 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