Add support for Tesla Powerwall

Hello,

It worked one time changing the mode to backup, then failed any subsequent time.

Log Details (WARNING)

Logger: custom_components.tesla_gateway
Source: custom_components/tesla_gateway/__init__.py:103 
Integration: Tesla Gateway 
First occurred: 8:38:17 PM (4 occurrences) 
Last logged: 8:38:26 PM

Step 1: Error 200 on attempt 0, call https://auth.tesla.com/static/404.html: <html> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Tesla SSO – Page Not Found</title> <link rel="shortcut icon" href="/images/icons/favicon.ico" type="image/vnd.microsoft.icon" /> <link rel="icon" type="image/png" href="/images/icons/favicon-196x196.png" sizes="196x196?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-160x160.png" sizes="160x160?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-96x96.png" sizes="96x96?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-16x16.png" sizes="16x16?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-32x32.png" sizes="32x32?2" /> <link rel="stylesheet" href="/styles/bulma.min.css" /> <link rel="stylesheet" href="/styles/main.css" /> <style> .container { text-align: center; width: 100%; } .section { width: 100%; } </style> </head> <body class="background-spokes2 has-valigned"> <!-- <div class="layout-header"> <div class="header-topmargin"></div> <div class="header-flag"> <img src="/images/tesla_flag.png" /> </div> </div> --> <div class="container is-valigned"> <div class="section"> <h1>Page Not Found</h1> </div> </div> </body> </html>
Step 1: Error 200 on attempt 1, call https://auth.tesla.com/static/404.html: <html> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Tesla SSO – Page Not Found</title> <link rel="shortcut icon" href="/images/icons/favicon.ico" type="image/vnd.microsoft.icon" /> <link rel="icon" type="image/png" href="/images/icons/favicon-196x196.png" sizes="196x196?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-160x160.png" sizes="160x160?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-96x96.png" sizes="96x96?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-16x16.png" sizes="16x16?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-32x32.png" sizes="32x32?2" /> <link rel="stylesheet" href="/styles/bulma.min.css" /> <link rel="stylesheet" href="/styles/main.css" /> <style> .container { text-align: center; width: 100%; } .section { width: 100%; } </style> </head> <body class="background-spokes2 has-valigned"> <!-- <div class="layout-header"> <div class="header-topmargin"></div> <div class="header-flag"> <img src="/images/tesla_flag.png" /> </div> </div> --> <div class="container is-valigned"> <div class="section"> <h1>Page Not Found</h1> </div> </div> </body> </html>
Step 1: Error 200 on attempt 2, call https://auth.tesla.com/static/404.html: <html> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Tesla SSO – Page Not Found</title> <link rel="shortcut icon" href="/images/icons/favicon.ico" type="image/vnd.microsoft.icon" /> <link rel="icon" type="image/png" href="/images/icons/favicon-196x196.png" sizes="196x196?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-160x160.png" sizes="160x160?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-96x96.png" sizes="96x96?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-16x16.png" sizes="16x16?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-32x32.png" sizes="32x32?2" /> <link rel="stylesheet" href="/styles/bulma.min.css" /> <link rel="stylesheet" href="/styles/main.css" /> <style> .container { text-align: center; width: 100%; } .section { width: 100%; } </style> </head> <body class="background-spokes2 has-valigned"> <!-- <div class="layout-header"> <div class="header-topmargin"></div> <div class="header-flag"> <img src="/images/tesla_flag.png" /> </div> </div> --> <div class="container is-valigned"> <div class="section"> <h1>Page Not Found</h1> </div> </div> </body> </html>
Step 1: Error 200 on attempt 3, call https://auth.tesla.com/static/404.html: <html> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Tesla SSO – Page Not Found</title> <link rel="shortcut icon" href="/images/icons/favicon.ico" type="image/vnd.microsoft.icon" /> <link rel="icon" type="image/png" href="/images/icons/favicon-196x196.png" sizes="196x196?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-160x160.png" sizes="160x160?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-96x96.png" sizes="96x96?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-16x16.png" sizes="16x16?2" /> <link rel="icon" type="image/png" href="/images/icons/favicon-32x32.png" sizes="32x32?2" /> <link rel="stylesheet" href="/styles/bulma.min.css" /> <link rel="stylesheet" href="/styles/main.css" /> <style> .container { text-align: center; width: 100%; } .section { width: 100%; } </style> </head> <body class="background-spokes2 has-valigned"> <!-- <div class="layout-header"> <div class="header-topmargin"></div> <div class="header-flag"> <img src="/images/tesla_flag.png" /> </div> </div> --> <div class="container is-valigned"> <div class="section"> <h1>Page Not Found</h1> </div> </div> </body> </html>

Here’s a snippet of the log. These repeat for each try.

2021-02-07 20:35:08 WARNING (MainThread) [custom_components.tesla_gateway] Step 1: Error 200 on attempt 6, call https://auth.tesla.com/static/404.html:
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tesla SSO – Page Not Found</title>
<link rel="shortcut icon" href="/images/icons/favicon.ico" type="image/vnd.microsoft.icon" />
<link rel="icon" type="image/png" href="/images/icons/favicon-196x196.png" sizes="196x196?2" />
<link rel="icon" type="image/png" href="/images/icons/favicon-160x160.png" sizes="160x160?2" />
<link rel="icon" type="image/png" href="/images/icons/favicon-96x96.png" sizes="96x96?2" />
<link rel="icon" type="image/png" href="/images/icons/favicon-16x16.png" sizes="16x16?2" />
<link rel="icon" type="image/png" href="/images/icons/favicon-32x32.png" sizes="32x32?2" />
<link rel="stylesheet" href="/styles/bulma.min.css" />
<link rel="stylesheet" href="/styles/main.css" />
<style>
.container {
text-align: center;
width: 100%;
}
.section {
width: 100%;
}
</style>
</head>
<body class="background-spokes2 has-valigned">
<!--
<div class="layout-header">
<div class="header-topmargin"></div>
<div class="header-flag">
<img src="/images/tesla_flag.png" />
</div>
</div>
-->
<div class="container is-valigned">
<div class="section">
<h1>Page Not Found</h1>
</div>
</div>
</body>
</html>
2021-02-07 20:35:11 ERROR (MainThread) [homeassistant.core] Error executing service: <ServiceCall tesla_gateway.set_operation (c:e599385b10732e39fa9f8c849466dc5f): real_mode=backup>
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/core.py", line 1471, in catch_exceptions
await coro_or_task
File "/usr/src/homeassistant/homeassistant/core.py", line 1490, in _execute_service
await handler.job.target(service_call)
File "/config/custom_components/tesla_gateway/__init__.py", line 281, in async_set_operation
access_token = yield from login()
File "/config/custom_components/tesla_gateway/__init__.py", line 106, in login
raise ValueError('Step 1: failed after %d attempts, last response %s:\n%s', step_max_attempts, response.status, returned_test)
NameError: name 'returned_test' is not defined

Thank you for helping!

Seems the problem may be related to trying to get a new token on every set_operation. Instead, we need to keep the token around and refresh it after it expires.
That is a bit more complicated to achieve so I will need some time to code it. In the mean time, I modified it to keep the token after the first login. So, in summary, not much better than having the token generated and hard-coding it…
If it complains, add a “access_token” entry in the “tesla_gateway” configuration (empty string as a value).

"""
Monitors and controls the Tesla gateway.
"""
import logging

import aiohttp
import asyncio
import async_timeout
import base64
import hashlib
import json
import os
import re
import time
from urllib.parse import parse_qs
import voluptuous as vol

from homeassistant.const import (
    CONF_USERNAME,
    CONF_PASSWORD,
    CONF_ACCESS_TOKEN
    )
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv

DOMAIN = 'tesla_gateway'

_LOGGER = logging.getLogger(__name__)

DEFAULT_TIMEOUT = 100

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Required(CONF_USERNAME): cv.string,
        vol.Required(CONF_PASSWORD): cv.string,
        vol.Optional(CONF_ACCESS_TOKEN, default=''): cv.string,
    }),
}, extra=vol.ALLOW_EXTRA)

tesla_base_url = 'https://owner-api.teslamotors.com'
tesla_auth_url = 'https://auth.tesla.com'

step_max_attempts = 7
step_attempt_sleep = 3

@asyncio.coroutine
def async_setup(hass, config):

    # Tesla gateway is SSL but has no valid certificates
    websession = async_get_clientsession(hass, verify_ssl=False)

    domain_config = config[DOMAIN]
    conf_user = domain_config[CONF_USERNAME]
    conf_password = domain_config[CONF_PASSWORD]
    access_token = domain_config[CONF_ACCESS_TOKEN]

    @asyncio.coroutine
    def login():

        # Code extracted from https://github.com/enode-engineering/tesla-oauth2/blob/e6922c6e7805d9f65c109d90c5eaf806c5b71938/tesla.py
        # Login process explained at https://tesla-api.timdorr.com/api-basics/authentication

        authorize_url = tesla_auth_url + '/oauth2/v3/authorize'
        callback_url = tesla_auth_url + '/void/callback'

        headers = {
            "User-Agent": "curl",
            "x-tesla-user-agent": "TeslaApp/3.10.9-433/adff2e065/android/10",
            "X-Requested-With": "com.teslamotors.tesla",
        }
        
        verifier_bytes = os.urandom(86)
        code_verifier = base64.urlsafe_b64encode(verifier_bytes).rstrip(b"=")
        code_challenge = base64.urlsafe_b64encode(hashlib.sha256(code_verifier).digest()).rstrip(b"=").decode("utf-8")
        state = base64.urlsafe_b64encode(os.urandom(16)).rstrip(b"=").decode("utf-8")

        params = (
            ("client_id", "ownerapi"),
            ("code_challenge", code_challenge),
            ("code_challenge_method", "S256"),
            ("redirect_uri", callback_url),
            ("response_type", "code"),
            ("scope", "openid email offline_access"),
            ("state", state),
        )

        try:
            # Step 1: Obtain the login page
            _LOGGER.debug('Step 1: GET %s\nparams %s', authorize_url, params)
            for attempt in range(step_max_attempts):
                with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
                    response = yield from websession.get(authorize_url,
                        headers=headers,
                        params=params,
                        raise_for_status=False)

                returned_text = yield from response.text()
                if response.status == 200 and "<title>" in returned_text:
                    crsf_regex_result = re.search(r'name="_csrf".+value="([^"]+)"', returned_text)
                    if crsf_regex_result:
                        _LOGGER.debug('Step 1: Success on attempt %d', attempt)
                        break

                _LOGGER.warning('Step 1: Error %d on attempt %d, call %s:\n%s', response.status, attempt, response.url, returned_text)
                time.sleep(step_attempt_sleep)
            else:
                raise ValueError('Step 1: failed after %d attempts, last response %s:\n%s', step_max_attempts, response.status, returned_test)

            # Step 2: Obtain an authorization code
            csrf = crsf_regex_result.group(1)
            transaction_id = re.search(r'name="transaction_id".+value="([^"]+)"', returned_text).group(1)

            body = {
                "_csrf": csrf,
                "_phase": "authenticate",
                "_process": "1",
                "transaction_id": transaction_id,
                "cancel": "",
                "identity": conf_user,
                "credential": conf_password,
            }
            
            _LOGGER.debug('Step 2: POST %s\nparams: %s\nbody: %s', authorize_url, params, body)
            for attempt in range(step_max_attempts):
                with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
                    response = yield from websession.post(authorize_url,
                        headers=headers,
                        params=params,
                        data=body,
                        raise_for_status=False,
                        allow_redirects=False)

                returned_text = yield from response.text()
                if response.status == 302 and not "<title>" in returned_text:
                    _LOGGER.debug('Step 2: Success on attempt %d', attempt)
                    break

                _LOGGER.warning('Step 2: Error %d on call %s:\n%s', response.status, response.url, returned_text)
                time.sleep(step_attempt_sleep)
            else:
                raise ValueError('Step 2: failed after %d attempts, last response %s:\n%s', step_max_attempts, response.status, returned_text)

            is_mfa = True if response.status == 200 and "/mfa/verify" in returned_text else False
            if is_mfa:
                raise ValueError('Multi-factor authentication enabled for the account and not supported')
            
            # Step 3: Exchange authorization code for bearer token
            code = parse_qs(response.headers["location"])[callback_url + '?code']

            token_url = tesla_auth_url + '/oauth2/v3/token'
            body = {
                "grant_type": "authorization_code",
                "client_id": "ownerapi",
                "code_verifier": code_verifier.decode("utf-8"),
                "code": code,
                "redirect_uri": callback_url
            }

            _LOGGER.debug('Step 3: POST %s', token_url)
            with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
                response = yield from websession.post(token_url,
                    headers=headers,
                    data=body,
                    raise_for_status=False)

            returned_json = yield from response.json()
            access_token = returned_json['access_token']
            domain_config[CONF_ACCESS_TOKEN] = access_token
            return access_token

        except asyncio.TimeoutError:
            _LOGGER.warning('Timeout call %s.', response.url)

        except aiohttp.ClientError:
            _LOGGER.error('Client error %s.', response.url)
        
        domain_config[CONF_ACCESS_TOKEN] = ''
        return ''

    @asyncio.coroutine
    def revoke(access_token):

        revoke_url = tesla_base_url + '/oauth/revoke'
        headers = {'Content-type': 'application/json'}
        body = {
            'token': access_token
        }

        try:
            with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
                response = yield from websession.post(revoke_url,
                    headers=headers,
                    json=body,
                    raise_for_status=False)

            if response.status != 200:
                returned_text = yield from response.text()
                _LOGGER.warning('Error %d on call %s:\n%s', response.status, response.url, returned_text)
            else:
                _LOGGER.debug('revoke completed')
                return True

        except asyncio.TimeoutError:
            _LOGGER.warning('Timeout call %s.', response.url)

        except aiohttp.ClientError:
            _LOGGER.error('Client error %s.', response.url)
        
        return False

    @asyncio.coroutine
    def get_energy_site_id(access_token):
        
        list_url = tesla_base_url + '/api/1/products'
        headers = {
            'Authorization': 'Bearer ' + access_token
            }
        body = {}

        try:        
            with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
                response = yield from websession.get(list_url,
                    headers=headers,
                    json=body,
                    raise_for_status=False)

            if response.status != 200:
                returned_text = yield from response.text()
                _LOGGER.warning('Error %d on call %s:\n%s', response.status, response.url, returned_text)
            else:
                returned_json = yield from response.json()
                _LOGGER.debug(returned_json)
                for r in returned_json['response']:
                    if 'energy_site_id' in r:
                        return r['energy_site_id']
                return None

        except asyncio.TimeoutError:
            _LOGGER.warning('Timeout call %s.', response.url)

        except aiohttp.ClientError:
            _LOGGER.error('Client error %s.', response.url)
        
        return None

    @asyncio.coroutine
    def set_operation(access_token,energy_site_id,service_data):

        operation_url = tesla_base_url + '/api/1/energy_sites/{}/operation'.format(energy_site_id)
        headers = {
            'Content-type': 'application/json',
            'Authorization': 'Bearer ' + access_token
            }
        body = {
            'default_real_mode': service_data['real_mode'],
            'backup_reserve_percent':int(service_data['backup_reserve_percent'])
            }
        _LOGGER.debug(body)
        
        try:        
            with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
                response = yield from websession.post(operation_url,
                    json=body,
                    headers=headers,
                    raise_for_status=False)

            if response.status != 200:
                returned_text = yield from response.text()
                _LOGGER.warning('Error %d on call %s:\n%s', response.status, response.url, returned_text)
            else:
                returned_json = yield from response.json()
                _LOGGER.debug('set operation successful, response: %s', returned_json)

        except asyncio.TimeoutError:
            _LOGGER.warning('Timeout call %s.', response.url)

        except aiohttp.ClientError:
            _LOGGER.error('Client error %s.', response.url)

    @asyncio.coroutine
    def async_set_operation(service):
        
        access_token = domain_config[CONF_ACCESS_TOKEN]
        if not access_token:
            access_token = yield from login()
        if access_token:
            energy_site_id = yield from get_energy_site_id(access_token)
            if energy_site_id:
                yield from set_operation(access_token, energy_site_id, service.data)

    hass.services.async_register(DOMAIN, 'set_operation', async_set_operation)

    return Tru

Thanks. I’ll try that.

My PW’s got updated to 20.49.0. Now the local web view requires login and the Powerwall integration only shows Unavailable. The release notes for 20.49.0 states "increased security for TEG 1 and 2).

Same here, I will take a look to the local web. I logged in again after the update but the usual http request is failing.
I also did another update to convert the SSO token to an “Owner” token and to call refresh. Seems to be working, multiple calls succeed. Will run it for a day or two before posting it (if someone is willing to test it I can also post it, just want to reduce the amount of noise).

A post in https://github.com/home-assistant/core/issues/45955 may have some useful info, specifically a post by bdraco https://github.com/bdraco/powerwall.

yes, it will get more tricky because the login process is with another url, so we may not be able to use the template sensor anymore…was trying to use another sensor to get the login token and then pass that as a state, but the rest sensor doesnt allow templates on headers

Oh I legit thought I had broken something as this stopped working same time I updated my blinds, glad to see it’s not just me. I did reset the login on my powerwall but yeah the integration just fails at unexpected error.

The fix @nathanielh posted was not released yet. However, if you download that powerwall integration and place it on custom_components, you override the HA one. After resetting my PW passwords, I was able to connect (customer password) and I see the data now.

Thanks man I downloaded that repo and put it in my custom components, but when I try to run it I get this error

 [homeassistant.config_entries] Error occurred loading configuration flow for integration powerwall: No module named 'homeassistant.components.dhcp'

That may be related to some other component this one uses, seems like you need to enable this: https://www.home-assistant.io/integrations/dhcp/

Yep was the problem I updated my hass and now I get further but when I go to the integration I get this screen

image

This is the error in the log

2021-02-10 09:17:39 ERROR (MainThread) [custom_components.powerwall-master.config_flow] Unexpected exception
Traceback (most recent call last):
File "/config/custom_components/powerwall-master/config_flow.py", line 77, in async_step_user
info = await validate_input(self.hass, user_input)
File "/config/custom_components/powerwall-master/config_flow.py", line 40, in validate_input
site_info = await hass.async_add_executor_job(
File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "/config/custom_components/powerwall-master/config_flow.py", line 25, in _login_and_fetch_site_info
power_wall.login("", password)
File "/usr/local/lib/python3.8/site-packages/tesla_powerwall/powerwall.py", line 66, in login
return self.login_as(User.CUSTOMER, email, password, force_sm_off)
File "/usr/local/lib/python3.8/site-packages/tesla_powerwall/powerwall.py", line 59, in login_as
response = self._api.login(user, email, password, force_sm_off)
File "/usr/local/lib/python3.8/site-packages/tesla_powerwall/api.py", line 138, in login
return self.post(
File "/usr/local/lib/python3.8/site-packages/tesla_powerwall/api.py", line 128, in post
return self._process_response(response)
File "/usr/local/lib/python3.8/site-packages/tesla_powerwall/api.py", line 51, in _process_response
raise APIError(
tesla_powerwall.error.APIError: Powerwall api error: The url /api/login/Basic returned error 404

UPDATE:

Managed to fix the problem turns out the top box is ip address not the email login so all working now.

Hello everybody. I’m new to HO, so sorry if I have some stupid questions :wink:

I also added my Powerwall 2 into HO a few days ago. It also worked nearly 24 hours and I recorded data from 2021-02-07 20:38 until 2021-02-08 19:28. After that, no new data was recorded. Trying to play around with the integration did not help so I tried to remove and again add it. When I try to add it again, I get only the error “Unknown Error” after entering the IP address.

grafik

In addition, after trying to add the Tesla Powerwall integration, I get the following error in the log

Core Log
2021-02-10 19:59:08 ERROR (MainThread) [homeassistant.components.powerwall.config_flow] Unexpected exception
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/powerwall/config_flow.py", line 64, in async_step_user
    info = await validate_input(self.hass, user_input)
  File "/usr/src/homeassistant/homeassistant/components/powerwall/config_flow.py", line 27, in validate_input
    site_info = await hass.async_add_executor_job(power_wall.get_site_info)
  File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.8/site-packages/tesla_powerwall/__init__.py", line 112, in get_site_info
    return SiteInfo(self._api.get_site_info())
  File "/usr/local/lib/python3.8/site-packages/tesla_powerwall/api.py", line 224, in get_site_info
    return self.get("site_info")
  File "/usr/local/lib/python3.8/site-packages/tesla_powerwall/api.py", line 103, in get
    return self._process_response(response)
  File "/usr/local/lib/python3.8/site-packages/tesla_powerwall/api.py", line 62, in _process_response
    raise AccessDeniedError(
tesla_powerwall.error.AccessDeniedError: Access denied for resource /api/site_info: Unable to GET to resource

Here is the content of various api sites of the Powerwall, I can access via the browser. Apparently, I’m running version 20.49.0. When trying to access any of the pages without logging in, I get a 403 error.

/api/site_info
  "max_system_energy_kWh": 0,
  "max_system_power_kW": 0,
  "site_name": "MY_NAME",
  "timezone": "Europe/Berlin",
  "max_site_meter_power_kW": 1000000000,
  "min_site_meter_power_kW": -1000000000,
  "nominal_system_energy_kWh": 27,
  "nominal_system_power_kW": 10,
  "grid_code": {
    "grid_code": "50Hz_230V_1_VDE4105_2018_Germany",
    "grid_voltage_setting": 230,
    "grid_freq_setting": 50,
    "grid_phase_setting": "Single",
    "country": "Germany",
    "state": "*",
    "distributor": "*",
    "utility": "*",
    "retailer": "*",
    "region": "VDE4105:2018"
/api/meters/site
[
  {
    "id": 0,
    "location": "site",
    "type": "neurio_mb",
    "cts": [
      true,
      true,
      true,
      false
    ],
    "inverted": [
      false,
      false,
      false,
      false
    ],
    "connection": {
      "serial_port": "/dev/ttymxc1",
      "baud": 115200,
      "modbus_id": 1,
      "short_id": "Tesla-0.0.7(2)",
      "device_serial": "0x000004714B017A8B",
      "neurio_connected": true,
      "disable_echo": true,
      "https_conf": {}
    },
    "real_power_scale_factor": 1,
    "Cached_readings": {
      "last_communication_time": "2021-02-10T19:34:14.290213152+01:00",
      "instant_power": 379.5035629272461,
      "instant_reactive_power": -855.8155517578125,
      "instant_apparent_power": 936.1854586058273,
      "frequency": 49.99971389770508,
      "energy_exported": 3204976.4024683805,
      "energy_imported": 1264833.5805239363,
      "instant_average_voltage": 234.78902689615884,
      "instant_total_current": 0,
      "i_a_current": 0,
      "i_b_current": 0,
      "i_c_current": 0,
      "v_l1n": 234.21629333496094,
      "v_l2n": 234.25747680664062,
      "v_l3n": 235.893310546875,
      "last_phase_voltage_communication_time": "2021-02-10T19:34:14.290204485+01:00",
      "real_power_a": 224.6822052001953,
      "real_power_b": 80.3097152709961,
      "real_power_c": 74.51164245605469,
      "reactive_power_a": -340.9933166503906,
      "reactive_power_b": -284.2757568359375,
      "reactive_power_c": -230.54647827148438,
      "last_phase_power_communication_time": "0001-01-01T00:00:00Z",
      "energy_exported_a": 1761675.663888889,
      "energy_exported_b": 1136651.7466666666,
      "energy_exported_c": 1907697.838611111,
      "energy_imported_a": 982981.7441666666,
      "energy_imported_b": 1684993.8566666667,
      "energy_imported_c": 197906.85861111112,
      "serial_number": "0x000004714B017A8B",
      "version": "Tesla-0.0.7(2)",
      "timeout": 1500000000
    }
  }
]
/api/meters/aggregates
{
  "site": {
    "last_communication_time": "2021-02-10T19:35:41.790690217+01:00",
    "instant_power": 374.6804389953613,
    "instant_reactive_power": -838.6568603515625,
    "instant_apparent_power": 918.5481809793631,
    "frequency": 49.99971389770508,
    "energy_exported": 3204976.4024683805,
    "energy_imported": 1264842.5433017144,
    "instant_average_voltage": 234.4982706705729,
    "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
  },
  "battery": {
    "last_communication_time": "2021-02-10T19:35:41.790144887+01:00",
    "instant_power": -60,
    "instant_reactive_power": 10,
    "instant_apparent_power": 60.8276253029822,
    "frequency": 49.994,
    "energy_exported": 932880,
    "energy_imported": 1186530,
    "instant_average_voltage": 233.75,
    "instant_total_current": -0.1,
    "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
  },
  "load": {
    "last_communication_time": "2021-02-10T19:35:41.789851555+01:00",
    "instant_power": 325.2219996409865,
    "instant_reactive_power": -787.4931194493327,
    "instant_apparent_power": 852.0063158395733,
    "frequency": 49.99971389770508,
    "energy_exported": 0,
    "energy_imported": 3981514.2811111123,
    "instant_average_voltage": 234.4982706705729,
    "instant_total_current": 1.3868844265289435,
    "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-02-10T19:35:41.789851555+01:00",
    "instant_power": 10.264152646064758,
    "instant_reactive_power": 44.61656951904297,
    "instant_apparent_power": 45.78199542603306,
    "frequency": 49.99971389770508,
    "energy_exported": 6175404.397503827,
    "energy_imported": 106.25722604848852,
    "instant_average_voltage": 234.61654154459634,
    "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
  }
}
/api/status
{
  "start_time": "2021-02-08 19:37:02 +0800",
  "up_time_seconds": "48h0m2.672586969s",
  "is_new": false,
  "version": "20.49.0",
  "git_hash": "42eb71218f889a3aa3635b7b57cffe1de76b7438",
  "commission_count": 0,
  "device_type": "hec",
  "sync_type": "v1"
}

I would like to test the reworked code from your post by putting it into the init.py. But I cannot find the folder /usr/local/lib/python3.8/concurrent/futures/ nor usr/src/homeassistant/homeassistant/components/powerwall/. Maybe because I removed the integration before?

How could I get this working so I could test this on my site as well?

You need to download the component from https://github.com/bdraco/powerwall and place it in the custom_components folder, then restart HA
The component that is in the official HA release still doesnt have the fix

Thank you very much for your work :+1: It worked for me right now, at least I’m getting values again. Also I did not see any strange errors in the logs, so it seem to be fine for now.

So Im now completely lost.

I implemented the fix from bruceH5200 and this worked fine. Now today Ive lost access to the local sensors. I have tried to follow the thread from the last few days but Im really struggling to follow what has changed.

interestingly I have two IP addresses for my powerwall gateway, both are fixed. I used to access the local API using one ip address, which I assume is the one I set up the integration on initially months ago. However if I now use the other IP address I can access the API just fine via a browser.
I have tried finiding where this IP address is stored in the integration, but no luck.

I have also downloaded and installed the github custom component listed above and restarted HA, but still no luck.

Can anyone advise what needs to be done after the token fix posted by Bruce a couple of weeks ago to get this back working again?

Many thanks for any help.

The script doesn’t access the powerwall locally, it is talking to the Tesla servers via the API who in turn talk to the powerwall. So the IP address of the powerwall is not relevant.
The script still seems to be working for me with the hardcoded key.

I got the same error: ```
[homeassistant.config_entries] Error occurred loading configuration flow for integration powerwall: No module named ‘homeassistant.components.dhcp’


I added "dhcp:" to the config.yaml but then get an error "Component error: dhcp - Integration 'dhcp' not found."

I have default_config: in the config.yaml.

No idea how to get the powerwall component from bdraco working.

The changing mode from backup to self powered is still working using your fix, which works great.

However the sensors I had set up using the local gateway API have all stopped. I was pulling these from the API using local IP address (https://192.168.X.X/api/meters/aggregates) but as of this morning they have all dissappeared. Navigating to that address now fails.

If I change the IP address to another that is associated with the gateway, this works…

Ah - hang on. I wonder… I was working in the cabinet last night, I wonder if Ive gone and broken/loosened the network cable that is connected to the gateway and Im now polling by wifi…

This could be a big egg on face moment…

Has the firmware on your powerwall upgraded, I think may have been making security changes.

They must have been doing something, firmware hasnt updated, but a restart of the gateway has forced it to reconnect to the local network. Cable was fine, but gateway had lost its local IP address for some reason. Sensors all back up and running again now.

Still doesnt explain why the custom component is failing saying dhcp is not available when i have default config enabled. Seems I may have somethingunderlying wrong with my HA setup.