DTS238-7 older firmware (2020) - dxs238xw component protocol differences

DTS238-7 W (older firmware, manufactured 2020-01) - different protocol/data format

Hardware

  • Device: DTS238-7 W (3-phase energy meter with WiFi)
  • Manufacturing date: January 2020 (from label)
  • Original firmware: WISEN app (older version)
  • ESP chip: ESP8285 (flashed with ESPHome 2026.4.5)
  • Component version: 1.1.1000

Problem

Older hardware (pre-2021) uses a different communication protocol than what the current dxs238xw component expects. The device communicates but measurement sensors (voltage, current, power, frequency) remain unknown.

What works

  • WiFi connection and ESPHome API :white_check_mark:
  • meter_state_detail = "Power Ok" :white_check_mark:
  • phase_count = 3 :white_check_mark:
  • max_voltage_limit, min_voltage_limit, max_current_limit :white_check_mark:
  • meter_state relay switch (read-only, ON state detected) :white_check_mark:
  • import_active_energy = 281.96 kWh :white_check_mark: (parsed from spontaneous message)

What does NOT work

  • Voltage / current / power / frequency sensors → Unknown
  • Relay control (SET_POWER_STATE cmd=0x09 is ignored by older firmware)
  • GET_MEASUREMENT request (cmd=0x0A) returns only echo, no measurement data

Protocol differences (older firmware)

Message types received

The device spontaneously sends only two message types — it does NOT respond to GET requests with data:

cmd=0x08 (25 bytes) — sent every ~5 seconds:

48.19.01.01.08.00.FA.00.C8.29.04.00.00.00.00.00.00.00.00.00.00.03.E8.00.45

Parsed correctly: max_voltage=250V, min_voltage=200V, max_current=105A

cmd=0x0B (67 bytes) — sent sporadically (observed once after SET command):

48.43.01.01.0B.00.00.0C.00.00.09.00.00.08.09.0D.09.0E.09.0D.00.00.00.00.00.00.00.00.00.00.00.00.00.00.10.00.00.0B.00.00.00.00.00.00.00.E5.01.88.03.E8.03.E8.13.83.00.00.6E.24.00.00.6E.24.00.00.00.00.11

cmd=0x0B data format (67 bytes, different from newer firmware)

Older firmware uses little-endian 16-bit values with different byte offsets:

Value Offset Format Scale Result
voltage_L1 d[9..10] LE16 ×0.1 230.4 V
voltage_L2 d[13..14] LE16 ×0.1 231.2 V
voltage_L3 d[15..16] LE16 ×0.1 231.7 V
current_L1 d[7..8] LE16 ×0.01 0.12 A
current_L2 d[11..12] LE16 ×0.01 0.00 A
current_L3 d[19..20] LE16 ×0.01 0.13 A
active_power_total d[34..35] LE16 ×1 16 W
frequency d[51..52] LE16 ×0.01 50.96 Hz
import_active_energy d[56..57] BE16 ×0.01 281.96 kWh
total_energy d[60..61] BE16 ×0.01 281.96 kWh

Note: All three L1/L2/L3 inputs were connected to the same phase (L1) during testing, so all voltage readings are expected to be ~230V.

CRC differences

Older firmware sends confirmation messages (6 bytes) with a different/incorrect CRC value. The component rejects these as "CRC check failed". Workaround: skip CRC check for confirmation messages.

Confirmation echo

Older firmware echoes the sent command (6 bytes, type=0x02) immediately. The component expected type=0x01 for answers, causing "WRONG_BYTES_TYPE_MESSAGE" errors.

GET_MEASUREMENT behavior

  • Sending cmd=0x0A (GET_MEASUREMENT) returns only a 6-byte echo
  • Measurement data (cmd=0x0B, 67 bytes) is sent spontaneously by the device, not as a response to GET requests
  • cmd=0x0B was observed once after sending a SET_POWER_STATE (cmd=0x09) command

Relay control

  • SET_POWER_STATE (cmd=0x09) is sent but the relay does NOT activate
  • The device acknowledges the command with an echo but ignores it

Suggested fix

Add detection of older firmware based on message length (67 bytes for cmd=0x0B vs 66 bytes for newer firmware) and use the correct little-endian parsing offsets. A prototype patch is available that detects the message length and switches between parsing modes:

case HEKR_CMD_RECEIVE_MEASUREMENT: {
  if (receive_array[1] == 67) {
    // Older firmware (little-endian format)
    #define LE16(a, i) ((a)[(i)] + ((a)[(i)+1] << 8))
    UPDATE_SENSOR_MEASUREMENTS(voltage_phase_1, LE16(receive_array, 9) * 0.1)
    UPDATE_SENSOR_MEASUREMENTS(voltage_phase_2, LE16(receive_array, 13) * 0.1)
    UPDATE_SENSOR_MEASUREMENTS(voltage_phase_3, LE16(receive_array, 15) * 0.1)
    UPDATE_SENSOR_MEASUREMENTS_CURRENT(current_phase_1, LE16(receive_array, 7) * 0.01)
    UPDATE_SENSOR_MEASUREMENTS_CURRENT(current_phase_2, LE16(receive_array, 11) * 0.01)
    UPDATE_SENSOR_MEASUREMENTS_CURRENT(current_phase_3, LE16(receive_array, 19) * 0.01)
    UPDATE_SENSOR_MEASUREMENTS_POWER(active_power_total, LE16(receive_array, 34) * 1.0)
    UPDATE_SENSOR_MEASUREMENTS(frequency, LE16(receive_array, 51) * 0.01)
    UPDATE_SENSOR_MEASUREMENTS(import_active_energy, ((receive_array[56] << 8) | receive_array[57]) * 0.01)
    UPDATE_SENSOR_MEASUREMENTS(total_energy, ((receive_array[60] << 8) | receive_array[61]) * 0.01)
    #undef LE16
  } else {
    // Newer firmware (existing big-endian parsing)
    // ... existing code ...
  }
  break;
}

Open questions

  1. What is the correct command to trigger measurement data on older firmware?
  2. What is the correct command to control the relay on older firmware?
  3. Are there other byte offsets in the 67-byte cmd=0x0B message that we haven't identified yet (per-phase power, power factor, reactive power)?

ESPHome log excerpt

[I][dxs238xw]: * CMD=0x08 len=25 raw: 48.19.01.01.08.00.FA.00.C8.29.04... (25)
[I][dxs238xw]: * CMD=0x0B len=67 raw: 48.43.01.01.0B.00.00.0C.00.00.09... (67)