Skip to content

Instantly share code, notes, and snippets.

@TooMuchAir
Last active December 7, 2025 17:34
Show Gist options
  • Select an option

  • Save TooMuchAir/a031566aeb1e6bcd882e7c9a0f6a80a6 to your computer and use it in GitHub Desktop.

Select an option

Save TooMuchAir/a031566aeb1e6bcd882e7c9a0f6a80a6 to your computer and use it in GitHub Desktop.

MQTT2TWC3

ESPHome YAML config file that turns an ESP and an RS485 bridge into an MQTT charge controller for the Tesla Gen 3 Wall Connector.

I've created the file with information gathered by various contributors on dracoventions/TWCManager#20, including the TWC3 Modbus simulator and TWC3 Controller.

The configuration file here allows using an ESPHome enabled device to send power grid load settings via simple MQTT messages. HomeAssistant can likely be used as well, but is not required.

HW Setup:
I have used a basic ESP-01 but later versions such as ESP32 should also work. In order to communicate with the TWC3, I'm using an RS485 bridge. Most (if not all) UART to RS485 boards should work. Note though, that the ESPs operate at 3.3V. A simple adapter board with voltage regulator & level shifter or a bridge module that runs on 3.3V can be used. Some ESP RS485 bridge modules even come with module and attachable ESP-01, requiring only a power supply and (shielded) RS485 cable.

Wiring:
Disclaimer: The work should only be carried out by certified electricians only and all devices, especially the TWC3, must be disconnected from power!
The ESP's TX/RX serial interface is connected to the bridge's RX/TX ports (3.3V!). TX connects with RX and vice versa.
The RS485 bridge needs to be connected with the RS485 terminals of the TWC3, whereby I had to connect A with Red/+ (A is typically -, but YMMV).

ESPHome:
The ESP must be ESPHome-controlled. The home page explains the multiple ways this can be achieved.

Config File:
The YAML file configures:

  • a variable (number) to store a single float value representing the current (amps) of each phase(cable) of the simulated power grid.
  • a Modbus server via serial that will respond to the TWC3. The TWC3 acts as the client and initiates the connection/request
  • an MQTT component that exposes the variable (read/write using topic/state or topic/command)
  • one or more WiFi networks to connect to.

The configuration assumes that 3 clamps are configured at 230V and the Modbus server will convey identical configured sensor values for each clamp.

ESP YAML Setup:
Adjust the settings in the "substitutions" section of the YAML file and create a "secrets" file with the necessary passwords.
The "max_value" will be used for the current (amps) setting of the TWC3. The threshold value configured in TWC3 must be smaller so that a higher value can be set in the ESP to force a reduction of charging power, essentially simulating a value above the threshold.
The "voltage" value is used for the calculation of power (watts), which is sent to the TWC3 (but not used?).
Finally the YAML code can be "Installed" (uploaded) the to the ESP device.

TWC3 Setup:
After connecting the TWC3 with the ESP/RS485 interface the TWC3 can be put into setup mode (pushing the handle button for ~5 secs). Newer TWC3 firmware versions require the usage of the Tesla One app.
The setup screen should now show a meter for which I configured clamps 1,2, and 3 as "Conductor":


Set the conductor limit (in amps) to a value that is below the max_value of the YAML file (see above).

Testing can be done with an MQTT Explorer application: Upon start, the ESP sets the maximum value as the electrical current (amps) load. Other values can be set by sending a new value to "*topic*/command". The Tesla One app seems to always round up to the next full amp, i.e. a setting of 2.1 A is shown as 3.0 A.

Happy charging!

