This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Tesla Powerwall Local Connection broken with New Release
#1
Tongue Hi


For a few months now I've been connecting our SHAC / LogicMachine to our TeslaPowerwall to load-shift our electricity usage between the battery and the grid in response to the 30 minute electricity pricing provided by Amber Electric.  This has worked really well and we have seen our energy bills fall by about 50%.

Tesla pushed a firmware update the the Powerwall today (v21.20.2) which is causing an error with the local login script.  The remote login script for control of the Powerwall still works well).

Here is the local login script which is no longer working...

Code:
123456789101112131415161718192021222324252627282930313233343536
https = require('ssl.https') json = require('json') https.TIMEOUT = 5 loginURL = 'https://POWERWALLGATEWAY/api/login/Basic'   loginBody = "{\"username\":\"customer\",\"password\":\"MYPASS\", \"email\":\"MY@EMAIL.COM\",\"force_sm_off\":false}"   loginPostResponse = {}   res, err, loginResponseHeaders, status = https.request     {       url = loginURL,       method = 'POST',       protocol = 'tlsv12',       headers =       {         ['Accept'] = '*/*',         ['Content-Type'] = 'application/json',         ['Content-Length'] = loginBody:len()       },       source = ltn12.source.string(loginBody),            sink = ltn12.sink.table(loginPostResponse)     }    log(res, err, loginResponseHeaders) -- DEBUG   if loginPostResponse then         mmm_cookies = loginResponseHeaders['set-cookie']     if mmm_cookies then       token = mmm_cookies:match('(AuthCookie=[^;]+)'..';')             log ("Successfully retrieved local Tesla Powerwall Login Token " .. token)           end        end 


Now, I get the following error

Code:
1
attempt to index global 'loginResponseHeaders' (a nil value)

and the debug line returns 

Code:
123456
* arg: 1   * nil * arg: 2   * string: closed * arg: 3   * nil


when trying to connect to the Powerwall with the above code.

Previously POWERWALLGATEWAY was a DNS name, however the PowerWall now drops the connection unless an IP address is used in a web browser.  I can connect ok to https://POWERWALLGATEWAY/api/login/Basic/ with a web browser to retrieve the token, however Logic Machine script no longer works.

Pointing a web browser at the Powerwall yields a NET::ERR_CERT_AUTHORITY_INVALID error, which one can click through, however the Logic Machine script can't get past this

The SSL certificate used by Tesla and discussed here https://github.com/vloschiavo/powerwall2. - but don't know how to work around this in the script.

Does anyone have another way that I can connect with logic machine using the IP address to get around this (SSL certificate?) issue?

Many thanks in advance

Kind Regards
James
Reply
#2
Further to my earlier post, the LUA code above was modelled on the CURL call below (which still works to retrieve AuthCookie / Login Token)

Code:
1234567891011
curl -s -k -i -c cookie.txt -X POST -H "Content-Type: application/json" -d "{\"username\":\"customer\",\"password\":\"MYPASS\", \"email\":\"MY@EMAIL.COM\",\"force_sm_off\":false}" "https://POWERWALLGATEWAYIP/api/login/Basic"which returns HTTP/2 200 cache-control: no-cache, no-store set-cookie: AuthCookie=Tm21w<SNIP>LQfX_Jfcvd8A==; Path=/ set-cookie: UserRecord=eyJlb<SNIP>ifQ==; Path=/ content-type: text/plain; charset=utf-8 content-length: 259 date: Fri, 02 Jul 2021 15:04:27 GMT


Can anyone see how the LUA version of this is different and why it would return a nil value


Kind Regards
James
Reply
#3
Try adding verbose flag to curl (-v) and post what you get. I suspect it might be possible that either HTTP/2 or TLS v1.3 is required.
Reply
#4
I got a very similar response with -v

curl -s -k -i -c -v cookie.txt -X POST -H "Content-Type: application/json" -d "{\"username\":\"customer\",\"password\":\"MYPASS\", \"email\":\"MY@EMAIL.COM\",\"force_sm_off\":false}" "https://POWERWALLGATEWAYIP/api/login/Basic"

… which returned

Code:
123456789
HTTP/2 200 cache-control: no-cache, no-store set-cookie: AuthCookie=ZKHyR<SNIP>kJg==; Path=/ set-cookie: UserRecord=eyJl<SNIP>jAwIn0=; Path=/ content-type: text/plain; charset=utf-8 content-length: 260 date: Fri, 02 Jul 2021 16:01:48 GMT {"email":"MY@EMAIL.COM,"firstname":"Tesla","lastname":"Energy","roles":["Home_Owner"],"token":"ZKHyR<SNIP>kJg==","provider":"Basic","loginTime":"2021-07-03T02:01:48.728093492+10:00"}%


Does the HTTP/2 mean a different protocol is needed to send the request with LogicMachine?

Kind Regards
James
Reply
#5
-v should be placed before -c, also try adding --http1.1 right after -v
Reply
#6
That returned a little more info

curl -s -k -i -v --http1.1 -c / ....

Code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
james@mac ~ % curl -s -k -i -v --http1.1 -c /Users/james/Desktop/cookie.txt -X POST -H "Content-Type: application/json" -d "{\"username\":\"customer\",\"password\":\"MYPASS\", \"email\":\"MY@EMAIL.COM\",\"force_sm_off\":false}" "https://POWERWALLGATEWAYIP/api/login/Basic" *   Trying POWERWALLGATEWAYIP... * TCP_NODELAY set * Connected to POWERWALLGATEWAYIP (POWERWALLGATEWAYIP) port 443 (#0) * ALPN, offering http/1.1 * successfully set certificate verify locations: *   CAfile: /etc/ssl/cert.pem   CApath: none * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-ECDSA-CHACHA20-POLY1305 * ALPN, server accepted to use http/1.1 * Server certificate: *  subject: C=US; ST=California; L=Palo Alto; O=Tesla; OU=Tesla Energy Products; CN=2c089aa83d18b2a35fb271e0a61d035fstart date: Jul 14 22:52:25 2018 GMTexpire date: Jul  8 22:52:25 2043 GMTissuer: C=US; ST=California; L=Palo Alto; O=Tesla; OU=Tesla Energy Products; CN=2c089aa83d18b2a35fb271e0a61d035fSSL certificate verify result: self signed certificate (18), continuing anyway. > POST /api/login/Basic HTTP/1.1 > Host: POWERWALLGATEWAYIP > User-Agent: curl/7.64.1 > Accept: */* > Content-Type: application/json > Content-Length: 96 > * upload completely sent off: 96 out of 96 bytes < HTTP/1.1 200 OK HTTP/1.1 200 OK < Cache-Control: no-cache, no-store Cache-Control: no-cache, no-store * Added cookie AuthCookie="Or7INl<SNIP>aDQ==" for domain POWERWALLGATEWAYIP, path /, expire 0 < Set-Cookie: AuthCookie=Or7INlCz<SNIP>XPaDQ==; Path=/ Set-Cookie: AuthCookie=Or7INlC<SNIP>DQ==; Path=/ * Added cookie UserRecord="eyJlb<SNIP>wIn0=" for domain POWERWALLGATEWAYIP, path /, expire 0 < Set-Cookie: UserRecord=eyJlb<SNIP>AwIn0=; Path=/ Set-Cookie: UserRecord=eyJlbW<SNIP>wIn0=; Path=/ < Date: Fri, 02 Jul 2021 16:11:53 GMT Date: Fri, 02 Jul 2021 16:11:53 GMT < Content-Length: 260 Content-Length: 260 < Content-Type: text/plain; charset=utf-8 Content-Type: text/plain; charset=utf-8 < * Connection #0 to host POWERWALLGATEWAYIP left intact {"email":"MY@EMAIL.COM","firstname":"Tesla","lastname":"Energy","roles":["Home_Owner"],"token":"Or7I<SNIP>aDQ==","provider":"Basic","loginTime":"2021-07-03T02:11:53.028512297+10:00"}* Closing connection 0

Any further ideas?

Kind Regards
James
Reply
#7
If I drop the -k, the curl call fails in the same way as the Lua call

Is there any way to force my SHAC / lua to allow insecure server connections when using SSL like with cURL

Kind Regards
James
Reply
#8
HTTPS certificate verification is disabled by default. You can try sending a request using raw sockets to check where the remote host closes the connection:
Code:
123456789101112131415161718192021222324252627282930313233343536373839404142
IP = '192.168.1.9' data = '{"username":"customer","password":"MYPASS","email":"MY@EMAIL.COM","force_sm_off":false}' socket = require('socket') ssl = require('ssl') params = {   mode = 'client',   protocol = 'tlsv1_2',   verify = 'none', } client = socket.tcp() client:settimeout(1) res, err = client:connect(IP, 443) log('connect', res, err) ctx = ssl.newcontext(params) client = ssl.wrap(client, ctx) client:settimeout(1) res, err = client:dohandshake() log('dohandshake', res, err) res, err = client:send( 'POST /api/login/Basic HTTP/1.1' .. '\r\n' .. 'Host: ' .. IP .. '\r\n' .. 'User-Agent: curl/7.64.1' .. '\r\n' .. 'Accept: */*' .. '\r\n' .. 'Connection: close' .. '\r\n' .. 'Content-Type: application/json' .. '\r\n' .. 'Content-Length: ' .. #data .. '\r\n\r\n' .. data) log('send', res, err) res, err, partial = client:receive('*a') log('receive', res, err, partial) client:close()
Reply
#9
That works and I can grab the AuthCookie as follows

res:match('(AuthCookie=[^;]+)'..';')

Many thanks!


My previous logic to retrieve info from the various API endpoints on the Powerwall also needs to be updated to pass the AuthCookie.

How can I use client : send to send the cooke as part of a GET request?

And also is there an easy way to return the result to a table to allow easy extraction of the returned data?


Here's the old code I'm trying to update

Code:
12345678910111213141516171819202122232425262728293031323334
requestURL = 'https://POWERWALLGATEWAYIP/api/meters/aggregates' requestData = '' requestPostResponse = {} token = GetUserParam('Wired', 'TeslaPowerwallLocalAccessToken') res, err, commandPostHeaders, status = https.request   {     url = requestURL,     method = 'GET',     protocol = 'tlsv12',     headers =       {         cookie = token,         ['Accept'] = '*/*',         ['Content-Type'] = 'application/json',         ['Content-Length'] = requestData:len()       },     source = ltn12.source.string(requestURL),          sink = ltn12.sink.table(requestPostResponse)   }     if err == 200 then       mydata = json.pdecode(table.concat(requestPostResponse))     teslaPowerwallPercentCharged = mydata.percentage       else     log('Error retrieving Tesla Powerwall Battery Charge ' .. tostring(err))     getTeslaPowerwallLocalAccessToken();   end


Many thanks

Kind Regards
James
Reply
#10
I've tried the following 

Code:
12345678910111213141516171819202122232425262728293031
  socket = require('socket')   ssl = require('ssl')     client = socket.tcp()   client:settimeout(1)   ip = 'POWERWALLGATEWAYIP'   token = GetUserParam('Wired', 'TeslaPowerwallLocalAccessToken')   params = {mode='client', protocol='tlsv1_2',verify='none',}       res, err = client:connect(ip, 443)   ctx = ssl.newcontext(params)   client = ssl.wrap(client, ctx)   client:settimeout(1)   res, err = client:dohandshake()   res, err = client:send(   'GET /api/system_status/soe HTTP/1.1' .. '\r\n' ..   'Host: ' .. ip .. '\r\n' ..   'Set-Cookie: ' .. token .. '\r\n' ..      'User-Agent: curl/7.64.1' .. '\r\n' ..   'Accept: */*' .. '\r\n' ..   'Connection: close' .. '\r\n\r\n'    )   res, err, partial = client:receive('*a')   log('receive', res, err, partial)   client:close()

however the way I'm passing the AuthCookie token doesn't seem correct as this is the error I get back from the Powerwall

Code:
123456789101112131415
* arg: 1   * string: receive * arg: 2   * string: HTTP/1.1 403 Forbidden Content-Type: application/json X-Content-Type-Options: nosniff Date: Mon, 05 Jul 2021 13:44:08 GMT Content-Length: 102 Connection: close {"code":403,"error":"Unable to GET to resource","message":"User does not have adequate access rights"} * arg: 3   * nil * arg: 4   * nil

How should the cookie / token value be set in the request header?

Kind Regards
James
Reply
#11
Replace "Set-Cookie" header with "Cookie"
Reply
#12
That works!

What's the best way to parse the response into a table so I can easily extract the values?

For example:

battery.instant_power
solar.instant_power
site.instant_power
load.instant_power

Here's a sample response

Code:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
* string: HTTP / 1.1 200 OK Cache - Control: no - cache, no - store Cache - Control: no - cache, no - store Content - Type: application / json Date: Mon, 05 Jul 2021 14: 03: 05 GMT Connection: close Transfer - Encoding: chunked 8 b8 {     "site": {         "last_communication_time": "2021-07-06T00:03:05.470976626+10:00",         "instant_power": -28.579999923706055,         "instant_reactive_power": -1695.0799560546875,         "instant_apparent_power": 1695.3208763576292,         "frequency": 50,         "energy_exported": 1540273.3280457454,         "energy_imported": 40741163.06999019,         "instant_average_voltage": 244.44000244140625,         "instant_total_current": 0,         "i_a_current": 0,         "i_b_current": 0,         "i_c_current": 0,         "last_phase_voltage_communication_time": "0001-01-01T00:00:00Z",         "last_phase_power_communication_time": "0001-01-01T00:00:00Z",         "timeout": 1500000000,         "num_meters_aggregated": 1     },     "battery": {         "last_communication_time": "2021-07-06T00:03:05.490090497+10:00",         "instant_power": 1450,         "instant_reactive_power": 10,         "instant_apparent_power": 1450.0344823486096,         "frequency": 49.999,         "energy_exported": 7754520,         "energy_imported": 8834250,         "instant_average_voltage": 244.20000000000002,         "instant_total_current": -33.6,         "i_a_current": 0,         "i_b_current": 0,         "i_c_current": 0,         "last_phase_voltage_communication_time": "0001-01-01T00:00:00Z",         "last_phase_power_communication_time": "0001-01-01T00:00:00Z",         "timeout": 1500000000,         "num_meters_aggregated": 2     },     "load": {         "last_communication_time": "2021-07-06T00:03:05.469970299+10:00",         "instant_power": 1461.8383691537629,         "instant_reactive_power": -1656.2864201852196,         "instant_apparent_power": 2209.1301734438607,         "frequency": 50,         "energy_exported": 0,         "energy_imported": 36597478.81583333,         "instant_average_voltage": 244.44000244140625,         "instant_total_current": 5.980356547837028,         "i_a_current": 0,         "i_b_current": 0,         "i_c_current": 0,         "last_phase_voltage_communication_time": "0001-01-01T00:00:00Z",         "last_phase_power_communication_time": "0001-01-01T00:00:00Z",         "timeout": 1500000000     },     "solar": {         "last_communication_time": "2021-07-06T00:03:05.469970299+10:00",         "instant_power": 2.609999895095825,         "instant_reactive_power": 57.65999984741211,         "instant_apparent_power": 57.71904089514971,         "frequency": 50,         "energy_exported": 10814591.854183536,         "energy_imported": 12338272.780294647,         "instant_average_voltage": 244.47000122070312,         "instant_total_current": 0,         "i_a_current": 0,         "i_b_current": 0,         "i_c_current": 0,         "last_phase_voltage_communication_time": "0001-01-01T00:00:00Z",         "last_phase_power_communication_time": "0001-01-01T00:00:00Z",         "timeout": 1500000000,         "num_meters_aggregated": 1     } } 0

Many thanks

Kind Regards
James
Reply
#13
Try this, might need some adjustments to extract the JSON part from the response.
Code:
1234567891011121314151617181920
lines = res:trim():split('\r\n') -- remove header lines and chunk size repeat   line = table.remove(lines, 1)   if line == '' then     table.remove(lines, 1)     break   end until #lines == 0 -- remove last line lines[ #lines ] = nil data = table.concat(lines) data = require('json').pdecode(data) log(data.battery.instant_power) log(data.solar.instant_power) log(data.site.instant_power) log(data.load.instant_power)
Reply
#14
hey there, this appears to work for you and I hope it is still working. I am new to powerwall and am using a shac to control my house. I too want to be able to direct powerwall according to amber power price - which I have updating in my SHAC. what appears in this thread is therefore my missing link. However, I cannot get to first base. I've tried with "Set-Cookie" and "Cookie" and get the following error...

tesla trial 3 05.12.2021 15:04:56
* arg: 1
* string: receive
* arg: 2
* string: HTTP/1.1 403 Forbidden
Content-Type: application/json
X-Content-Type-Options: nosniff
Date: Sun, 05 Dec 2021 04:04:56 GMT
Content-Length: 102
Connection: close

{"code":403,"error":"Unable to GET to resource","message":"User does not have adequate access rights"}
* arg: 3
* nil
* arg: 4
* nil"

any ideas?

Mike
Reply


Forum Jump: