TMC is an independent, primarily volunteer organization that relies on ad revenue to cover its operating costs. Please consider whitelisting TMC on your ad blocker and becoming a Supporting Member. For more info: Support TMC
Start a Discussionhttps://teslamotorsclub.com/tmc/tags/

PowerWall and switchgear API's

Discussion in 'Tesla Energy' started by Ulmo, Jul 27, 2017.

Tags:
  1. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    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:

    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"
    
    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:

    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}
    
    In JSON:
    Code:
    powerwalls:
      0:
        PackagePartNumber "1092170-03-E"
        PackageSerialNumber "T17E0004XXX"
      1:
        PackagePartNumber: "1092170-03-E"
        PackageSerialNumber: "T17F0004XXX"
      hasSync: true
    
    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.
     
    • Informative x 3
    • Helpful x 1
    • Love x 1
  2. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    Here's some interesting code for login:
    Code:
      function i(e, t) {
       var n = this,
        r = arguments.length > 2 && void 0 !== arguments[2] && arguments[2],
        o = arguments[3];
       return function() {
        var i = (0, p.default)(u.default.mark(function i(c) {
         return u.default.wrap(function(n) {
          for (;;) switch (n.prev = n.next) {
           case 0:
            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", {
             method: "POST",
             credentials: "same-origin",
             headers: {
              "Content-Type": "application/json"
             },
             body: (0, d.default)({
              username: e,
              password: t,
              force_sm_off: r
             })
            }).then(A.parseText).then(A.parseResponseText).then(A.checkResponseStatus).then(function(e) {
             c(a(e)), o && o(e)
            }));
           case 4:
            n.next = 10;
            break;
           case 6:
            n.prev = 6, n.t0 = n.catch(1), c((0, M.showError)(h.RECEIVE_LOGIN_ERROR, n.t0.toString(), n.t0.response)), c(v());
           case 10:
           case "end":
            return n.stop()
          }
         }, i, n, [
          [1, 6]
         ])
        }));
        return function(e) {
         return i.apply(this, arguments)
        }
       }()
      }
    
    After skimming Tesla Model S REST API, I realized that web apps send data in RFC733/RFC822/RFC977 type headers, which I remember also HTTP uses. Obviously, the case 0: is the headers, and the body: is the body. In the body, it shows username: e, and password: t. It is function i(e,t), so I wonder where the caller gets e,t from.

    I don't know JAVA, so I'm a little baffled.

    In another thread, someone said you can provision this thing from the login ("RUN WIZARD" on home page), so that explains a lot of the provisioning stuff in the code. At this time, I have no interest in provisioning it; it was provisioned by the authorized installer.
     
    • Informative x 1
  3. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
  4. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    Found it! Thanks to wireshark and wireshark-http-gunzip.

    Code:
    GET /api/meters/aggregates HTTP/1.1
    Host: 10.1.0.15
    User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0
    Accept: */*
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    origin: http://10.1.0.15
    Referer: http://10.1.0.15/
    Connection: keep-alive
    
    HTTP/1.1 200 OK
    Access-Control-Allow-Credentials: false
    Access-Control-Allow-Credentials: false
    Access-Control-Allow-Headers: X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Accept-Encoding, Authorization
    Access-Control-Allow-Headers: X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Accept-Encoding, Authorization
    Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
    Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
    Access-Control-Allow-Origin: *
    Access-Control-Allow-Origin: *
    Access-Control-Max-Age: 86400
    Access-Control-Max-Age: 86400
    Content-Type: application/json
    Date: Thu, 27 Jul 2017 09:19:59 GMT
    Transfer-Encoding: chunked
    
    842
    {"site":{"last_communication_time":"2017-07-27T09:19:58.969380696Z","instant_power":-9.509994506835938,"instant_reactive_power":3.44000244140625,"instant_apparent_power":10.113041694610512,"frequency":60,"energy_exported":49924.15972227374,"energy_imported":18247.312222273744,"instant_average_voltage":239.76000213623047,"instant_total_current":0,"i_a_current":0,"i_b_current":0,"i_c_current":0},"battery":{"last_communication_time":"2017-07-27T09:19:58.95119964Z","instant_power":500,"instant_reactive_power":210,"instant_apparent_power":542.3098745182499,"frequency":59.938,"energy_exported":86520,"energy_imported":114950,"instant_average_voltage":239.75,"instant_total_current":-13.4,"i_a_current":0,"i_b_current":0,"i_c_current":0},"load":{"last_communication_time":"2017-07-27T09:19:58.95119964Z","instant_power":478.8129181478954,"instant_reactive_power":220.16620066112372,"instant_apparent_power":527.0056607844524,"frequency":60,"energy_exported":0,"energy_imported":149800.18611111114,"instant_average_voltage":239.76000213623047,"instant_total_current":1.9970508586992597,"i_a_current":0,"i_b_current":0,"i_c_current":0},"solar":{"last_communication_time":"2017-07-27T09:19:58.969459696Z","instant_power":-3.6699999570846558,"instant_reactive_power":18.859999656677246,"instant_apparent_power":19.213757746335492,"frequency":60,"energy_exported":210201.27472223042,"energy_imported":294.24111111929506,"instant_average_voltage":239.76000213623047,"instant_total_current":0,"i_a_current":0,"i_b_current":0,"i_c_current":0},"busway":{"last_communication_time":"0001-01-01T00:00:00Z","instant_power":0,"instant_reactive_power":0,"instant_apparent_power":0,"frequency":0,"energy_exported":0,"energy_imported":0,"instant_average_voltage":0,"instant_total_current":0,"i_a_current":0,"i_b_current":0,"i_c_current":0},"frequency":{"last_communication_time":"0001-01-01T00:00:00Z","instant_power":0,"instant_reactive_power":0,"instant_apparent_power":0,"frequency":0,"energy_exported":0,"energy_imported":0,"instant_average_voltage":0,"instant_total_current":0,"i_a_current":0,"i_b_current":0,"i_c_current":0}}
    0
    
    Now I know why so much of the Javascript seemed to be so focused on unit conversion.

    And, apparently, I don't know how to use Wireshark, still. Ugh! Annoying ... I just want the complete sequential stream, uncompressed. No simple way?
     
  5. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    #5 Ulmo, Jul 27, 2017
    Last edited: Jul 27, 2017
    It gets:
    1. /
    2. /app.hex.css
    3. /app.hex.js
    4. /vender.hex.js
    5. /favicon.ico
    6. /0.network.hex.js
    7. /hex.png
    8. /api/status
    9. fonts
    Then it loops on these three:
    1. /api/sitemaster
    2. /api/site_info
    3. /api/meters/aggregates
    I think I'll do the same thing every second and see what happens.

    Looks like the command is easy in curl:

    curl -s 'http://1.2.3.4/api/{sitemaster,site_info,meters/aggregates}'
     
  6. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    I came home from work to find many hours of JSON logs stored up. Since I don't know Java or C#, I'm pleased to find The most simple JSON parser in C for small systems for parsing the JSON data in C (which I know). Let's see what I can figure out quickly.
     
  7. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    My notes on return values for http://w.x.y.z/api/meters/aggregate :

    Meters:
    • solar=Solar Panels Inverter connection
    • battery=PowerWall connection
    • load=House subpanel connection
    • site=PG&E Grid connection (this is the least sensible name of the list)
    • busway: empty (probably relevant for other setups; for instance, perhaps for full house?)
    • frequency: empty (is this for some type of frequency monitor that I don't have or need?)
    Units:

    Let's start with an example:
    Code:
    'load':
      'last_communication_time': '2017-07-28T02:18:44.451244454Z'
      'instant_power': 1520.8155457067037
      'instant_reactive_power': -251.61099383215097
      'instant_apparent_power': 1541.4888959315865
      'frequency': 60
      'energy_exported': 0
      'energy_imported': 168136.1375
      'instant_average_voltage': 239.86000061035156
      'instant_total_current': 6.340430008491672
      'i_a_current': 0
      'i_b_current': 0
      'i_c_current': 0
    
    Units:
    • last_communication_time: ISO date YYYY-MM-DDThh:mm:ss.sssssssssZ (Z is Zulu for timezone Zero). January 1st of the Year 0001 at 00:00:00 Zulu is no data.
    • instant_power: watt (W)
    • instant_reactive_power: volt ampere reactive (var)
    • instant_apprent_power: volt ampere (VA)
    • frequency: hertz (Hz)
    • energy_exported: watt hour (Wh)
    • energy_imported: watt hour (Wh)
    • instant_average_voltage: instantaneous voltage (V inst)
    • instant_total_current: amp (A)
    I set up a MYSQL database, and am going to store the values per meter with the above field names and field values.
     
  8. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    It occurs to me I ought to use a JSON database (e.g., MongoDB); that way, whenever Tesla PowerWall changes its output, only my graphing apps break, not the database collection stuff, unless the APIs change. However, I don't know Java, and I don't know MongoDB. I really can't help anyone else be smart at this point; the pieces are there for others to do better than me at this point.
     
  9. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    I'm missing key information: I'm missing the battery level, as well as the ability to adjust its reserve setting.

    I just found this, and want more insight into how to authenticate (login?):
    Code:
      function m(e) {
       var t = e.mode,
        n = e.backupReserve,
        r = e.generationLimit,
        o = e.solarLimit,
        a = e.batteryLimit;
       return "string" == typeof r && (r = parseFloat(r)),
        "string" == typeof o && (o = parseFloat(o)), "string" == typeof a && (a = parseFloat(a)),
        function(e) {
         e(j()), e((0, S.clearError)(R.RECEIVE_SAVE_OPERATION_SETTINGS_ERROR));
         var i = {
          backup_reserve_percent: n,
          max_pv_export_power_kW: o
         };
         return t && (i.mode = t), fetch(k.default.api.uri + "/operation", {
          method: "POST",
          credentials: "same-origin",
          headers: {
           "Content-Type": "application/json"
          },
          body: (0, T.default)(i)
         }).then(D.parseText).then(D.parseResponseText).then(D.checkResponseStatus).then(function(i) {
          e(s({
           mode: t,
           backupReserve: n,
           generationLimit: r,
           solarLimit: o,
           batteryLimit: a
          }))
         }).catch(function(t) {
          e((0, S.showError)(R.RECEIVE_SAVE_OPERATION_SETTINGS_ERROR, t.toString(), t.response)), e(H())
         })
        }
    
     
  10. ulrichw

    ulrichw Member

    Joined:
    Apr 1, 2016
    Messages:
    10
    Location:
    SF Bay Area
    A couple of notes:
    1. The javascript you found (Javascript is, btw, *not* the same as Java) may not be relevant to the app - If Tesla developed a native app, the code that's running would be in the app itself. The javascript you found is used for a web interface into the Powerwall.
    2. In order to determine how to login, what you need to do is capture the traffic generated when the app connects to the Powerwall. Note that the app may be able to reuse sessions, so you may not see this every time, but I'm guessing if you exit the app and launch it again enough times, you should see a login.
    3. In order to determine how to set the level, perform this operation while your capture is running.
     
    • Helpful x 1
  11. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    #11 Ulmo, Jul 28, 2017
    Last edited: Jul 28, 2017
    Regarding #2-#3, I'm going to have to get the sniffer into position. I have to read about asking switches to send me sniff data from 1-2 switch hops away (I don't currently have a wire long enough to reposition my sniffer in such a way, and the danger of moving it would be excessive). My assumption was that controlling my PowerWall was a private affair, and it would be encrypted, so that's why I didn't yet try that.

    Ahh, the days of youth, when everything was an arm's reach away, and we longed for the days of age when we could stretch our legs out. I don't miss them. I like this problem :D

    Regarding #1, I know, but since I'm on a hunt, I'm willing to look at any clue, relevant or not.

    I think knowing the protocol the Mothership (Tesla HQ) communicates to the PowerWall would be good to know. I look forward to figuring out how to capture this.
     
  12. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    History for one week showed up in the app. It took 7 days for that to happen. Meanwhile, the days more than one day prior weren't available. Also, I can't zoom in.
     
  13. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    So, recap of what I've gotten so far:
    • No state of charge data; probably need deeper hacking, sniffing, etc.
    • No control interface (would be derivative of above)
    • Manual polling of flow data is working; I save it however I want; right now into MySQL.
      • Graphing it: my learning curve on Splunk is really annoying me (I blame the point and click interface). Luckily, a quick search brings up many, many alternatives. So far with Splunk:
        • The wrong item showed up as the X-axis; I'm trying to select the X axis. No way to do it.
        • Having trouble selecting what fields to graph.
        • Generally very frustrating.
        • Probably going to try another graphing software.
     
  14. zedkyuu

    zedkyuu Member

    Joined:
    Jul 5, 2016
    Messages:
    81
    Location:
    Castro Valley, CA
    I started to do something similar, but didn't know about beautifiers, and mostly just searched through the JS. Great work so far!

    I did run into the auth stuff as well and wondered if supplying the installer credentials might help with that. But it sounds like doing the setup workflow immediately decommissions the system, which is something I don't want to do yet. SOC data would be very handy.

    I also kind of wonder if there's a way to get the firmware off the gateway, like if it's stored on an SD card, or USB drive, or similar. Knowing my luck, though, it's probably a NAND part soldered to the board.
     
  15. ulrichw

    ulrichw Member

    Joined:
    Apr 1, 2016
    Messages:
    10
    Location:
    SF Bay Area
    Sorry - I didn't read your previous messages carefully enough - I thought you were already sniffing traffic between the app and the Powerwall. I was quite surprised that this was going over an unencrypted connection, but figured since it's on your LAN, Tesla may have thought that the protection would come from your LAN's security.

    You're probably right that the app traffic would be unencrypted.

    You're not going to be able to sniff 3rd party traffic on a switch, typically.

    The best way to sniff is probably to sniff the wireless traffic - see, for example: ::wolfhoundsec::: Monitoring WPA2 wireless traffic w/ Wireshark

    If the connection is using https: you're going to be kind of screwed - sniffing will be much more complicated. Here's a possible starting point: How to debug HTTP(s) traffic on Android – Sebastiano Gottardo – Medium
     
  16. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    #16 Ulmo, Jul 30, 2017
    Last edited: Jul 30, 2017
    At least I have my first graph of power flows, if not state of charge of batteries:
    power.png

    Done via curl http://x/api/meters/aggregates, jsmn, bash, mysql, bash, gnuplot.
     
    • Helpful x 1
    • Like x 1
  17. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    • Like x 1
  18. MichaelVorst

    MichaelVorst Member

    Joined:
    Apr 6, 2016
    Messages:
    6
    Location:
    Sydney, Australia
    For those of us who can't speak "jsmn, bash, mysql, bash, gnuplot" there is a pre-rolled logging and display service called PVOutput (Latest Outputs).

    They have a local integration service that can run on Windows PC, grabbing files from other monitoring systems, but no-one has yet written a parser for the PW2 output.
     
    • Informative x 1
  19. Ulmo

    Ulmo Active Member

    Joined:
    Jan 19, 2016
    Messages:
    2,367
    Location:
    Vienna Woods, Aptos, California
    I was able to figure out the Tesla mothership app server API for getting the level of charge. Here's my post about it:

    Model S REST API

    They show up as orange dots on my graphs that I'm currently sharing with the public while we all figure this stuff out: Ulmo.Solar I was inspired by someone else to follow their website naming convention; it's purely from respect, not plagiarism, but I do intentionally copy the best.

    I still want to know how to authorize to get more data (state of charge), and control my PowerWalls, from both places (LAN and the mothership).
     
    • Like x 1
  20. mr blue sky

    mr blue sky Member

    Joined:
    Jul 13, 2016
    Messages:
    31
    Location:
    California
    Regarding the "User does not have adequate access rights," I looked at the source code for the landing page on the provisioning wizard. It references two js files one of which (on my system anyway) is at /app.94070648a056d37c80dc.js
    This one references:
    fetch(k.default.api.uri+"/operation",{method:"POST",credentials:"same-origin"

    I believe this means it is using a cookie based login. You might be able to get the cookie set in your local browser by logging into the wizard and commissioning your system. I assume you would though have to create a specially formed URL request to make sure that credentials:"same-origin" is passed. Remember the login to the wizard is any e-mail address, password is the gateway serial number in all caps.

    You might also be able to just borrow this js file and use it for authentication since it looks like this script also handles logging in via
    /login/Basic and setting "AuthCookie". Then you can use Chrome's developer functions to examine the auth cookie. When I did this, it just set a seemingly random login string as the AuthCookie value, expiry is "session."
     
    • Informative x 1

Share This Page