Definitely interested Some code would be much appreciated.
Or if I only need the SoC etc, so readonly… does this still work?
Definitely interested Some code would be much appreciated.
Or if I only need the SoC etc, so readonly… does this still work?
Here is the component code with the public api.
Would be great if someone takes the time to integrate it to the tesla component (it uses the same API).
Also note that this only handles one site/gateway
"""
Monitors and controls the Tesla gateway.
"""
import logging
import aiohttp
import asyncio
import async_timeout
import json
import time
import voluptuous as vol
from homeassistant.const import (
CONF_USERNAME,
CONF_PASSWORD
)
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,
}),
}, extra=vol.ALLOW_EXTRA)
tesla_base_url = 'https://owner-api.teslamotors.com'
@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 = None
@asyncio.coroutine
def login():
login_url = tesla_base_url + '/oauth/token'
headers = {'Content-type': 'application/json'}
body = {
'email': conf_user,
'password': conf_password,
'client_secret': 'c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3',
'client_id': '81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384',
'grant_type': 'password'
}
try:
with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
response = yield from websession.post(login_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)
access_token = returned_json['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)
return None
@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 = 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)
yield from revoke(access_token)
hass.services.async_register(DOMAIN, 'set_operation', async_set_operation)
return True
@james_hiscott sure, this is my automation, the triggers are connected to another automation that tracks TOU, but if you take a look at the “action” part should be easy enough:
alias: tesla_gateway_operation
trigger:
- platform: state
entity_id: utility_meter.energy_meter_daily_imported
to: 'superoffpeak'
- platform: state
entity_id: utility_meter.energy_meter_daily_imported
from: 'superoffpeak'
- platform: homeassistant
event: start
action:
- service: tesla_gateway.set_operation
data_template:
real_mode: >
{% if is_state('utility_meter.energy_meter_daily_imported', 'superoffpeak') %}
backup
{% else %}
self_consumption
{% endif %}
backup_reserve_percent: >
{% if is_state('utility_meter.energy_meter_daily_imported', 'superoffpeak') %}
100
{% else %}
5
{% endif %}
I posted the TOU scripts already here: Rainforest EAGLE-200 Energy Gateway - Does you have one?
Awesome, thanks @estebanp so to confirm (sorry quite new to this level of HA development):
You add the public api code as a component in the HA folder (post 6), do I need to change client_secret and client_id?
You set the required CONF_USERNAME and CONF_PASSWORD in configuration.yaml
you add the rest sensor (in post 3) and set the solar_url to the local IP in secrets
Then run the automation every time you switch tariff to set the battery to the below:
You check if you are on the super off peak tariff, if so you set the gateway to “backup” mode and the reserve percent = 100%
I am assuming this means it will start to pull from the grid and charge the battery to 100%?
@james_hiscott, no worries, it takes some time to wire all these things together…
__init__.py
with the component code from the post above.manifest.json
file with the following:{
"domain": "testla_gateway",
"name": "Tesla Gateway",
"documentation": "",
"dependencies": [],
"codeowners": [],
"requirements": []
}
set_operation:
description: >
Changes operation
fields:
real_mode:
description: Mode to set to the Tesla gateway.
example: 'self_consumption, backup'
backup_reserve_percent:
description: Percentage of battery reserve
tesla_gateway:
username: <username of your tesla account, can be through a secret>
password: <password of your tesla account, can be through a secret>
@estebanp Thank you for bringing your explanation down to our level.
In your manifest.json code, should domain be “testla_gateway” with the extra t?
I tried it with and without the t and I’m still getting a notification saying:
The following integrations and platforms could not be set up:
- tesla_gateway
Please check your config.
Is there anything else we might be missing?
Sorry @james_hiscott , thats a typo, should be “tesla_gateway” (I had it wrong locally and didnt affect it, I think its just used for documentation)
It should be loading by having the custom_component/tesla_gateway/init.py. Depending on your HA version, you should be getting a warning at the beginning of the log statis “You are using a custom itnegration for tesla_gateway”…
Is this the first custom_components you have? the “custom_components” folder should be sibling to configuration.yaml
@estebanp I have other custom_components which are working.
Here’s what I’m seeing in the log:
Logger: homeassistant.setup
First occured: 1:31:42 PM (1 occurences)
Last logged: 1:31:42 PM
Setup failed for tesla_gateway: No setup function defined.
What version of HA are you running?
There is a “setup” function, is async_setup, I dunno at what version async was added, here is the documentation: https://developers.home-assistant.io/docs/asyncio_working_with_async/
Core 0.106.2 and OS version 3.11.
In any case, I do at least have your power and energy sensors collecting data locally, so that’s nice.
How are you displaying your battery %?
Mine is even worse (106.6)…
However everything looks correct to me (and the same as other custom components:
An configuration.yaml looks good as well. Very strange
Do you think a car will come with it ?
Well, if freebies are on offer …
it may be that it needs the non-async versions of those functions… did a quick search and couldn’t find if they are actually required or if there is some setting that drives it.
@Matthew_Budraitis for the battery % I use a custom integration: “mini-graph-card” https://github.com/kalkih/mini-graph-card
@james_hiscott I have not tried this in hassio, dunno if there is any difference, are the same setup/update methods in your other custom components?
From which entity are you getting the battery % though? I’m not seeing that attribute in what I have coming in locally.
Is coming from “sensor.energy_battery_percentage” which is a rest sensor:
- platform: rest
name: Energy battery percentage
resource: !secret solar_battery_url
method: GET
verify_ssl: false
value_template: '{{ value_json.percentage | float | round(2) }}'
unit_of_measurement: '%'
solar_battery_url is: http://<gateway ip>/api/system_status/soe
FYI i got this loading with hassio, now I get the expected:
2020-03-19 16:22:33 WARNING (MainThread) [homeassistant.loader] You are using a custom integration for tesla_gateway which has not been tested by Home Assistant. This component might cause stability problems, be sure to disable it if you do experience issues with Home Assistant.
Not sure what was wrong (probably user error) so just removed all the files and did it again, did a restart and boom! (note the config checker still says it wont work till the restart)
Now i just need to play with it. Will report back soon.
@estebanp Finally got around to making my rules and i can set the mode between “backup” and “self_consumption”, changes quite quickly and works well. When i go back to Self consumption i set the backup_reserve_percent as well and it just seems to get ignored. Is this still working for you, my SW is 1.46.0?
This is what i send via the “call service” page for testing (guessing this is just wrong).
{
real_mode: 'self_consumption',
backup_reserve_percent: 5
}
Super helpful error back from Tesla:
Error 400 on call https://owner-api.teslamotors.com/api/1/energy_sites/XXXX/operation: {“response”:null,“error”:“Invalid operation setting for txid
9d1051fa667XXXXXXXXX50ab9d572cf
”,“error_description”:“”}
@james_hiscott: regarding the warning, yeah, you will get that warning with any custom integration.
Regarding the error, at some point Tesla updated the API, is no longer “real_mode” but “default_real_mode”, that change should do the trick.
Doesn’t your code do that already:
body = {
'default_real_mode': service_data['real_mode'],
'backup_reserve_percent':int(service_data['backup_reserve_percent'])
}
It blows up if i pass: