I'm trying to log the data from my PowerWalls; the Tesla App is insufficient by leaps and bounds (no history past yesterday, not detailed enough, etc.).
On the local Tesla switch IP#, I did "webcopy -r 1.2.3.4" (where 1.2.3.4 is the IP# of the Tesla switch (the box the Ethernet cable is plugged into) on my LAN) and got a directory structure like this:
where I wrote "hexnumberxnibbles" was a hexadecimal number of x nibbles.
I then took app.hexnumber20nibbles.js and put it through beautifier at Best Online JavaScript Tool to View,Beautify,Formatter,Minify,Obfuscator Javascript Code and got a file I can look at in Emacs. On line 2,066 of that file is the following:
Later in the file are things like api.uri such as default.api.uri. Here are some:
I went to the URL http://1.2.3.4/api/site_info and got this:
Note that it seems to want to talk to the Smart Meter a bit in the code, or seems to. Note that IEEE1547:2003 is IEEE 1547 which is "IEEE 1547 Standard for Interconnecting Distributed Resources with Electric Power Systems"; I wonder what interconnection information we could get that way? Anyway, here's some of the other api responses:
http://1.2.3.4/api/operation returns:
What access rights do I need? Is there a key?
Just now, I realized that there are three tabs in Firefox: JSON, Raw Data, and Headers. Firefox automatically selected JSON. For instance, for api/sitemaster, I got in JSON:
In Raw Data, I got:
I think I like the Raw Data format better programmatically, but for simplicity of understanding, I think I'll stick to JSON, unless the response is not very useful, in which case, I'll just write the Raw Data.
Headers gives me:
Those headers sure tell us a lot about what kind of authorizations it wants.
I'm going to keep showing some more URI outputs:
api/meters:
I get the same response for api/networks and api/system/networks.
Here's api/powerwalls:
In JSON:
XXX were numbers corresponding to my equipment serial numbers.
api/solar was unauthorized.
I'm going to keep looking at this Javascript until I find out where it gets the readings from in the display that I see when I go to its home page.
On the local Tesla switch IP#, I did "webcopy -r 1.2.3.4" (where 1.2.3.4 is the IP# of the Tesla switch (the box the Ethernet cable is plugged into) on my LAN) and got a directory structure like this:
Code:
app.hexnumber20nibbles.js
app.hexnumber32nibbles.css
favicon-16x16.png
favicon-32x32.png
favicon.ico
index.html
robots.txt
src/assets/fonts/Gotham-Black.otf
src/assets/fonts/Gotham-BlackIta.otf
src/assets/fonts/Gotham-Bold.otf
src/assets/fonts/Gotham-BoldIta.otf
src/assets/fonts/Gotham-Book.otf
src/assets/fonts/Gotham-BookIta.otf
src/assets/fonts/Gotham-Light.otf
src/assets/fonts/Gotham-LightIta.otf
src/assets/fonts/Gotham-Medium.otf
src/assets/fonts/Gotham-MediumIta.otf
src/assets/fonts/Gotham-Thin.otf
src/assets/fonts/Gotham-ThinIta.otf
src/assets/fonts/Gotham-Ultra.otf
src/assets/fonts/Gotham-UltraIta.otf
src/assets/fonts/Gotham-XLight.otf
src/assets/fonts/Gotham-XLightIta.otf
vendor.hexnumber20nibbles.js
where I wrote "hexnumberxnibbles" was a hexadecimal number of x nibbles.
I then took app.hexnumber20nibbles.js and put it through beautifier at Best Online JavaScript Tool to View,Beautify,Formatter,Minify,Obfuscator Javascript Code and got a file I can look at in Emacs. On line 2,066 of that file is the following:
Code:
r.api.uri = "/api", r.static.uri = "", t.default = r
Later in the file are things like api.uri such as default.api.uri. Here are some:
Code:
$ egrep api.uri app.xxx-beautified.js
r.api.uri = "/api", r.static.uri = "", t.default = r
return e(I()), fetch(k.default.api.uri + "/site_info").then(D.checkStatus).then(D.parseJSON).then(function(t) {
return fetch(k.default.api.uri + "/site_info/site_name", {
return e(B()), fetch(k.default.api.uri + "/operation", {
return t && (i.mode = t), fetch(k.default.api.uri + "/operation", {
return e(U()), e((0, S.clearError)(R.RECEIVE_SITEMASTER_SETTINGS_ERROR)), fetch(k.default.api.uri + "/sitemaster").then(D.checkStatus).then(D.parseJSON).then(function(t) {
return e(V()), e((0, S.clearError)(R.RECEIVE_START_SITEMASTER_ERROR)), e((0, S.clearError)(R.RECEIVE_STOP_SITEMASTER_ERROR)), fetch(k.default.api.uri + "/sitemaster", {
return e(K()), e((0, S.clearError)(R.RECEIVE_START_SITEMASTER_ERROR)), e((0, S.clearError)(R.RECEIVE_STOP_SITEMASTER_ERROR)), fetch(k.default.api.uri + "/sitemaster", {
return t($()), fetch(k.default.api.uri + "/site_info/timezone", {
return c(L()), n.prev = 1, n.next = 4, (0, A.timeoutPromise)(A.REQUEST_TIMEOUT, new Error("Request timed out"), fetch(b.default.api.uri + "/login/Basic", {
return t(T()), fetch(b.default.api.uri + "/logout", {
return e(Z()), e((0, P.clearError)(B.RECEIVE_DETECT_METER_ERROR)), fetch(U.default.api.uri + "/meters/detect_wired_meters", {
return n(ee()), fetch(U.default.api.uri + "/meters/" + t, {
return o(ne()), o((0, P.clearError)(B.RECEIVE_CREATE_METER_ERROR)), o((0, P.clearError)(B.RECEIVE_COMMISSION_METER_ERROR)), fetch(U.default.api.uri + "/meters", {
return a(oe()), a((0, P.clearError)(B.RECEIVE_COMMISSION_METER_ERROR)), r.abrupt("return", fetch(U.default.api.uri + "/meters/" + n + "/commission", {
return t(ie()), fetch(U.default.api.uri + "/meters", {
return fetch(U.default.api.uri + "/meters/" + o.serial + "/cts", {
return t(le()), fetch(U.default.api.uri + "/meters/" + e + "/cts", {
return e(fe()), fetch(U.default.api.uri + "/meters/readings", {
return n(Me()), fetch(U.default.api.uri + "/meters/" + e + "/invert_cts", {
return e(he()), fetch(U.default.api.uri + "/meters/aggregates").then(F.checkStatus).then(F.parseText).then(F.parseResponseText).then(F.checkResponseStatus).then(function(t) {
return e.abrupt("return", fetch(U.default.api.uri + "/meters/" + n + "/verify", {
return e.abrupt("return", fetch(U.default.api.uri + "/meters/status", {
return e(y()), e((0, M.clearError)(h.RECEIVE_CONFIG_INITIALIZED_ERROR)), fetch(b.default.api.uri + "/status").then(A.parseText).then(A.parseResponseText).then(A.checkResponseStatus).then(function(t) {
return n(v()), n((0, M.clearError)(h.RECEIVE_SYNC_CONFIG_ERROR)), e.prev = 2, e.next = 5, (0, A.timeoutPromise)(A.REQUEST_TIMEOUT, new Error("Config sync timed out"), fetch(b.default.api.uri + "/config/completed", {
return e.abrupt("return", fetch(b.default.api.uri + "/config", {
return e(re()), fetch(J.default.api.uri + "/networks", {
return e(oe()), fetch(J.default.api.uri + "/system/networks", {
return e(ie()), fetch(J.default.api.uri + "/networks/wifi_security_types", {
return e(ce()), fetch(J.default.api.uri + "/networks/wifi_scan", {
return e(ue()), fetch(J.default.api.uri + "/networks/wifi_scan", {
return n && (0, ee.requiresPassword)(e) && (a.password = n), r && (0, ee.requiresUsername)(e) && (a.username = r), fetch(J.default.api.uri + "/networks", {
return r(Me()), fetch(J.default.api.uri + "/networks/" + e, {
return t(_e()), fetch(J.default.api.uri + "/networks/" + e, {
return fetch(J.default.api.uri + "/networks/" + e + "/disable", {
return te = 0, e.abrupt("return", fetch(J.default.api.uri + "/networks/" + t + "/enable", {
return e.abrupt("return", fetch(J.default.api.uri + "/system/networks/ping_test", {
return e.abrupt("return", fetch(J.default.api.uri + "/system/networks", {
return e(m()), e((0, l.clearError)(f.RECEIVE_POWERWALLS_ERROR)), (0, u.default)(M.default.api.uri + "/powerwalls", {
return (0, u.default)(M.default.api.uri + "/powerwalls", {
return e(T()), fetch(y.default.api.uri + "/solar", {
return e(g()), fetch(y.default.api.uri + "/solar/brands", {
return t(O()), fetch(y.default.api.uri + "/solar/brands/" + e, {
return e(S()), fetch(y.default.api.uri + "/solar", {
return e(L()), fetch(E.default.api.uri + "/site_info", {
return e(v()), fetch(E.default.api.uri + "/site_info/grid_codes", {
return n(T()), fetch(E.default.api.uri + "/site_info/grid_code", {
return e(T()), fetch(y.default.api.uri + "/customer", {
return fetch(y.default.api.uri + "/customer", {
return e(S()), fetch(y.default.api.uri + "/customer/registration/emailed", {
return e(R()), fetch(y.default.api.uri + "/customer/registration/skip", {
r.api.uri = "/api", r.static.uri = "", t.default = r
return e(I()), fetch(k.default.api.uri + "/site_info").then(D.checkStatus).then(D.parseJSON).then(function(t) {
return fetch(k.default.api.uri + "/site_info/site_name", {
return e(B()), fetch(k.default.api.uri + "/operation", {
return t && (i.mode = t), fetch(k.default.api.uri + "/operation", {
return e(U()), e((0, S.clearError)(R.RECEIVE_SITEMASTER_SETTINGS_ERROR)), fetch(k.default.api.uri + "/sitemaster").then(D.checkStatus).then(D.parseJSON).then(function(t) {
return e(V()), e((0, S.clearError)(R.RECEIVE_START_SITEMASTER_ERROR)), e((0, S.clearError)(R.RECEIVE_STOP_SITEMASTER_ERROR)), fetch(k.default.api.uri + "/sitemaster", {
return e(K()), e((0, S.clearError)(R.RECEIVE_START_SITEMASTER_ERROR)), e((0, S.clearError)(R.RECEIVE_STOP_SITEMASTER_ERROR)), fetch(k.default.api.uri + "/sitemaster", {
return t($()), fetch(k.default.api.uri + "/site_info/timezone", {
return c(L()), n.prev = 1, n.next = 4, (0, A.timeoutPromise)(A.REQUEST_TIMEOUT, new Error("Request timed out"), fetch(b.default.api.uri + "/login/Basic", {
return t(T()), fetch(b.default.api.uri + "/logout", {
return e(Z()), e((0, P.clearError)(B.RECEIVE_DETECT_METER_ERROR)), fetch(U.default.api.uri + "/meters/detect_wired_meters", {
return n(ee()), fetch(U.default.api.uri + "/meters/" + t, {
return o(ne()), o((0, P.clearError)(B.RECEIVE_CREATE_METER_ERROR)), o((0, P.clearError)(B.RECEIVE_COMMISSION_METER_ERROR)), fetch(U.default.api.uri + "/meters", {
return a(oe()), a((0, P.clearError)(B.RECEIVE_COMMISSION_METER_ERROR)), r.abrupt("return", fetch(U.default.api.uri + "/meters/" + n + "/commission", {
return t(ie()), fetch(U.default.api.uri + "/meters", {
return fetch(U.default.api.uri + "/meters/" + o.serial + "/cts", {
return t(le()), fetch(U.default.api.uri + "/meters/" + e + "/cts", {
return e(fe()), fetch(U.default.api.uri + "/meters/readings", {
return n(Me()), fetch(U.default.api.uri + "/meters/" + e + "/invert_cts", {
return e(he()), fetch(U.default.api.uri + "/meters/aggregates").then(F.checkStatus).then(F.parseText).then(F.parseResponseText).then(F.checkResponseStatus).then(function(t) {
return e.abrupt("return", fetch(U.default.api.uri + "/meters/" + n + "/verify", {
return e.abrupt("return", fetch(U.default.api.uri + "/meters/status", {
return e(y()), e((0, M.clearError)(h.RECEIVE_CONFIG_INITIALIZED_ERROR)), fetch(b.default.api.uri + "/status").then(A.parseText).then(A.parseResponseText).then(A.checkResponseStatus).then(function(t) {
return n(v()), n((0, M.clearError)(h.RECEIVE_SYNC_CONFIG_ERROR)), e.prev = 2, e.next = 5, (0, A.timeoutPromise)(A.REQUEST_TIMEOUT, new Error("Config sync timed out"), fetch(b.default.api.uri + "/config/completed", {
return e.abrupt("return", fetch(b.default.api.uri + "/config", {
return e(re()), fetch(J.default.api.uri + "/networks", {
return e(oe()), fetch(J.default.api.uri + "/system/networks", {
return e(ie()), fetch(J.default.api.uri + "/networks/wifi_security_types", {
return e(ce()), fetch(J.default.api.uri + "/networks/wifi_scan", {
return e(ue()), fetch(J.default.api.uri + "/networks/wifi_scan", {
return n && (0, ee.requiresPassword)(e) && (a.password = n), r && (0, ee.requiresUsername)(e) && (a.username = r), fetch(J.default.api.uri + "/networks", {
return r(Me()), fetch(J.default.api.uri + "/networks/" + e, {
return t(_e()), fetch(J.default.api.uri + "/networks/" + e, {
return fetch(J.default.api.uri + "/networks/" + e + "/disable", {
return te = 0, e.abrupt("return", fetch(J.default.api.uri + "/networks/" + t + "/enable", {
return e.abrupt("return", fetch(J.default.api.uri + "/system/networks/ping_test", {
return e.abrupt("return", fetch(J.default.api.uri + "/system/networks", {
return e(m()), e((0, l.clearError)(f.RECEIVE_POWERWALLS_ERROR)), (0, u.default)(M.default.api.uri + "/powerwalls", {
return (0, u.default)(M.default.api.uri + "/powerwalls", {
return e(T()), fetch(y.default.api.uri + "/solar", {
return e(g()), fetch(y.default.api.uri + "/solar/brands", {
return t(O()), fetch(y.default.api.uri + "/solar/brands/" + e, {
return e(S()), fetch(y.default.api.uri + "/solar", {
return e(L()), fetch(E.default.api.uri + "/site_info", {
return e(v()), fetch(E.default.api.uri + "/site_info/grid_codes", {
return n(T()), fetch(E.default.api.uri + "/site_info/grid_code", {
return e(T()), fetch(y.default.api.uri + "/customer", {
return fetch(y.default.api.uri + "/customer", {
return e(S()), fetch(y.default.api.uri + "/customer/registration/emailed", {
return e(R()), fetch(y.default.api.uri + "/customer/registration/skip", {
I went to the URL http://1.2.3.4/api/site_info and got this:
Code:
site_name: "Home Energy Gateway"
timezone: "America/Los_Angeles"
min_site_meter_power_kW: -1000000000
max_site_meter_power_kW: 1000000000
nominal_system_energy_kWh: 13.5
grid_code: "60Hz_240V_s_IEEE1547:2003"
grid_voltage_setting: 240
grid_freq_setting: 60
grid_phase_setting: "Split"
country: "United States"
Note that it seems to want to talk to the Smart Meter a bit in the code, or seems to. Note that IEEE1547:2003 is IEEE 1547 which is "IEEE 1547 Standard for Interconnecting Distributed Resources with Electric Power Systems"; I wonder what interconnection information we could get that way? Anyway, here's some of the other api responses:
http://1.2.3.4/api/operation returns:
Code:
code 403
error "Unable to GET to resource"
message "User does not have adequate access rights"
Just now, I realized that there are three tabs in Firefox: JSON, Raw Data, and Headers. Firefox automatically selected JSON. For instance, for api/sitemaster, I got in JSON:
Code:
running true
uptime "7706s,"
connected_to_tesla true
In Raw Data, I got:
Code:
{"running":true,"uptime":"7706s,","connected_to_tesla":true}
I think I like the Raw Data format better programmatically, but for simplicity of understanding, I think I'll stick to JSON, unless the response is not very useful, in which case, I'll just write the Raw Data.
Headers gives me:
Code:
Access-Control-Allow-Methods GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
Access-Control-Allow-Origin *
Access-Control-Max-Age 86400
Content-Encoding gzip
Content-Length 80
Content-Type application/json
Date Thu, 27 Jul 2017 07:36:54 GMT
access-control-allow-credentials false
access-control-allow-headers X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Accept-Encoding, Authorization
Those headers sure tell us a lot about what kind of authorizations it wants.
I'm going to keep showing some more URI outputs:
api/meters:
Code:
{"code":403,"error":"Unable to GET to resource","message":"User does not have adequate access rights"}
I get the same response for api/networks and api/system/networks.
Here's api/powerwalls:
Code:
{"powerwalls":[{"PackagePartNumber":"1092170-03-E","PackageSerialNumber":"T17E0004XXX"},{"PackagePartNumber":"1092170-03-E","PackageSerialNumber":"T17F0004XXX"}],"hasSync":true}
Code:
powerwalls:
0:
PackagePartNumber "1092170-03-E"
PackageSerialNumber "T17E0004XXX"
1:
PackagePartNumber: "1092170-03-E"
PackageSerialNumber: "T17F0004XXX"
hasSync: true
api/solar was unauthorized.
I'm going to keep looking at this Javascript until I find out where it gets the readings from in the display that I see when I go to its home page.