Tesla Wall Connector Gen 3 RESTful

I have the same question. I also see it in various states from what you have listed as well. Currently ours is sitting in State 9, its plugged in and was charging, but not currently. I have seen others as well. We have a 22 M3LR.

I spent the better part of an evening digging into /tedapi/v1, and after glancing over the main.js I realised it is simply based on Protocol Buffers, i.e. similar to the VCSEC or Powerwall.

If you load up the TWC3 web UI, and look at the responses from /tedapi/v1, you’ll see binary data. However, I found copy-pasting the binary responses from either Firefox or Chrome corrupts, but using Wireshark to export the data bytes (nested inside HTTP) worked fine.

You can decode the saved requests and responses with protoc --decode_raw. Because we don’t have the corresponding .proto, there are no field names, and we must figure out what they are.

What I found was the main.js actually contained JavaScript routines for encoding/decoding these protobufs, from which we can infer the original message definitions. For example, this routine (which I’ve abbreviated):

t.EcuId = {
  decode(e, t) {
      switch (e >>> 3) {
        case 1:
          i.partNumber = n.string();
          break;
        case 2:
          i.serialNumber = n.string();
          break;
      }
    }
  }
}

We can infer that the corresponding message (as defined in a .proto file) should be:

message EcuId {
  string part_number = 1;
  string serial_number = 2;
}

Which you could then decode with:

protoc twc3.proto --decode=EcuId < EcuId.pb

Obviously there are a lot more protos to decode, but for the purposes of limiting the charge current, you could focus solely on those.

The other interesting thing I noticed about main.js is the load sharing protocol itself appears to be based on protobufs. This suggests it may be possible to implement a corresponding load sharing participant (eventually).

It looks like all /tedapi/v1 requests/responses use an AuthEnvelope, which wraps a MessageEnvelope, which in turn wraps pretty much all the other protos. So AuthEnvelope is the top-level proto you want to be decoding.

To access the /tedapi/v1, the internal wi-fi access point needs to be activated, and it times out after a couple of hours, limiting its utility. But if there’s a way to implement the load sharing protocol it might be willing to keep the AP activated.

There are plenty of people who have reverse engineered the .proto definitions from various Tesla products, e.g. VCSEC here: GitHub - trifinite/vcsec-archive

But to my knowledge, nobody has yet (publicly) reversed the protobufs corresponding to the TWC3. This would be extraordinarily useful.

5 Likes

Please keep digging! Would love to be able to control load management from the Tesla wall charger :slight_smile: Sorry to say I’m not that smart to be at any help! :joy:

I’ve uploaded a minimal example of interacting with the TWC3: GitHub - jeremyvisser/wc3magic

I was able to successfully decode the same config structure that the web interface displays.

In addition, I was able to update the charge current from a script. I’ve included a sample script in the above repository that does this.

3 Likes

@jeremy23 Do you have any plans to implement this into HA somehow?

I think I would be fairly reliant on others to help with that.

An unsolved problem is how to reliably maintain administrative connection. To use the web interface (or send protos), you need to manually hold the charger button for 5 seconds to activate the internal AP.

After a period of inactivity, it times out and deactivates. Constantly polling it (e.g. requesting config) keeps it awake, but this is unreliable.

Using the load sharing protocol for providing solar input is an interesting possibility, since it is (electrically) safer than setting the max charge current. But I’m still unclear on how this protocol works.

I don’t have a second TWC3 to play with. When I create a hotspot named TeslaWallConnector_000001 with password AAAAAAAAAAAA, it connects and attempts to open a TLS connection to the gateway IP on TCP port 34578, presenting a client certificate signed by

For example, when you pair your TWC3 with another for load sharing, it connects to the SSID of the other TWC3, and tries to connect to its IP (assumed to be the gateway IP) on TCP port 34578 and establish a Mutual TLS connection, presenting a certificate issued by the “Tesla Energy Product Issuing CA”.

Or if you try to connect yourself, it tells you these are the accepted CAs:

% openssl s_client -connect 192.168.92.1:34578
---
Certificate chain
 0 s:CN = 1529455-02-D--PGTxxxxxxxxxxx, OU = Gen3 Wall Connector, OU = Energy, O = Tesla Inc., L = Palo Alto, ST = California, C = US
   i:CN = Tesla Energy Product Issuing CA, OU = Energy, OU = PKI, O = Tesla, C = US
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA256
   v:NotBefore: Nov 1 00:00:00 2021 GMT; NotAfter: Nov 1 00:00:00 2023 GMT
---
---
Acceptable client certificate CA names
CN = Tesla Energy Product Issuing CA, OU = Energy, OU = PKI, O = Tesla, C = US
CN = Tesla Powerwall Products CA
CN = Tesla Powerwall Products CA
OU = NXP Plug Trust CA, CN = Tesla Inc.-471c4e0b81d78ffc66028b985e22f2d9vE200
Client Certificate Types: RSA sign, ECDSA sign

Now obviously I don’t have access to this CA. If I present a self-signed certificate, it simply closes the connection with an “Unknown CA” error. I don’t know of a way to overwrite this CA with my own, nor mint any new certificates.

Since the cert has a 2 year expiry, possibly there’s a way to renew it. And if there’s a way to renew it, perhaps there’s a way to generate a new one. Or maybe it just ignores the validity period.

Maybe the whole load sharing thing is a dead end and the right answer is to just poll the device a lot, and poke the max charge rate setting. That seems like the simplest option, but it does require active polling.

If somebody can figure out a reliable way to convince the TWC3 to keep its internal AP alive (or whether there are older firmware versions that have this behaviour), that would be really great knowledge to have.

1 Like

Sounds like the load sharing could be an issue if one don’t have the cert then, and I guess Tesla would not be super happy to hand that over to us :rofl: I guess the only thing people want is to be able to control the amp’s or that’s what I would love to do. Today I do it via the Tesla API and that works fine but if I charge another vehicle I have no way to control the max amps…

Thanks for investigating and hopefully there is someone else that could help in some way to get this feature integrated into HA.

Since Home Assistant is monitoring the handle temperature from the wall connector, one funny anecdote I’ve discovered is the handle temperature gets erroneously recorded as 127°C while the button is held down.

I accidentally triggered an over–temperature alert on the wall connector because I held down the charger handle button for around 30 seconds one time, and it erroneously read the temperature as 127°C. :rofl:

On a more serious note, I’ve not made any further progress in figuring out the load sharing protocol, in particular how to get past the Mutual TLS authentication.

While I can set the overall max charge rate no problem, I’ve still not made further progress in reliably keeping the internal AP awake, to make repeatedly setting the charge rate reliable.

Keeping the browser page open seems to keep it awake, but sending a config settings request via curl does not seem to do the same thing. Perhaps I need keepalives, or some particular combination of requests, or something else. Or maybe it’s just not possible.

I could have sworn some old firmware versions kept the internal AP alive, so it’s possible downgrading to a version known to have this behaviour might be an option. While I found some old firmware versions here, and http://192.168.92.1/service lets you upload a firmware binary, I found that uploading old versions took no effect. So downgrading might not be supported.

Any useful findings from others would be most appreciated.

1 Like

Hi Jeremy,

many thanks for your investigation and the protocol buffer examples on GitHub!

It seems that the TWC Gen3 “prohibits” downgrades by checking the version number in the filename. So if you change the filename of the old version to a version number of a newer than the already installed version it should work (see also this post: Software Gen3 Wall Connector - #1112 von DerFizi - Weitere Ladestationen - TFF Forum - Tesla Fahrer & Freunde). Note: If your TWC has an internet connection, it will automatically update itself after some time.

I have tested your example successfully and could change the ampere settings. Unfortunately also in my case the service/installation WiFi access point stops working after some time, even after permanently changing the setting once every 5 minutes.

Furthermore I observed that the last setting is still active after a reboot of the TWC Gen3, so the change is stored internally to an EEPROM / EERAM / FRAM. I am not sure how many writing cycles are fine for the corresponding IC, but I have some doubts that this could be a problem.

One more point: It is possible to change the setting starting from 6 A. If I change it to a value between 1…5 A then an error message is returned (and the value stays at 6 A). If a value of 0 A is sent to the TWC Gen3, then there is no error message, but the value still stays at 6 A.
But what I initially wanted is to be able to stop charging by setting the value to 0 A (and then restart it again with a value >= 6 A), which is not working.

So I still think that the current way is not as optimal, as I hope the load sharing option could be.

Is there anyway how I can support you for further investigations? For your information: I have 2 TWCs (which are not yet setup for load sharing, but I could do it for tests).

Best regards,
Michael

1 Like

Agreed, setting the max charge is persisted to flash, so I agree this is a longevity problem. But the load sharing is probably runtime information, so is better suited to this task.

One thing that would be good to know is exactly what the load sharing traffic uses. I’m guessing the followers connect to the leader on 192.168.92.1:34578, performing Mutual TLS auth, and then exchanging WCLoadSharingFollowerState, WCLoadSharingLeaderState, WCLoadSharingFollowerState, and similar messages. But that’s just my guess and it’d be nice to know for sure.

While it’s unlikely a simple TLS MITM on TCP port 34578 would actually work, it’s still worth trying just to rule it out.

Or if the initial authentication happens via TLS, but the actual load sharing happens via UDP (e.g. unauthenticated), then it would be interesting to know whether anything can be injected there.

When load sharing is enabled, I’m interested to know whether the internal AP stays active all the time, or whether it times out like before, which might add further information on keeping the AP alive for other reasons.

1 Like

I found a forum thread where users stated continually broadcasting the SSID was “fixed” in 22.7.0, so I downgraded to firmware version 21.36.6 (pro tip: if it looks like it didn’t downgrade, power cycle it at the breaker).

So after downgrading to 21.36.6, I can confirm the internal AP is now continually awake even several hours later.

Unfortunately though, after a timeout period, the API disallows talking to it, requiring a physical handle button press, even when the AP is still active.

Even directly sending protos throws an error_response{ status{ code: 16 }} in this state.

So this may be a dead end, and finding ways to keep the API awake (including on newer firmware versions) is likely to be a better use of time.

1 Like

Is the procedure with just renaming the version in the filename to a newer one working, or did you find another way?

Finally I found some time for testing.

As stated above, I have two TWC Gen3 which were until now not configured for power sharing and both are running on firmware v22.33.1

For the case that the master/slave configuration procedure is not known, here in short:

  1. Activate the installation/service wireless access point on both TWCs
  2. Login to the master and enter the SSID and password of the second one (which will be the slave)
  3. Enter the maximum current for both TWCs (minimum is 2x 6 A → 12 A)
  4. Activate power sharing

Observed behavior after activating power sharing:

  • The normal API fo the master TWC is (after a short timeout) permanently available (…/api/1/vitals|wifi_status|version|lifetime)
  • The same API is not available anymore for the slave TWC
  • For some minutes the installation/service AP is available for both TWCs
  • After around 5 minutes the installation/service AP of the slave is not visible/accessible
  • After around 10 minutes the installation/service AP of the master is not visible, but an existing connection is still alive and it is possible to change the current setting with the protocol buffer
  • After around 30 minutes the connection to the installation/service AP of the master is still alive, but changing the current setting results in an error (a normal “ping” is working)

Some additional notes:

  • It is not possible to add a not existing second TWC with some “fantasy” credentials (this process terminates with an error message… so you really need a second device for the configuration):
    image
  • The master can display some fundamental data of the slave, but I am not sure if all data, that are normally available via the “main” API, are visible or not:
    image
  • After enabling power sharing the response contains a lot of “participant_dins” in a a new entry called “load_sharing_config”, see the following two outputs (click on “Before …” and “After …”):
Before enabling power sharing
$ ./set-charge-current.sh 6
* TCP_NODELAY set
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to 192.168.92.1 (192.168.92.1) port 80 (#0)
> POST /tedapi/v1 HTTP/1.1
> Host: 192.168.92.1
> User-Agent: curl/7.52.1
> Accept: */*
> Content-Type: application/octet-stream
> Content-Length: 52
> 
} [52 bytes data]
* upload completely sent off: 52 out of 52 bytes
< HTTP/1.1 200 OK
< Connection: keep-alive
< Transfer-Encoding: chunked
< Cache-Control: no-store, max-age=0
< Content-Type: application/octet-stream
< 
{ [71 bytes data]
* Curl_http_done: called premature == 0
100   112    0    60  100    52    752    652 --:--:-- --:--:-- --:--:--   769
* Connection #0 to host 192.168.92.1 left intact
payload {
  delivery_channel: 1
  sender {
    din: "1529455-02-D--PGT21XXXXXXXXXX"
  }
  recipient {
    local: 1
  }
  wc {
    configure_settings_response {
      settings {
        max_output_current_amps: 6
        gmi_mode: 3
        country: "DE"
        third_party_vehicle_mode: 1
      }
    }
  }
}
external_auth {
  type: 1
}
After enabling power sharing
$ ./set-charge-current.sh 13
* TCP_NODELAY set
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to 192.168.92.1 (192.168.92.1) port 80 (#0)
> POST /tedapi/v1 HTTP/1.1
> Host: 192.168.92.1
> User-Agent: curl/7.52.1
> Accept: */*
> Content-Type: application/octet-stream
> Content-Length: 52
> 
} [52 bytes data]
* upload completely sent off: 52 out of 52 bytes
< HTTP/1.1 200 OK
100    52    0     0  100    52      0    680 --:--:-- --:--:-- --:--:--   684< Connection: keep-alive
< Transfer-Encoding: chunked
< Cache-Control: no-store, max-age=0
< Content-Type: application/octet-stream
< 
{ [150 bytes data]
* Curl_http_done: called premature == 0
100   191    0   139  100    52   1621    606 --:--:-- --:--:-- --:--:--  1616
* Connection #0 to host 192.168.92.1 left intact
payload {
  delivery_channel: 1
  sender {
    din: "1529455-02-D--PGT21XXXXXXXXXX"
  }
  recipient {
    local: 1
  }
  wc {
    configure_settings_response {
      settings {
        max_output_current_amps: 13
        gmi_mode: 3
        country: "DE"
        third_party_vehicle_mode: 1
        load_sharing_config {
          participant_dins: 10
          participant_dins: 28
          participant_dins: 49
          participant_dins: 53
          participant_dins: 50
          participant_dins: 57
          participant_dins: 52
          participant_dins: 53
          participant_dins: 53
          participant_dins: 45
          participant_dins: 48
          participant_dins: 50
          participant_dins: 45
          participant_dins: 68
          participant_dins: 45
          participant_dins: 45
          participant_dins: 80
          participant_dins: 71
          participant_dins: 84
          participant_dins: 50
          participant_dins: 49
          participant_dins: 49
          participant_dins: 54
          participant_dins: 50
          participant_dins: 48
          participant_dins: 50
          participant_dins: 51
          participant_dins: 50
          participant_dins: 57
          participant_dins: 48
          participant_dins: 10
          participant_dins: 28
          participant_dins: 49
          participant_dins: 53
          participant_dins: 50
          participant_dins: 57
          participant_dins: 52
          participant_dins: 53
          participant_dins: 53
          participant_dins: 45
          participant_dins: 48
          participant_dins: 50
          participant_dins: 45
          participant_dins: 68
          participant_dins: 45
          participant_dins: 45
          participant_dins: 80
          participant_dins: 71
          participant_dins: 84
          participant_dins: 50
          participant_dins: 49
          participant_dins: 49
          participant_dins: 54
          participant_dins: 50
          participant_dins: 48
          participant_dins: 50
          participant_dins: 51
          participant_dins: 51
          participant_dins: 51
          participant_dins: 56
          fixed_limit {
            network_limit_amps: 16
          }
          charging_enabled: true
        }
      }
    }
  }
}

Are you able to access the slave status data using rest config in HA?

1 Like

I was just looking at the json from the vitals API and see a session energy value. It seems totally off from what I would expect to see (energy delivered between contactor_closed==true to contactor_closed==false) which likely means I don’t understand what a session is in the context. My wild guess now is that is from connecting the charge cable to the port of the car (vehicle connected == true) to removing from the car (vehicle connected == false). I think this would be a helpful value to expose in the integration. Are there any plans for this? Can I help with that?

Has anyone figured out the “State” values and what exactly they mean? ie 1,4,8,9,11?

I have seen a few where it is plugged in, but not charging and sitting in various states for example.

Thanks.

I do not have full decoding, but here are some states.

These are trivial:

1: Idle, not connected
11: Charging
7: Fault, recovery via unplugging

This is not so clear:

4: Charger ready car not charging due 
9: Charger ready car not charging due to battery full (reached charge limit)

By not so clear I mean that there is really no way that the pilot signal could give this information, except for Tesla cars (they use proprietary protocol).

Other states I have not seen lately… Also fault, 7, is a new finding from last ~3 weeks…

I did have an additional look at the data and below are my findings though observation (Model Y and Kia Soul EV+). To calculate the charge rate the EVSE thinks is happening I used:

Charge rate = ((reported energy at the end of charging - reported energy at the beginning of charging) / charging time in min) * 60

1: Idle, not connected
4: Idle, connected
8: Charging stopped, connected (either set limit reached or manually stopped via Tesla app, this seems to be the normal state following 10)
9: Charging stopped, connected (I only see 11 → 9 → 4 as a sequence)
10: Charging (it seems at a rate less than 10.5 kW? )
11: Charging (it seems at a rate of more than 10.5 kW)

1 Like

Looks like firmware 23.8.2 is starting to implement some things around modbus (RS485) for wired load sharing. Firmware 23.8.2 Details | Wall Monitor

Probably not active yet but definitely worth the attention.

1 Like

Hello,

Does anyone experienced many deconnection in HA ?

My TWC 3 always connect / disconnect, many times per minutes. The Wifi signal is 84% and rssi - 48 which seems to be very good.

Capture d’écran 2023-04-18 à 19.42.30

2023-04-18 19:36:10.811 ERROR (MainThread) [backoff] Giving up async_request(...) after 3 tries (tesla_wall_connector.exceptions.WallConnectorConnectionTimeoutError: Timeout while connecting to Wall Connector at 192.168.1.70)
2023-04-18 19:36:10.817 ERROR (MainThread) [homeassistant.components.tesla_wall_connector] Error fetching tesla-wallconnector data: Could not fetch data from Tesla WallConnector at 192.168.1.70: Timeout
2023-04-18 19:36:40.370 INFO (MainThread) [homeassistant.components.tesla_wall_connector] Fetching tesla-wallconnector data recovered

2023-04-18 19:42:11.290 INFO (MainThread) [backoff] Backing off async_request(...) for 0.1s (tesla_wall_connector.exceptions.WallConnectorConnectionTimeoutError: Timeout while connecting to Wall Connector at 192.168.1.70)
2023-04-18 19:42:12.352 INFO (MainThread) [backoff] Backing off async_request(...) for 0.3s (tesla_wall_connector.exceptions.WallConnectorConnectionTimeoutError: Timeout while connecting to Wall Connector at 192.168.1.70)
2023-04-18 19:42:13.680 ERROR (MainThread) [backoff] Giving up async_request(...) after 3 tries (tesla_wall_connector.exceptions.WallConnectorConnectionTimeoutError: Timeout while connecting to Wall Connector at 192.168.1.70)
2023-04-18 19:42:13.684 ERROR (MainThread) [homeassistant.components.tesla_wall_connector] Error fetching tesla-wallconnector data: Could not fetch data from Tesla WallConnector at 192.168.1.70: Timeout
2023-04-18 19:42:43.440 INFO (MainThread) [homeassistant.components.tesla_wall_connector] Fetching tesla-wallconnector data recovered

Every time since one year …

it seems that when Wall monitor is active (and poll every 5 sec approximatively) the deconnection did not occurs.

I think this is an interesting point.