substitutions:
device_name: esp-tesla
friendly_name: ESPHome Tesla
mqtt_broker: your.mqtt.server # CHANGE!
tx_pin: GPIO1 # connect to RX on modbus bridge # CHANGE!
rx_pin: GPIO3 # connect to TX on modbus bridge # CHANGE!
mqtt_value_id: meter_reading
min_value: 0.0
max_value: 35.0 # adjust to a value above configured max-value in Wall Connector config
grid_voltage: 230 # used for power calculation
# serial <-> modbus bridge config
baud_rate: 115200
data_bits: 8
parity: NONE
stop_bits: 1
esp8266: # CHANGE!
board: esp01_1m
logger:
level: INFO # Don't use DEBUG in production
baud_rate: 0 #disable serial in order to use serial ports for modbus
logs:
# Reduce verbosity for components that might log sensitive data
wifi: WARN
api: WARN
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
min_version: 2025.9.0
name_add_mac_suffix: false
wifi:
networks:
- ssid: !secret wifi_ssid
password: !secret wifi_password
- ssid: !secret wifi2_ssid
password: !secret wifi2_password
min_auth_mode: WPA2
ota:
- platform: esphome
password: !secret eh_tesla_ota_password
globals:
- id: ct1_power_w
type: float
initial_value: "0.0"
- id: ct2_power_w
type: float
initial_value: "0.0"
- id: ct3_power_w
type: float
initial_value: "0.0"
- id: ct4_power_w
type: float
initial_value: "0.0"
- id: ct_total_w
type: float
initial_value: "0.0"
- id: ct1_current_a
type: float
initial_value: "0.0"
- id: ct2_current_a
type: float
initial_value: "0.0"
- id: ct3_current_a
type: float
initial_value: "0.0"
- id: ct4_current_a
type: float
initial_value: "0.0"
- id: ct_total_a
type: float
initial_value: "0.0"
# Power config value
number:
- platform: template
id: meter_reading
name: "Meter Reading"
icon: "mdi:meter-electric"
disabled_by_default: false
unit_of_measurement: "A"
min_value: ${min_value}
max_value: ${max_value}
step: 0.01
initial_value: ${max_value} # cannot be used with lambda
optimistic: true # update when set
on_value:
then:
lambda: |-
// set ct1_current to meter reading
id(ct1_current_a) = id(${mqtt_value_id}).state;
// set all other ct_currents to same. Ignore ct4 (it was initialized with 0.0).
id(ct2_current_a) = id(ct3_current_a) = id(ct1_current_a);
id(ct_total_a) = id(ct1_current_a) + id(ct2_current_a) + id(ct3_current_a) + id(ct4_current_a);
id(ct1_power_w) = ${grid_voltage} * id(ct1_current_a); // assume 230 volts
id(ct2_power_w) = id(ct3_power_w) = id(ct1_power_w); // all amps and watts are equal, ignore ct4
id(ct_total_w) = id(ct1_power_w) + id(ct2_power_w) + id(ct3_power_w) + id(ct4_power_w);
mqtt:
broker: ${mqtt_broker}
username: !secret mqtt_user
password: !secret mqtt_pw
birth_message:
topic: ${device_name}/availability
payload: online
will_message:
topic: ${device_name}/availability
payload: offline
reboot_timeout: 0s
uart:
- id: wallconn_uart
tx_pin: ${tx_pin}
rx_pin: ${rx_pin}
baud_rate: ${baud_rate}
data_bits: ${data_bits}
parity: ${parity}
stop_bits: ${stop_bits}
modbus:
- id: wallconn_modbus
uart_id: wallconn_uart
role: server
modbus_controller:
- id: wc_mb_server # modbus server (wall conector is client/requestor)
modbus_id: wallconn_modbus
address: 1
server_registers:
# Serial number / MAC address
- { address: 1, value_type: U_WORD, read_lambda: 'return 0x3078;' }
- { address: 2, value_type: U_WORD, read_lambda: 'return 0x3030;' }
- { address: 3, value_type: U_WORD, read_lambda: 'return 0x3030;' }
- { address: 4, value_type: U_WORD, read_lambda: 'return 0x3034;' }
- { address: 5, value_type: U_WORD, read_lambda: 'return 0x3731;' }
- { address: 6, value_type: U_WORD, read_lambda: 'return 0x3442;' }
- { address: 7, value_type: U_WORD, read_lambda: 'return 0x3035;' }
- { address: 8, value_type: U_WORD, read_lambda: 'return 0x3638;' }
- { address: 9, value_type: U_WORD, read_lambda: 'return 0x3631;' }
- { address: 10, value_type: U_WORD, read_lambda: 'return 0x0000;' }
# "1.6.1‑Tesla"
- { address: 11, value_type: U_WORD, read_lambda: 'return 0x312E;' }
- { address: 12, value_type: U_WORD, read_lambda: 'return 0x362E;' }
- { address: 13, value_type: U_WORD, read_lambda: 'return 0x312D;' }
- { address: 14, value_type: U_WORD, read_lambda: 'return 0x5465;' }
- { address: 15, value_type: U_WORD, read_lambda: 'return 0x736C;' }
- { address: 16, value_type: U_WORD, read_lambda: 'return 0x6100;' }
# Four reserved words (all 0xFFFF)
- { address: 17, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
- { address: 18, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
- { address: 19, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
- { address: 20, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
# "012.00020A.H"
- { address: 21, value_type: U_WORD, read_lambda: 'return 0x3031;' }
- { address: 22, value_type: U_WORD, read_lambda: 'return 0x322E;' }
- { address: 23, value_type: U_WORD, read_lambda: 'return 0x3030;' }
- { address: 24, value_type: U_WORD, read_lambda: 'return 0x3032;' }
- { address: 25, value_type: U_WORD, read_lambda: 'return 0x3041;' }
- { address: 26, value_type: U_WORD, read_lambda: 'return 0x2E48;' }
- { address: 27, value_type: U_WORD, read_lambda: 'return 0x0000;' }
# Reserved
- { address: 28, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
# Meter number "90954"
- { address: 29, value_type: U_WORD, read_lambda: 'return 0x3930;' }
- { address: 30, value_type: U_WORD, read_lambda: 'return 0x3935;' }
- { address: 31, value_type: U_WORD, read_lambda: 'return 0x3400;' }
# Model? "VAH4810AB0231"
- { address: 32, value_type: U_WORD, read_lambda: 'return 0x5641;' }
- { address: 33, value_type: U_WORD, read_lambda: 'return 0x4834;' }
- { address: 34, value_type: U_WORD, read_lambda: 'return 0x3831;' }
- { address: 35, value_type: U_WORD, read_lambda: 'return 0x3041;' }
- { address: 36, value_type: U_WORD, read_lambda: 'return 0x4230;' }
- { address: 37, value_type: U_WORD, read_lambda: 'return 0x3233;' }
- { address: 38, value_type: U_WORD, read_lambda: 'return 0x3100;' }
# eight more reserved (0xFFFF)
- { address: 39, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
- { address: 40, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
- { address: 41, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
- { address: 42, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
- { address: 43, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
- { address: 44, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
- { address: 45, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
- { address: 46, value_type: U_WORD, read_lambda: 'return 0xFFFF;' }
# Mac "04:71:4B:05:68:61"
- { address: 47, value_type: U_WORD, read_lambda: 'return 0x3034;' }
- { address: 48, value_type: U_WORD, read_lambda: 'return 0x3A37;' }
- { address: 49, value_type: U_WORD, read_lambda: 'return 0x313A;' }
- { address: 50, value_type: U_WORD, read_lambda: 'return 0x3442;' }
- { address: 51, value_type: U_WORD, read_lambda: 'return 0x3A30;' }
- { address: 52, value_type: U_WORD, read_lambda: 'return 0x353A;' }
- { address: 53, value_type: U_WORD, read_lambda: 'return 0x3638;' }
- { address: 54, value_type: U_WORD, read_lambda: 'return 0x3A36;' }
- { address: 55, value_type: U_WORD, read_lambda: 'return 0x3100;' }
# CT1 power W
- { address: 0x88, value_type: FP32, read_lambda: 'return id(ct1_power_w);' }
# CT2 power W
- { address: 0x8A, value_type: FP32, read_lambda: 'return id(ct2_power_w);' }
# CT3 power W
- { address: 0x8C, value_type: FP32, read_lambda: 'return id(ct3_power_w);' }
# CT4 power W
- { address: 0x8E, value_type: FP32, read_lambda: 'return id(ct4_power_w);' }
# Aggregate watts
- { address: 0x90, value_type: FP32, read_lambda: 'return id(ct_total_w);' }
# Reserved
- { address: 0x92, value_type: U_WORD, read_lambda: 'return 0;' }
# CT1 current amps
- { address: 0xF4, value_type: FP32, read_lambda: 'return id(ct1_current_a);' }
# CT2 current amps
- { address: 0xF6, value_type: FP32, read_lambda: 'return id(ct2_current_a);' }
# CT3 current amps
- { address: 0xF8, value_type: FP32, read_lambda: 'return id(ct3_current_a);' }
# CT4 current amps
- { address: 0xFA, value_type: FP32, read_lambda: 'return id(ct4_current_a);' }
# Total amps
- { address: 0xFC, value_type: FP32, read_lambda: 'return id(ct_total_a);' }
# Initialization handshake
- { address: 40002, value_type: U_WORD, read_lambda: 'return 0x0001;' }
- { address: 40003, value_type: U_WORD, read_lambda: 'return 0x0042;' }
- { address: 40004, value_type: U_WORD, read_lambda: 'return 0x4765;' }
- { address: 40005, value_type: U_WORD, read_lambda: 'return 0x6E65;' }
- { address: 40006, value_type: U_WORD, read_lambda: 'return 0x7261;' }
- { address: 40007, value_type: U_WORD, read_lambda: 'return 0x6300;' }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment