Integration Service not working for Powerwall SW Ver. 20.49.0

Powerwall with SW Ver. 20.49.0 requires login to access API
Script Logs error 453
2021-02-08 14:05:29,819 INFO [Thread-2] (WebClient.java:243) - >>> https://192.168.0.xxx:443/api/meters/aggregates
2021-02-08 14:05:29,835 ERROR [Thread-2] (AHttpLogReader.java:453) - <<< [403] {“code”:403,“error”:“Unable to GET to resource”,“message”:“User does not have adequate access rights”}

Yes, I’ve been hit with this one also - upgrade to 20.49.0 must have happened around 5:30am this morning.
No data is being collected by the PVO integration Windows scripts any more, however the IP address of the gateway is still active and the interactive GUI display on that IP address still runs (after interactively logging in).
After logging in, https://192.168.0.63/api/meters/aggregates then does produce data.
I figured as a workaround I might log into the Windows VM that is running the PVO service, and fire up a webbrowser to log into the gateway from that IP address - but the VM is not playing ball due to a unrelated issue with display drivers I think.
If you can set up a web browser from the IP address running the PVO service and log in, data may start flowing again.

Unsure why, but after setting up a web browser on the same monitor IP address, logging in, and displaying the ‘aggregates’ URL fine - the monitor service (where the service is fetching URLs from the same source IP address) is still not able to retrieve statistics.

Looks like you need to send a cookie with the get request now…

This may take a while to fix

ugh, this is bad. any idea if this cookie is something generated with each login session or is static?

Does this update come with release notes on the security changes?

If so please post it over so the Integration Service can be updated to support this login requirement.

Some useful information below, lacking official documentation -

The change may be the /meter/aggregate command now requires authentication. (See POST /api/login/Basic) section above.

Need a volunteer with curl installed with access to the gateway to run -

curl -s -i -X POST -H "Content-Type: application/json" -d '{"username":"your_gateway_login","password":"your_gateway_password","force_sm_off":false}' https://192.168.0.xxx/api/login/Basic

The gateway should respond with some data with a token -

e.g.

{"email":null,"firstname":"Tesla","lastname":"Energy","roles":["Provider_Engineer"],"token":"OgiGHjoNvwx17SRIaYFIOWPJSaKBYwmMGc5K4tTz57EziltPYsdtjU_DJ08tJqaWbWjTuI3fa_8QW32ED5zg1A==","provider":"Basic"}

Please note the warning from the site above on running the login command.

Authentication example: Note: Getting an authentication token will stop the powerwall. It won’t charge, discharge, or collect stats on v1.15+. Therefore you should re-enable the powerwall after getting a token.
See: the /api/sitemaster/run section above.

with -s there’s no output from curl.
w/o -s curl reports a certificate error.
running curl with -k seems to bypass the certificate check. maybe not a good idea but for testing should be OK.

unfortunately i’m getting

HTTP/1.1 401 Unauthorized

**Content-Type** : application/json

**X-Content-Type-Options** : nosniff

**Date** : Wed, 10 Feb 2021 01:48:25 GMT

**Content-Length** : 62

{"code":401,"error":"bad credentials","message":"Login Error"}

back from curl - i’m pretty sure i have the password right.

ok - the problem is that it should not be “username”:"<your_gateway_login>" but “username”:“customer”

literally the word “customer” - from reading another forum on this problem they don’t actually care about the login name - they want to know if you are trying to log in as a customer or an installer. then they just check the password.

the system did not stop per the powerwall internal web server, so they must have fixed that.

do you need the output from this or are you expecting this to make the current code start working since an authorization has occurred?

Thanks to trying this, did you get a token back? Please post the response and redact your token.

i did, yes, i redacted it but didn’t post since i wasn’t sure if you needed it:

Set-Cookie: AuthCookie=<a_cookie (looks like base64)>; Path=/

Set-Cookie: UserRecord=<more stuff, but no trailing ==, maybe not base64>; Path=/

Date: Wed, 10 Feb 2021 01:57:46 GMT

Content-Length: 243

Content-Type: text/plain; charset=utf-8

{"email":"","firstname":"Tesla","lastname":"Energy","roles":["Home_Owner"],"token":"<base64 token>","provider":"Basic","loginTime":"2021-02-09T17:57:46.479577511-08:00"}

Great, now try -

curl -k -H "Authorization: Bearer <base64 token>" https://192.168.xxx.xxx/api/meters/aggregates

Running without the token would give the current error.

…/aggregates

{“code”:401,“error”:“token contains an invalid number of segments”,“message”:“Invalid m2m/bearer token”}

Usually means the token is invalid, there shouldn’t be any < > in the token

"Authorization: Bearer OgiGHjoNvwx17SRIaYFIOWPJSaKBYwmMGc5K4tTz57EziltPYsdtjU_DJ08tJqaWbWjTuI3fa_8QW32ED5zg1A=="

Bit of topic, but hope this helps - makeshift py script just to keep things updated until problem is solved. Using python implementation of the Powerwall API https://github.com/jrester/tesla_powerwall and parts from @ekul_135 's Powerwall2PVOutput
Works with the firmware 20.49.0
! Please verify the mapping of extended parameters to your PVOutput system

from tesla_powerwall import Powerwall
from tesla_powerwall import MeterType
from tesla_powerwall import API
import os
import datetime
import time
import urllib
import http.client
import ssl
import sys
import logging
from logging.handlers import RotatingFileHandler
from logging import handlers

log_file = "pw2pvo.log"
pvo_host= "pvoutput.org"
pvo_key = "<your PVOutput System key>"
pvo_sysid = "<your PVOutput System ID>"
pwemail = "<Your Powerwall Customer Email>"
pwpass = "<Your Powerwall Customer Password>"
powerwall = Powerwall("<Your Powerwall IP>")

log = logging.getLogger('')
log.setLevel(logging.INFO)
format = logging.Formatter("%(asctime)s - %(message)s")
ch = logging.StreamHandler(sys.stdout)
ch.setFormatter(format)
log.addHandler(ch)
fh = handlers.RotatingFileHandler(log_file, maxBytes=(1048576*5), backupCount=1)
fh.setFormatter(format)
log.addHandler(fh)
logger = logging.getLogger(__name__)
logger.info('Start Test')

if (not os.environ.get('PYTHONHTTPSVERIFY', '') and getattr(ssl, '_create_unverified_context', None)):
    ssl._create_default_https_context = ssl._create_unverified_context

class Connection():
    def __init__(self, api_key, system_id, host):
        self.host = host
        self.api_key = api_key
        self.system_id = system_id

    def get_status(self, date=None, time=None):
        path = '/service/r2/getstatus.jsp'
        params = {}
        if date:
            params['d'] = date
        if time:
            params['t'] = time
        params = urllib.urlencode(params)
        response = self.make_request("GET", path, params)
        if response.status == 400:
            # Initialise a "No status found"
            return "%s,00:00,,,,,,," % datetime.datetime.now().strftime('%Y%m%d')
        if response.status != 200:
            raise StandardError(response.read())
        return response.read()

    def add_status(self, date, time, energy_exp=None, power_exp=None, energy_imp=None, power_imp=None, temp=None, voltage=None, battery=None, home=None, soc=None, gird=None, frequency=None, solar=None, cumulative=False):
        path = '/service/r2/addstatus.jsp'
        params = {
                'd': date,
                't': time
                }
        if energy_exp:
            params['v1'] = energy_exp
        if power_exp:
            params['v2'] = power_exp
        if energy_imp:
            params['v3'] = energy_imp
        if power_imp:
            params['v4'] = power_imp
        if temp:
            params['v5'] = temp
        if voltage:
            params['v6'] = voltage
        if battery:
            params['v7'] = battery
        if home:
            params['v8'] = home
        if soc:
            params['v11'] = soc    
        if gird:
            params['v10'] = gird
        if frequency:
            params['v9'] = frequency
        if solar:
            params['v12'] = solar    
        if cumulative:
            params['c1'] = 1
        params = urllib.parse.urlencode(params)
        response = self.make_request('POST', path, params)
        if response.status == 400:
            raise ValueError(response.read())
        if response.status != 200:
            raise StandardError(response.read())
    
    def make_request(self, method, path, params=None):
        conn = http.client.HTTPConnection(self.host)
        headers = {
                'Content-type': 'application/x-www-form-urlencoded',
                'Accept': 'text/plain',
                'X-Pvoutput-Apikey': self.api_key,
                'X-Pvoutput-SystemId': self.system_id
                }
        conn.request(method, path, params, headers)
        return conn.getresponse()


# Identify the powerwall version
powerwall.detect_and_pin_version()
print("Detected and pinned version: {}".format(powerwall.get_pinned_version()))
if powerwall.is_authenticated():
    logger.info("Logged in")
else:
    powerwall.login(pwemail, pwpass)
logger.info("Authenticated to Powerwall")

api=powerwall.get_api()

while True:
    try:
        aGirdPower=[]
        aHomePower=[]
        aHomeVoltage=[]
        aHomeFrequency=[]
        aSolarPower=[]
        aBatteryPower=[]
        aBatteryCharge=[]
        i=0
        while i<60:      
            soe=api.get_system_status_soe()
            pwm=api.get_meters_aggregates()
            aGirdPower.append(pwm['site']['instant_power'])
            aHomePower.append(pwm['load']['instant_power'])
            aHomeVoltage.append(pwm['load']['instant_average_voltage'])
            aHomeFrequency.append(pwm['load']['frequency'])
            aSolarPower.append(pwm['solar']['instant_power'])
            aBatteryPower.append(pwm['battery']['instant_power'])
            aBatteryCharge.append(soe['percentage'])
            i=i+1
            time.sleep(5)
        if len(aHomePower)>0:
            GirdPower=sum(aGirdPower,0.00)/len(aGirdPower)
            HomePower=sum(aHomePower,0.00)/len(aHomePower)
            HomeVoltage=sum(aHomeVoltage,0.00)/len(aHomeVoltage)
            HomeFrequency=sum(aHomeFrequency,0.00)/len(aHomeFrequency)
            SolarPower=sum(aSolarPower,0.00)/len(aSolarPower)
            BatteryPower=sum(aBatteryPower,0.00)/len(aBatteryPower)
            BatteryCharge=sum(aBatteryCharge,0.00)/len(aBatteryCharge)
            print(HomePower,HomeVoltage,HomeFrequency)
            print(SolarPower)
            print(GirdPower)
            print(BatteryPower,BatteryCharge)
            if SolarPower<0 :
                HomePower=HomePower-SolarPower
                SolarPower=0
            sdate=datetime.datetime.now()
            pvoDate=sdate.strftime("%Y%m%d")
            pvoTime=sdate.strftime("%H:%M")
            logger.info(str(pvoDate)+","+str(pvoTime)+","+str(SolarPower)+","+str(HomePower)+","+str(HomeVoltage)+","+str(BatteryPower)+","+str(HomePower)+","+str(BatteryCharge)+","+str(GirdPower)+","+str(HomeFrequency)+","+str(SolarPower))
            pvocon=Connection(pvo_key, pvo_sysid, pvo_host)
            pvocon.add_status(pvoDate, pvoTime, power_exp=SolarPower, power_imp=HomePower, voltage=HomeVoltage, battery=BatteryPower, home=HomePower, soc=BatteryCharge, gird=GirdPower, frequency=HomeFrequency, solar=SolarPower)
        else:
            print(str(pvoDate)+","+str(pvoTime)+", Error: No data to sent")
            logger.info(str(pvoDate)+","+str(pvoTime)+", Error: No data to sent")
    except StandardError as e:
        logger.info(str(e))
        time.sleep(30)
1 Like

no, i didn’t put any carets or anything… it just didn’t work. my syntax is exactly as you have quoted. i even logged in once more and got a new token and then immediately tried it.

1 Like

thanks, as it turns out i was using PowerWall2PVOutput and started looking into how to have it handle the login and cookie and pretty quickly realized that it was based on some pretty old stuff (urllib instead of request) and will take more python knowledge than i have to fix. i’ll give your code a try.

Try sending back just the AuthCookie returned from gateway login, no token.

curl -k --cookies "AuthCookie=<cookie_data>" https://192.168.xxx.xxx/api/meters/aggregates

Please test the following patch which will now login to the gateway -

  1. Stop the service
  2. Make a backup of file <pvoutput_install>\lib\org.pvoutput.integration.jar
  3. Download the patch -
  4. Rename from org.pvoutput.integration_1.5.5.1.jar to org.pvoutput.integration.jar
  5. Copy org.pvoutput.integration.jar to the PVOutput installation lib folder, replacing the existing file.
  6. Edit conf\powerwall.ini and add lines with gateway ip
    • login-url=https://192.168.x.x/api/login/Basic
    • login-pass=your_password
  7. Start the service

If the above works v1.5.5.1 will be released.

2 Likes

sorry - i had gone to bed (PST) but i did try this just now and i got data back from the powerwall. so passing the auth cookie does work.

i’ll try the java program but it might take me a little while as i’ve been using the python scripts up to this point.