Tesla Powerwall / Teslemetry configuration for predbat
Firstly I am not a coder, I have attached my yaml and log. I am following instructions of edhull.co.uk guide. On the dashboard I get "Error: Exception raised 'NoneType' object has no attribute 'split'
Predbat v1.2.9. v8.25.6
- Tesla 2x Powerwall 3
- Rasp Pi 5
I'm not familiar with the edhull config of Predbat (and would have been useful if he'd contributed a PR to include it in the predbat documentation, but if we get yours working, I can do that)
It looks like you have mirrored his apps.yaml with the key bits. His has some bad yaml indentation which might cause problems, yours looks OK.
So can't see anything obviously wrong.
Have you tried in a HA script setting your battery to start and stop charging using the commands in the apps.yaml to confirm that you have that bit of the config and Tesla integration working?
BTW you want to set Predbat to 'control charge only' mode as you've not defined any export/discharge services
Try adding
has_ge_inverter_mode: False
To the inverter config
Thanks @springfall2008 that does the trick, it now works in charge only mode!
Side note on the topic of Telsa, and happy to open a feature request for this but is there a way to get the discharge to work too? The idea being you inject the plan from predbat into tesla as a tarrif setting?
Really want to use predbat, but it doesnt export currently :(
Side note on the topic of Telsa, and happy to open a feature request for this but is there a way to get the discharge to work too?
Glad you have got predbat charging your tesla powerwall OK now
The edhull configuration https://edhull.co.uk/blog/2025-08-24/predbat-docker-tesla only sets up battery charging
Battery exporting is entirely possible from predbat but you'd need to work out what the Tesla integration command options are to set discharge mode, etc.
You'd extend the configuration in apps.yaml with discharge_start_service and discharge_stop_service https://springfall2008.github.io/batpred/inverter-setup/#i-want-to-add-an-unsupported-inverter-to-predbat
You'll need to experiment and work out what these commands are as we don't have a Powerwall.
Thanks @gcoan
exporting with Powerwall is tricky, as there is no built in button as such to do it. you have to trick it with its tariff options. I've made my own export button, that will trigger an export. The discharge_start_service and discharge_stop_service, can these just call function in home assistant.
for example? discharge_start_service: service: button.press entity_id: "my home assistant script"
Got it.... i think, just doing some testing. but its looking promising.
The below should work, but you do have to fudge the tarrif at the same time. hence the rest command to rest_command.powerwall_force_export_now
Below are the additions to the apps.YAML
discharge_start_service: - service: rest_command.powerwall_force_export_now - service: switch.turn_on entity_id: switch.my_home_allow_charging_from_grid - service: select.select_option entity_id: "select.my_home_allow_export" option: "battery_ok" - service: select.select_option entity_id: select.my_home_operation_mode option: "autonomous" discharge_stop_service: - service: rest_command.powerwall_api_set_iog_custom_tariff - service: select.select_option entity_id: select.my_home_operation_mode option: "self_consumption" - service: select.select_option entity_id: "select.my_home_allow_export" option: "never"
And below is the rest_command.powerwall_force_export_now rest_command: powerwall_force_export_now: url: "https://owner-api.teslamotors.com/api/1/energy_sites/123456789123456789/time_of_use_settings" method: POST headers: Authorization: !secret tesla_auth_token Content-Type: application/json payload: > {% set now = now() %} {% set minute = now.minute %} {% set start = now.replace(minute=0) if minute < 60 else now.replace(minute=60) %} {% set end = start + timedelta(minutes=60) %} { "tou_settings": { "tariff_content_v2": { "version": 1, "utility": "Octopus Energy", "code": "OCTO-IOG-CUSTOM", "name": "Octopus IOG (Force Export Now)", "currency": "GBP", "monthly_minimum_bill": 0, "min_applicable_demand": 0, "max_applicable_demand": 0, "monthly_charges": 0, "daily_charges": [ { "name": "Charge", "amount": 0 } ], "daily_demand_charges": {}, "demand_charges": { "ALL": { "rates": { "ALL": 0 } }, "AllYear": { "rates": {} } }, "energy_charges": { "ALL": { "rates": { "ALL": 0 } }, "AllYear": { "rates": { "SUPER_OFF_PEAK": 0.07, "ON_PEAK": 0.31 } } }, "seasons": { "AllYear": { "fromMonth": 1, "fromDay": 1, "toMonth": 12, "toDay": 31, "tou_periods": { "SUPER_OFF_PEAK": { "periods": [ { "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 0, "fromMinute": 0, "toHour": {{ start.hour }}, "toMinute": {{ start.minute }} }, { "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": {{ end.hour }}, "fromMinute": {{ end.minute }}, "toHour": 0, "toMinute": 0 } ] }, "ON_PEAK": { "periods": [ { "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": {{ start.hour }}, "fromMinute": {{ start.minute }}, "toHour": {{ end.hour }}, "toMinute": {{ end.minute }} } ] } } } }, "sell_tariff": { "min_applicable_demand": 0, "max_applicable_demand": 0, "monthly_minimum_bill": 0, "monthly_charges": 0, "utility": "Octopus Energy", "daily_charges": [ { "name": "Charge", "amount": 0 } ], "demand_charges": { "ALL": { "rates": { "ALL": 0 } }, "AllYear": { "rates": {} } }, "energy_charges": { "ALL": { "rates": { "ALL": 0 } }, "AllYear": { "rates": { "SUPER_OFF_PEAK": 0.07, "ON_PEAK": 0.30 } } }, "seasons": { "AllYear": { "fromMonth": 1, "fromDay": 1, "toMonth": 12, "toDay": 31, "tou_periods": { "SUPER_OFF_PEAK": { "periods": [ { "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 0, "fromMinute": 0, "toHour": {{ start.hour }}, "toMinute": {{ start.minute }} }, { "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": {{ end.hour }}, "fromMinute": {{ end.minute }}, "toHour": 0, "toMinute": 0 } ] }, "ON_PEAK": { "periods": [ { "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": {{ start.hour }}, "fromMinute": {{ start.minute }}, "toHour": {{ end.hour }}, "toMinute": {{ end.minute }} } ] } } } } } } } }
great, sounds promising, yes predbat should be able to invoke any HA service function as you have found
when you're happy with the solution, can you repost the apps.yaml and rest setup (presumably in configuration.yaml) with the code preceded and followed by three backwards quotes ``` so the formatting stays good
I assume there is also a rest_command.powerwall_api_set_iog_custom_tariff to set the tariff back to normal?
the /123456789123456789/ in the URL is your unique powerwall serial number (or similar)?
and tesla_auth_token needs to be defined in secrets.yaml?
Largely its working. Few quirks, not sure if its intended, but it looks like it calls a stop charging command before issue a start discharging commands, and depending on the sequence these come in, it can cause the "wrong" thing to happen. Looks like a similar setup to the sigenstore where it just toggles between the modes. If you leave it to sort itself out, if it did initally select the wrong one, it does sort itself within a few minutes .
Is it meant to turn one off to enable the other? If it is, ill have to make sure they dont clash
the configs ill paste into a separate message after this one.
For now there are the 2 rest commands, the important one to trick it into exporting, and then the otherone to set it back to normal. I do have a powershell script that gets the current value, saves it and can then revert to it, but couldnt get that working inside HA just yet. so for now i just statically set back to IOG
The /123456789123456789/ refers to your tesla Site ID, and is unique to each persons account.
And yes the tesla auth token is in secrets.yaml.
Ill try and put some basic steps together incase people want to try and follow along.
-
Get your refresh token, Easiest way is "Access Token Generator for Tesla" - https://chromewebstore.google.com/detail/access-token-generator-fo/djpjpanpjaimfjalnpkppkjiedmgpjpe?hl=en
-
run the below in powershell to generate your access token.
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/x-www-form-urlencoded")
$body = "grant_type=refresh_token&client_id=ownerapi&refresh_token=<REPLACE_WITH_YOUR_REFRESH_TOKEN>&scope=openid%20email%20offline_access"
$response = Invoke-RestMethod 'https://auth.tesla.com/oauth2/v3/token' -Method 'POST' -Headers $headers -Body $body
$response | ConvertTo-Json
- Run the below to get your "energy_site_id"
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", "Bearer REPLACE_WITH_YOUR_ACCESS_TOKEN")
$response = Invoke-RestMethod 'https://owner-api.teslamotors.com/api/1/products' -Method 'GET' -Headers $headers
$response | ConvertTo-Json
"response": [
{
"energy_site_id": 123456789123456789,
...
-
Save your energy_site_id & bearer token someone safe, youll need both later.
-
Create 2 Rest_commands, one for forced export, and one to revert back to afterwards. Below is in my rest_command.yaml, if you have not split this out and everything is in config.yaml, add 'rest_command:' to the start, and indent everything by 1 you will also need to relpace 123456789123456789 with your energy site ID
'''
powerwall_force_export_now:
url: "https://owner-api.teslamotors.com/api/1/energy_sites/123456789123456789/time_of_use_settings"
method: POST
headers:
Authorization: !secret tesla_auth_token
Content-Type: application/json
payload: >
{% set now = now() %}
{% set minute = now.minute %}
{% set start = now.replace(minute=0) if minute < 60 else now.replace(minute=60) %}
{% set end = start + timedelta(minutes=60) %}
{
"tou_settings": {
"tariff_content_v2": {
"version": 1,
"utility": "Octopus Energy",
"code": "OCTO-IOG-CUSTOM",
"name": "Octopus IOG (Force Export Now)",
"currency": "GBP",
"monthly_minimum_bill": 0,
"min_applicable_demand": 0,
"max_applicable_demand": 0,
"monthly_charges": 0,
"daily_charges": [
{ "name": "Charge", "amount": 0 }
],
"daily_demand_charges": {},
"demand_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": { "rates": {} }
},
"energy_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": {
"rates": {
"SUPER_OFF_PEAK": 0.07,
"ON_PEAK": 0.31
}
}
},
"seasons": {
"AllYear": {
"fromMonth": 1,
"fromDay": 1,
"toMonth": 12,
"toDay": 31,
"tou_periods": {
"SUPER_OFF_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 0, "fromMinute": 0, "toHour": {{ start.hour }}, "toMinute": {{ start.minute }} },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": {{ end.hour }}, "fromMinute": {{ end.minute }}, "toHour": 0, "toMinute": 0 }
]
},
"ON_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": {{ start.hour }}, "fromMinute": {{ start.minute }}, "toHour": {{ end.hour }}, "toMinute": {{ end.minute }} }
]
}
}
}
},
"sell_tariff": {
"min_applicable_demand": 0,
"max_applicable_demand": 0,
"monthly_minimum_bill": 0,
"monthly_charges": 0,
"utility": "Octopus Energy",
"daily_charges": [
{ "name": "Charge", "amount": 0 }
],
"demand_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": { "rates": {} }
},
"energy_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": {
"rates": {
"SUPER_OFF_PEAK": 0.07,
"ON_PEAK": 0.30
}
}
},
"seasons": {
"AllYear": {
"fromMonth": 1,
"fromDay": 1,
"toMonth": 12,
"toDay": 31,
"tou_periods": {
"SUPER_OFF_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 0, "fromMinute": 0, "toHour": {{ start.hour }}, "toMinute": {{ start.minute }} },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": {{ end.hour }}, "fromMinute": {{ end.minute }}, "toHour": 0, "toMinute": 0 }
]
},
"ON_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": {{ start.hour }}, "fromMinute": {{ start.minute }}, "toHour": {{ end.hour }}, "toMinute": {{ end.minute }} }
]
}
}
}
}
}
}
}
}
powerwall_api_set_iog_custom_tariff:
url: "https://owner-api.teslamotors.com/api/1/energy_sites/123456789123456789/time_of_use_settings"
method: POST
headers:
Authorization: !secret tesla_auth_token
Content-Type: application/json
payload: >
{
"tou_settings": {
"tariff_content_v2": {
"version": 1,
"monthly_minimum_bill": 0,
"min_applicable_demand": 0,
"max_applicable_demand": 0,
"monthly_charges": 0,
"utility": "Octopus Energy",
"code": "OCTO-IOG-CUSTOM",
"name": "Octopus IOG (Custom-restored)",
"currency": "GBP",
"daily_charges": [
{ "name": "Charge", "amount": 0 }
],
"daily_demand_charges": {},
"demand_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": { "rates": {} }
},
"energy_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": {
"rates": {
"SUPER_OFF_PEAK": 0.07,
"ON_PEAK": 0.31
}
}
},
"seasons": {
"AllYear": {
"fromMonth": 1,
"fromDay": 1,
"toMonth": 12,
"toDay": 31,
"tou_periods": {
"SUPER_OFF_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 0, "fromMinute": 0, "toHour": 2, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 3, "fromMinute": 0, "toHour": 5, "toMinute": 30 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 23, "fromMinute": 30, "toHour": 0, "toMinute": 0 }
]
},
"ON_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 2, "fromMinute": 0, "toHour": 3, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 5, "fromMinute": 30, "toHour": 16, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 19, "fromMinute": 0, "toHour": 23, "toMinute": 30 }
]
}
}
}
},
"sell_tariff": {
"min_applicable_demand": 0,
"max_applicable_demand": 0,
"monthly_minimum_bill": 0,
"monthly_charges": 0,
"utility": "Octopus Energy",
"daily_charges": [
{ "name": "Charge", "amount": 0 }
],
"demand_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": { "rates": {} }
},
"energy_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": {
"rates": {
"SUPER_OFF_PEAK": 0.07,
"ON_PEAK": 0.22
}
}
},
"seasons": {
"AllYear": {
"fromMonth": 1,
"fromDay": 1,
"toMonth": 12,
"toDay": 31,
"tou_periods": {
"SUPER_OFF_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 0, "fromMinute": 0, "toHour": 2, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 3, "fromMinute": 0, "toHour": 5, "toMinute": 30 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 23, "fromMinute": 30, "toHour": 0, "toMinute": 0 }
]
},
"ON_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 2, "fromMinute": 0, "toHour": 3, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 5, "fromMinute": 30, "toHour": 16, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 19, "fromMinute": 0, "toHour": 23, "toMinute": 30 }
]
}
}
}
}
}
}
}
}
'''
And then finally is the apps.yaml. There are a few different ways to do this. depending on the behaviour you want.
below is my apps.yaml with some additional stuff for car charger
pred_bat:
module: predbat
class: PredBat
prefix: predbat
timezone: Europe/London
run_every: 5
# Replace this with the total PV energy generated today from your inverter
pv_today: sensor.my_home_solar_generated
# Misc
charge_control_immediate: False
inverter_type: TESLA
inverter:
name: "Tesla Powerwall via Teslemetry"
has_rest_api: False
has_mqtt_api: False
output_charge_control: "none"
has_charge_enable_time: False
has_discharge_enable_time: False
has_target_soc: False
# While the Powerwall does have a reserve SoC, we don't need to
# leverage it for controlling charge/discharge
has_reserve_soc: False
has_ge_inverter_mode: False
charge_time_format: "S"
charge_time_entity_is_option: False
soc_units: "%"
num_load_entities: 1
time_button_press: False
clock_time_format: "%Y-%m-%d %H:%M:%S"
write_and_poll_sleep: 2
has_time_window: False
support_charge_freeze: False
support_discharge_freeze: False
has_idle_time: False
# ---- Live power ----
battery_power:
- sensor.my_home_battery_power
battery_power_invert:
- False
pv_power:
- sensor.my_home_solar_power
load_power:
- sensor.my_home_load_power
grid_power:
- sensor.my_home_grid_power
grid_power_invert:
- True
inverter_reserve_max: 80 #Anything between 80-100 will always be treated as 100
# ---- Daily energy (kWh, cumulative today) ----
load_today:
- sensor.my_home_home_usage
import_today:
- sensor.my_home_grid_imported
export_today:
- sensor.my_home_grid_exported
# ---- State of charge ----
soc_percent:
- sensor.my_home_percentage_charged
soc_max:
- "13.5" # ensure this matches your usable kWh
# ---- Powerwall controls via Teslemetry (must be writable) ----
allow_charge_from_grid:
- switch.my_home_allow_charging_from_grid
allow_export:
- select.my_home_allow_export
# ---- Solar forecast (Solcast) ----
pv_forecast_today: sensor.solcast_pv_forecast_forecast_today
pv_forecast_tomorrow: sensor.solcast_pv_forecast_forecast_tomorrow
currency_symbols: ['£','p']
threads: auto
forecast_hours: 48
# --- Predbat service hooks (Tesla / Teslemetry) ---
# These hooks are called when Predbat wants to change the current
# state of charge/discharge. They can tie-in to other HA entities.
#
# Tesla PW Operation modes can be one of:
# ['self_consumption','autonomous','backup']
#
# grid-charging=on, mode=backup: Powerwall will charge
# grid-charging=off, mode=backup: Powerwall will hold
# mode=self_consumption: Powerwall will discharge
charge_start_service:
- service: switch.turn_on
entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "autonomous"
# repeat: True
- service: number.set_value
entity_id: number.my_home_backup_reserve
value: "100"
- service: select.select_option
entity_id: select.my_home_allow_export
option: "never"
charge_hold_service:
- service: switch.turn_on
entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "self_consumption"
# repeat: True
- service: number.set_value
entity_id: number.my_home_backup_reserve
value: "{{ states('sensor.my_home_percentage_charged') | float }}"
- service: select.select_option
entity_id: select.my_home_allow_export
option: "pv_only"
charge_freeze_service:
- service: switch.turn_off
entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "self_consumption"
# repeat: True
- service: number.set_value
entity_id: number.my_home_backup_reserve
value: "{{ states('sensor.my_home_percentage_charged') | float }}"
- service: select.select_option
entity_id: select.my_home_allow_export
option: "pv_only"
charge_stop_service:
- service: switch.turn_off
entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "autonomous"
# repeat: True
- service: number.set_value
entity_id: number.my_home_backup_reserve
value: "0"
- service: select.select_option
entity_id: select.my_home_allow_export
option: "battery_ok"
discharge_start_service:
- service: rest_command.powerwall_force_export_now
repeat: True
- service: switch.turn_on
entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_allow_export
option: "battery_ok"
# repeat: True
- service: number.set_value
entity_id: number.my_home_backup_reserve
value: "0"
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "autonomous"
# repeat: True
discharge_stop_service:
- service: rest_command.powerwall_api_set_iog_custom_tariff
repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "self_consumption"
# repeat: True
- service: select.select_option
entity_id: select.my_home_allow_export
option: "never"
# repeat: True
# Inverter max AC limit (one per inverter)
# If you have a second inverter for PV only please add the two values together
inverter_limit: 11950 # For a 6kW inverter
# Set the maximum charge/discharge rate of the battery
battery_rate_max:
- 5000
# Export limit is a software limit set on your inverter that prevents exporting above a given level
# When enabled Predbat will model this limit
#export_limit:
# - 3600
# - 3600
#
# The maximum rate the inverter can charge and discharge the battery can be overwritten, this will change
# the register programming and thus cap the max rates. The default is to use the maximum supported rates (recommended)
#
inverter_limit_charge:
- 5000
inverter_limit_discharge:
- 11000
# car_charging_energy defines an incrementing sensor which measures the charge added to your car
# is used for car_charging_hold feature to filter out car charging from the previous load data
# Automatically set to detect Wallbox and Zappi, if it doesn't match manually enter your sensor name
# Also adjust car_charging_energy_scale if it's not in kwH to fix the units
car_charging_energy: 're:(sensor.psl_545713_current_energy)'
# Defines the number of cars modelled by the system, set to 0 for no car
num_cars: 1
# car_charging_planned is set to a sensor which when positive indicates the car will charged in the upcoming low rate slots
# This should not be needed if you use Intelligent Octopus slots which will take priority if enabled
# The list of possible values is in car_charging_planned_response
# Auto matches Zappi and Wallbox, or change it for your own
# One entry per car
car_charging_planned:
- 're:(sensor.psl_545713_status)'
car_charging_planned_response:
- 'yes'
- 'on'
- 'true'
- 'connected'
- 'ev connected'
- 'charging'
- 'paused'
- 'waiting for car demand'
- 'waiting for ev'
- 'scheduled'
- 'enabled'
- 'latched'
- 'locked'
- 'plugged in'
- 'Waiting for schedule'
- 'Pending'
# In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots)
# The car charging now can be set to a sensor to indicate the car is charging and to plan
# for it to charge during this 30 minute slot
car_charging_now:
- sensor.psl_545713_status
# Positive responses for car_charging_now
car_charging_now_response:
- 'yes'
- 'on'
- 'true'
- 'Charging'
# To make planned car charging more accurate, either using car_charging_planned or the Octopus Energy plugin,
# specify your battery size in kwh, charge limit % and current car battery soc % sensors/values.
# If you have Intelligent Octopus the battery size and limit will be extracted from the Octopus Energy plugin directly.
# Set the car SOC% if you have it to give an accurate forecast of the cars battery levels.
# One entry per car if you have multiple cars.
car_charging_battery_size:
- sensor.123456789_battery_capacity
car_charging_limit:
- number.123456789_battery_charging_limit
car_charging_soc:
- sensor.123456789_battery
# If you have Octopus intelligent, enable the intelligent slot information to add to pricing
# Will automatically disable if not found, or comment out to disable fully
# When enabled it overrides the 'car_charging_planned' feature and predict the car charging based on the intelligent plan (unless octopus intelligent charging is False)
# This matches either the intelligent slot from the Octopus Plugin or from the Intelligent plugin
octopus_intelligent_slot: 're:(binary_sensor.octopus_intelligent_slot|re:binary_sensor.octopus_energy_intelligent_dispatching)'
octopus_ready_time: 're:(time.octopus_energy_([0-9a-z_]+|)_intelligent_target_time)'
octopus_charge_limit: 're:(number.octopus_energy([0-9a-z_]+|)_intelligent_charge_target)'
# Example alternative configuration for Ohme integration release >=v0.6.1
#octopus_intelligent_slot: 'binary_sensor.ohme_slot_active'
#octopus_ready_time: 'time.ohme_target_time'
#octopus_charge_limit: 'number.ohme_target_percent'
# Set this to False if you use Octopus Intelligent slot for car planning but when on another tariff e.g. Agile
#octopus_slot_low_rate: False
# Carbon Intensity data from National grid
#carbon_intensity: 're:(sensor.carbon_intensity_uk)'
# Octopus saving session points to the saving session Sensor in the Octopus plugin, when enabled saving sessions will be at the assumed
# Rate is read automatically from the add-in and converted to pence using the conversion rate below (default is 8)
octopus_saving_session: 're:(binary_sensor.octopus_energy([0-9a-z_]+|)_saving_session(s|))'
octopus_saving_session_octopoints_per_penny: 8
# Octopus free session points to the free session Sensor in the Octopus plugin
# Note: You must enable this event sensor in the Octopus Integration in Home Assistant for it to work
octopus_free_session: 're:(event.octopus_energy_([0-9a-z_]+|)_octoplus_free_electricity_session_events)'
# Energy rates
# Please set one of these three, if multiple are set then Octopus is used first, second rates_import/rates_export and latest basic metric
# Set import and export entity to point to the Octopus Energy plugin import and export sensors
# automatically matches your meter number assuming you have only one (no need to edit the below)
# Will be ignored if you don't have the sensor but will error if you do have one and it's incorrect
# NOTE: To get detailed energy rates you need to go in and manually enable the following events in HA
# event.octopus_energy_electricity_xxxxxxxx_previous_day_rates
# event.octopus_energy_electricity_xxxxxxxx_current_day_rates
# event.octopus_energy_electricity_xxxxxxxx_next_day_rates
# and if you have export enable:
# event.octopus_energy_electricity_xxxxxxxx_export_previous_day_rates
# event.octopus_energy_electricity_xxxxxxxx_export_current_day_rates
# event.octopus_energy_electricity_xxxxxxxx_export_next_day_rates
# Predbat will automatically find the event. entities from the link below to the sensors
metric_octopus_import: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_current_rate)'
metric_octopus_export: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_export_current_rate)'
# Standing charge in pounds, can be set to a sensor or manually entered (e.g. 0.50 is 50p)
# The default below will pick up the standing charge from the Octopus Plugin
# The standing charge only impacts the cost graphs and doesn't change the way Predbat plans
# If you don't want to show the standing charge then just delete this line or set to zero
metric_standing_charge: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_current_standing_charge)'
# Or set your actual rates across time for import and export
# If start/end is missing it's assumed to be a fixed rate
# Gaps are filled with zero rate
#rates_import:
# - start: "00:30:00"
# end: "04:30:00"
# rate: 7.5
# - start: "04:30:00"
# end: "00:30:00"
# rate: 40.0
#
#rates_export:
# - rate: 4.2
# Can be used instead of the plugin to get import rates directly online
# Overrides metric_octopus_import and rates_import
# rates_import_octopus_url : "https://api.octopus.energy/v1/products/FLUX-IMPORT-23-02-14/electricity-tariffs/E-1R-FLUX-IMPORT-23-02-14-A/standard-unit-rates"
# rates_import_octopus_url : "https://api.octopus.energy/v1/products/AGILE-FLEX-BB-23-02-08/electricity-tariffs/E-1R-AGILE-FLEX-BB-23-02-08-A/standard-unit-rates"
# Overrides metric_octopus_export and rates_export
# rates_export_octopus_url: "https://api.octopus.energy/v1/products/FLUX-EXPORT-BB-23-02-14/electricity-tariffs/E-1R-FLUX-EXPORT-BB-23-02-14-A/standard-unit-rates"
# rates_export_octopus_url: "https://api.octopus.energy/v1/products/AGILE-OUTGOING-BB-23-02-28/electricity-tariffs/E-1R-AGILE-OUTGOING-BB-23-02-28-A/standard-unit-rates/"
# rates_export_octopus_url: "https://api.octopus.energy/v1/products/OUTGOING-FIX-12M-BB-23-02-09/electricity-tariffs/E-1R-OUTGOING-FIX-12M-BB-23-02-09-A/standard-unit-rates/"
# Import rates can be overridden with rate_import_override
# Export rates can be overridden with rate_export_override
# Use the same format as above, but a date can be included if it just applies for a set day (e.g. Octopus power ups)
# This will override even the Octopus plugin rates if enabled
#
#rates_import_override:
# - date: '2023-09-10'
# start: '14:00:00'
# end: '14:30:00'
# rate: 112
# load_scaling: 0.8
# For pv estimate, leave blank for central estimate, or add 10 for 10% curve (worst case) or 90 or 90% curve (best case)
# If you use 10 then disable pv_metric10_weight below
# pv_estimate: 10
# Days previous is the number of days back to find historical load data
# Recommended is 7 to capture day of the week but 1 can also be used
# if you have more history you could use 7 and 14 (in a list) but the standard data in HA only lasts 10 days
days_previous:
- 1
- 2
- 3
- 7
- 14
- 21
# Days previous weight can be used to control the weighting of the previous load points, the values are multiplied by their
# weights and then divided through by the total weight. E.g. if you used 1 and 0.5 then the first value would have 2/3rd of the weight and the second 1/3rd
# Include one value for each days_previous value, each weighting on a separate line.
# If any days_previous's that are not given a weighting they will assume a default weighting of 1.
days_previous_weight:
- 1
- 1
- 1
- 1
- 1
- 1
I still need to set something up to go an automatically refresh the access token, but i think that'll just be another simple rest command.
brilliant, excellent writeup and very useful to other Tesla Powerwall owners
your energy_site_id & bearer token someone safe, youll need both later.
I presume you copy the contents of the bearer token file into secrets.yaml? Pity you can't store it in a file in /config as that'd be easier to refresh periodically
I'll get this merged in and let you know when it's ready for review
I presume you copy the contents of the bearer token file into secrets.yaml? Pity you can't store it in a file in /config as that'd be easier to refresh periodically
Yeah, so a basic rest command looks like this
powerwall_api_get_tariff: url: "https://owner-api.teslamotors.com/api/1/energy_sites/123456789123456789/tariff_rate" method: GET headers: Authorization: !secret tesla_auth_token
And your secrets.YAML looks like
tesla_auth_token: "Bearer super_secret_sauce_12345"
Largely its working. Few quirks, not sure if its intended, but it looks like it calls a stop charging command before issue a start discharging commands, and depending on the sequence these come in, it can cause the "wrong" thing to happen. Looks like a similar setup to the sigenstore where it just toggles between the modes. If you leave it to sort itself out, if it did initally select the wrong one, it does sort itself within a few minutes .
Is it meant to turn one off to enable the other? If it is, ill have to make sure they dont clash
@gcoan when you get 2 minutes, could you look at the screenshot i added here, and the note. is this expected behaviour?
@gcoan when you get 2 minutes, could you look at the screenshot i added here, and the note. is this expected behaviour?
sorry missed the screenshot
yes it is intentional, the way the inverter is setup you have basically two modes, charging and discharging, so predbat will issue a stop charging before it issues a start discharging I don't know about Powerwall's, but on my GivEnergy inverter there are basically three modes, charging (from solar or grid if not enough solar), exporting (forced discharge at max rate) and Eco which is charging from solar if available, discharging to meet house demand, all the time trying to balance grid import/export to zero. Trefor advised you set has_ge_inverter_mode to false which means you only have the two charging and discharging modes and so (I believe) a swap from one to another will always cause a stop beforehand. You could move the commands into an automation script, or add pauses to ensure one has finished first. Or if there's an Eco mode then worth changing this setting back?
Ah that makes sense let me make a few more changes to the apps.yaml then so they don't conflict with each other
The below is now a non conflicting version that works. Only setting that might need tweaking is the charge hold.
Only additional comment i would add, is tesla tarrifs take about 5 minutes to take affect. you can speed this up by changing a setting like "allow export" and changing it back again. Not sure if there is a way to add this login to predbat...bit of a Tesla limitation :(
pred_bat:
module: predbat
class: PredBat
prefix: predbat
timezone: Europe/London
run_every: 5
# Replace this with the total PV energy generated today from your inverter
pv_today: sensor.my_home_solar_generated
# Misc
charge_control_immediate: False
inverter_type: TESLA
inverter:
name: "Tesla Powerwall via Teslemetry"
has_rest_api: False
has_mqtt_api: False
output_charge_control: "none"
has_charge_enable_time: False
has_discharge_enable_time: False
has_target_soc: False
# While the Powerwall does have a reserve SoC, we don't need to
# leverage it for controlling charge/discharge
has_reserve_soc: False
has_ge_inverter_mode: False
charge_time_format: "S"
charge_time_entity_is_option: False
soc_units: "%"
num_load_entities: 1
time_button_press: False
clock_time_format: "%Y-%m-%d %H:%M:%S"
write_and_poll_sleep: 2
has_time_window: False
support_charge_freeze: False
support_discharge_freeze: False
has_idle_time: False
# ---- Live power ----
battery_power:
- sensor.my_home_battery_power
battery_power_invert:
- False
pv_power:
- sensor.my_home_solar_power
load_power:
- sensor.my_home_load_power
grid_power:
- sensor.my_home_grid_power
grid_power_invert:
- True
inverter_reserve_max: 80 #Anything between 80-100 will always be treated as 100
# ---- Daily energy (kWh, cumulative today) ----
load_today:
- sensor.my_home_home_usage
import_today:
- sensor.my_home_grid_imported
export_today:
- sensor.my_home_grid_exported
# ---- State of charge ----
soc_percent:
- sensor.my_home_percentage_charged
soc_max:
- "13.5" # ensure this matches your usable kWh
# ---- Powerwall controls via Teslemetry (must be writable) ----
allow_charge_from_grid:
- switch.my_home_allow_charging_from_grid
allow_export:
- select.my_home_allow_export
# ---- Solar forecast (Solcast) ----
pv_forecast_today: sensor.solcast_pv_forecast_forecast_today
pv_forecast_tomorrow: sensor.solcast_pv_forecast_forecast_tomorrow
currency_symbols: ['£','p']
threads: auto
forecast_hours: 48
# --- Predbat service hooks (Tesla / Teslemetry) ---
# These hooks are called when Predbat wants to change the current
# state of charge/discharge. They can tie-in to other HA entities.
#
# Tesla PW Operation modes can be one of:
# ['self_consumption','autonomous','backup']
#
# grid-charging=on, mode=backup: Powerwall will charge
# grid-charging=off, mode=backup: Powerwall will hold
# mode=self_consumption: Powerwall will discharge
charge_start_service:
# - service: switch.turn_on
# entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "autonomous"
repeat: True
- service: number.set_value
entity_id: number.my_home_backup_reserve
value: "100"
# repeat: True
# - service: select.select_option
# entity_id: select.my_home_allow_export
# option: "never"
# repeat: True
charge_hold_service:
# - service: switch.turn_on
# entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
# - service: select.select_option
# entity_id: select.my_home_operation_mode
# option: "self_consumption"
# repeat: True
- service: number.set_value
entity_id: number.my_home_backup_reserve
value: "{{ states('sensor.my_home_percentage_charged') | float }}"
repeat: True
# - service: select.select_option
# entity_id: select.my_home_allow_export
# option: "pv_only"
# repeat: True
# charge_freeze_service:
# - service: switch.turn_off
# entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
# - service: select.select_option
# entity_id: select.my_home_operation_mode
# option: "self_consumption"
# repeat: True
# - service: number.set_value
# entity_id: number.my_home_backup_reserve
# value: "{{ states('sensor.my_home_percentage_charged') | float }}"
# repeat: True
# - service: select.select_option
# entity_id: select.my_home_allow_export
# option: "pv_only"
# repeat: True
charge_stop_service:
# - service: switch.turn_off
# entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "autonomous"
repeat: True
- service: number.set_value
entity_id: number.my_home_backup_reserve
value: "0"
repeat: True
# - service: select.select_option
# entity_id: select.my_home_allow_export
# option: "battery_ok"
# repeat: True
discharge_start_service:
- service: rest_command.powerwall_force_export_now
repeat: True
# - service: switch.turn_on
# entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_allow_export
option: "battery_ok"
repeat: True
# - service: number.set_value
# entity_id: number.my_home_backup_reserve
# value: "0"
# repeat: True
# - service: select.select_option
# entity_id: select.my_home_operation_mode
# option: "autonomous"
# repeat: True
discharge_stop_service:
- service: rest_command.powerwall_api_set_iog_custom_tariff
repeat: True
# - service: select.select_option
# entity_id: select.my_home_operation_mode
# option: "self_consumption"
# repeat: True
- service: select.select_option
entity_id: select.my_home_allow_export
option: "never"
repeat: True
# Inverter max AC limit (one per inverter)
# If you have a second inverter for PV only please add the two values together
inverter_limit: 11950 # For a 11kW inverter
# Set the maximum charge/discharge rate of the battery
battery_rate_max:
- 5000
# Export limit is a software limit set on your inverter that prevents exporting above a given level
# When enabled Predbat will model this limit
#export_limit:
# - 3600
# - 3600
#
# The maximum rate the inverter can charge and discharge the battery can be overwritten, this will change
# the register programming and thus cap the max rates. The default is to use the maximum supported rates (recommended)
#
inverter_limit_charge:
- 5000
inverter_limit_discharge:
- 11000
# car_charging_energy defines an incrementing sensor which measures the charge added to your car
# is used for car_charging_hold feature to filter out car charging from the previous load data
# Automatically set to detect Wallbox and Zappi, if it doesn't match manually enter your sensor name
# Also adjust car_charging_energy_scale if it's not in kwH to fix the units
car_charging_energy: 're:(sensor.psl_545713_current_energy)'
# Defines the number of cars modelled by the system, set to 0 for no car
num_cars: 1
# car_charging_planned is set to a sensor which when positive indicates the car will charged in the upcoming low rate slots
# This should not be needed if you use Intelligent Octopus slots which will take priority if enabled
# The list of possible values is in car_charging_planned_response
# Auto matches Zappi and Wallbox, or change it for your own
# One entry per car
car_charging_planned:
- 're:(sensor.psl_545713_status)'
car_charging_planned_response:
- 'yes'
- 'on'
- 'true'
- 'connected'
- 'ev connected'
- 'charging'
- 'paused'
- 'waiting for car demand'
- 'waiting for ev'
- 'scheduled'
- 'enabled'
- 'latched'
- 'locked'
- 'plugged in'
- 'Waiting for schedule'
- 'Pending'
- 'Charging paused'
# In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots)
# The car charging now can be set to a sensor to indicate the car is charging and to plan
# for it to charge during this 30 minute slot
car_charging_now:
- sensor.psl_545713_status
# Positive responses for car_charging_now
car_charging_now_response:
- 'yes'
- 'on'
- 'true'
- 'Charging'
# To make planned car charging more accurate, either using car_charging_planned or the Octopus Energy plugin,
# specify your battery size in kwh, charge limit % and current car battery soc % sensors/values.
# If you have Intelligent Octopus the battery size and limit will be extracted from the Octopus Energy plugin directly.
# Set the car SOC% if you have it to give an accurate forecast of the cars battery levels.
# One entry per car if you have multiple cars.
car_charging_battery_size:
- sensor.123456789_battery_capacity
car_charging_limit:
- '100'
car_charging_soc:
- sensor.123456789_battery
# If you have Octopus intelligent, enable the intelligent slot information to add to pricing
# Will automatically disable if not found, or comment out to disable fully
# When enabled it overrides the 'car_charging_planned' feature and predict the car charging based on the intelligent plan (unless octopus intelligent charging is False)
# This matches either the intelligent slot from the Octopus Plugin or from the Intelligent plugin
octopus_intelligent_slot: 're:(binary_sensor.octopus_intelligent_slot|re:binary_sensor.octopus_energy_intelligent_dispatching)'
octopus_ready_time: 're:(time.octopus_energy_([0-9a-z_]+|)_intelligent_target_time)'
octopus_charge_limit: 're:(number.octopus_energy([0-9a-z_]+|)_intelligent_charge_target)'
# Example alternative configuration for Ohme integration release >=v0.6.1
#octopus_intelligent_slot: 'binary_sensor.ohme_slot_active'
#octopus_ready_time: 'time.ohme_target_time'
#octopus_charge_limit: 'number.ohme_target_percent'
# Set this to False if you use Octopus Intelligent slot for car planning but when on another tariff e.g. Agile
#octopus_slot_low_rate: False
# Carbon Intensity data from National grid
#carbon_intensity: 're:(sensor.carbon_intensity_uk)'
# Octopus saving session points to the saving session Sensor in the Octopus plugin, when enabled saving sessions will be at the assumed
# Rate is read automatically from the add-in and converted to pence using the conversion rate below (default is 8)
octopus_saving_session: 're:(binary_sensor.octopus_energy([0-9a-z_]+|)_saving_session(s|))'
octopus_saving_session_octopoints_per_penny: 8
# Octopus free session points to the free session Sensor in the Octopus plugin
# Note: You must enable this event sensor in the Octopus Integration in Home Assistant for it to work
octopus_free_session: 're:(event.octopus_energy_([0-9a-z_]+|)_octoplus_free_electricity_session_events)'
# Energy rates
# Please set one of these three, if multiple are set then Octopus is used first, second rates_import/rates_export and latest basic metric
# Set import and export entity to point to the Octopus Energy plugin import and export sensors
# automatically matches your meter number assuming you have only one (no need to edit the below)
# Will be ignored if you don't have the sensor but will error if you do have one and it's incorrect
# NOTE: To get detailed energy rates you need to go in and manually enable the following events in HA
# event.octopus_energy_electricity_xxxxxxxx_previous_day_rates
# event.octopus_energy_electricity_xxxxxxxx_current_day_rates
# event.octopus_energy_electricity_xxxxxxxx_next_day_rates
# and if you have export enable:
# event.octopus_energy_electricity_xxxxxxxx_export_previous_day_rates
# event.octopus_energy_electricity_xxxxxxxx_export_current_day_rates
# event.octopus_energy_electricity_xxxxxxxx_export_next_day_rates
# Predbat will automatically find the event. entities from the link below to the sensors
metric_octopus_import: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_current_rate)'
metric_octopus_export: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_export_current_rate)'
# Standing charge in pounds, can be set to a sensor or manually entered (e.g. 0.50 is 50p)
# The default below will pick up the standing charge from the Octopus Plugin
# The standing charge only impacts the cost graphs and doesn't change the way Predbat plans
# If you don't want to show the standing charge then just delete this line or set to zero
metric_standing_charge: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_current_standing_charge)'
# Or set your actual rates across time for import and export
# If start/end is missing it's assumed to be a fixed rate
# Gaps are filled with zero rate
#rates_import:
# - start: "00:30:00"
# end: "04:30:00"
# rate: 7.5
# - start: "04:30:00"
# end: "00:30:00"
# rate: 40.0
#
#rates_export:
# - rate: 4.2
# Can be used instead of the plugin to get import rates directly online
# Overrides metric_octopus_import and rates_import
# rates_import_octopus_url : "https://api.octopus.energy/v1/products/FLUX-IMPORT-23-02-14/electricity-tariffs/E-1R-FLUX-IMPORT-23-02-14-A/standard-unit-rates"
# rates_import_octopus_url : "https://api.octopus.energy/v1/products/AGILE-FLEX-BB-23-02-08/electricity-tariffs/E-1R-AGILE-FLEX-BB-23-02-08-A/standard-unit-rates"
# Overrides metric_octopus_export and rates_export
# rates_export_octopus_url: "https://api.octopus.energy/v1/products/FLUX-EXPORT-BB-23-02-14/electricity-tariffs/E-1R-FLUX-EXPORT-BB-23-02-14-A/standard-unit-rates"
# rates_export_octopus_url: "https://api.octopus.energy/v1/products/AGILE-OUTGOING-BB-23-02-28/electricity-tariffs/E-1R-AGILE-OUTGOING-BB-23-02-28-A/standard-unit-rates/"
# rates_export_octopus_url: "https://api.octopus.energy/v1/products/OUTGOING-FIX-12M-BB-23-02-09/electricity-tariffs/E-1R-OUTGOING-FIX-12M-BB-23-02-09-A/standard-unit-rates/"
# Import rates can be overridden with rate_import_override
# Export rates can be overridden with rate_export_override
# Use the same format as above, but a date can be included if it just applies for a set day (e.g. Octopus power ups)
# This will override even the Octopus plugin rates if enabled
#
#rates_import_override:
# - date: '2023-09-10'
# start: '14:00:00'
# end: '14:30:00'
# rate: 112
# load_scaling: 0.8
# For pv estimate, leave blank for central estimate, or add 10 for 10% curve (worst case) or 90 or 90% curve (best case)
# If you use 10 then disable pv_metric10_weight below
# pv_estimate: 10
# Days previous is the number of days back to find historical load data
# Recommended is 7 to capture day of the week but 1 can also be used
# if you have more history you could use 7 and 14 (in a list) but the standard data in HA only lasts 10 days
days_previous:
- 1
- 2
- 3
- 7
- 14
- 21
# Days previous weight can be used to control the weighting of the previous load points, the values are multiplied by their
# weights and then divided through by the total weight. E.g. if you used 1 and 0.5 then the first value would have 2/3rd of the weight and the second 1/3rd
# Include one value for each days_previous value, each weighting on a separate line.
# If any days_previous's that are not given a weighting they will assume a default weighting of 1.
days_previous_weight:
- 1
- 1
- 1
- 1
- 1
- 1
some of the rest commands will need changing to have the new token in them, but below is how you generate and keep your access token and refresh token up to date in home assistant.
Add these input helpers to your Home Assistant configuration.yaml or create via UI
input_text: tesla_refresh_token_part1: name: "Tesla Refresh Token - Part 1" max: 255 mode: password
tesla_refresh_token_part2: name: "Tesla Refresh Token - Part 2" max: 255 mode: password
tesla_refresh_token_part3: name: "Tesla Refresh Token - Part 3" max: 255 mode: password
tesla_refresh_token_part4: name: "Tesla Refresh Token - Part 4" max: 255 mode: password
tesla_access_token_part1: name: "Tesla Access Token - Part 1" max: 255 mode: password
tesla_access_token_part2: name: "Tesla Access Token - Part 2" max: 255 mode: password
tesla_access_token_part3: name: "Tesla Access Token - Part 3" max: 255 mode: password
tesla_access_token_part4: name: "Tesla Access Token - Part 4" max: 255 mode: password
Add this rest_command to configuration.yaml
rest_command: tesla_refresh_token: url: "https://auth.tesla.com/oauth2/v3/token" method: POST content_type: "application/x-www-form-urlencoded" payload: "grant_type=refresh_token&client_id=ownerapi&refresh_token={{ (states('input_text.tesla_refresh_token_part1') or '') + (states('input_text.tesla_refresh_token_part2') or '') + (states('input_text.tesla_refresh_token_part3') or '') + (states('input_text.tesla_refresh_token_part4') or '') }}&scope=openid%20email%20offline_access"
Add this automation to trigger the token refresh
automation:
-
alias: "Refresh Tesla Access Token" description: "Refresh Tesla access token every 8 hours" trigger: platform: time_pattern hours: "/8"
action:
-
service: rest_command.tesla_refresh_token response_variable: tesla_response
-
service: input_text.set_value target: entity_id: input_text.tesla_access_token_part1 data: value: "{{ tesla_response.content.access_token[0:250] }}"
-
service: input_text.set_value target: entity_id: input_text.tesla_access_token_part2 data: value: "{{ tesla_response.content.access_token[250:500] }}"
-
service: input_text.set_value target: entity_id: input_text.tesla_access_token_part3 data: value: "{{ tesla_response.content.access_token[500:750] }}"
-
service: input_text.set_value target: entity_id: input_text.tesla_access_token_part4 data: value: "{{ tesla_response.content.access_token[750:] }}"
-
service: input_text.set_value target: entity_id: input_text.tesla_refresh_token_part1 data: value: "{{ tesla_response.content.refresh_token[0:250] }}"
-
service: input_text.set_value target: entity_id: input_text.tesla_refresh_token_part2 data: value: "{{ tesla_response.content.refresh_token[250:500] }}"
-
service: input_text.set_value target: entity_id: input_text.tesla_refresh_token_part3 data: value: "{{ tesla_response.content.refresh_token[500:750] }}"
-
service: input_text.set_value target: entity_id: input_text.tesla_refresh_token_part4 data: value: "{{ tesla_response.content.refresh_token[750:] }}"
-
service: persistent_notification.create data: title: "Tesla Tokens Updated" message: "Access and refresh tokens have been successfully updated" notification_id: "tesla_token_update"
-
@Slee2112 I have merged your above setup into the Predbat documentation: https://github.com/gcoan/batpred/blob/main/docs/inverter-setup.md
I would appreciate your review of it. With the access token stuff I am not sure I have captured everything needed, in particular:
- getting the access token, converting that to a refresh token, getting the site id https://github.com/springfall2008/batpred/issues/2745#issuecomment-3453635079 - not sure if needed?
- changes to the REST commands to use the tokens now stored in input_text helpers
- the access token REST, do you need to pre-prime the input_text's first as I assume this refreshes an existing token, otherwise how does the call know what token to issue
I have not copied the car charger stuff over into apps.yaml, we didn't discuss that. Is that a Tesla car charger config? There is a different approach to Tesla car charging in the predbat docs https://springfall2008.github.io/batpred/devices/#tesla but yours looks like more detail??
@gcoan Had a look. Few changes. While the setup does work through Teslemetry, it also works through Tesla Fleet, not sure if its worth calling that out?
Easiest way to get your refresh token is via a 3rd party app like "Access Token Generator for Tesla" - https://chromewebstore.google.com/detail/access-token-generator-fo/djpjpanpjaimfjalnpkppkjiedmgpjpe?hl=en
This token needs to be copied, and then split into 4 parts, so it fits within the "refresh" input helpers. The refresh token is then used to generate an access token valid for 8 hours, and a new refresh token than is valid for ~30 days.
There are some additional helpers i've added, and tweaks to getting the energy site id automatically, to make the process a little more streamlined, a few more automations to help with this, so will outline the whole process again below.
The car config can be ignored from my apps.yaml, its setup with Podpoint, but for this i would leave it out to avoid confusion.
- Get the refresh token
- Create the below helpers in config.yaml
input_text:
tesla_refresh_token_part1:
name: "Tesla Refresh Token - Part 1"
max: 255
mode: password
tesla_refresh_token_part2:
name: "Tesla Refresh Token - Part 2"
max: 255
mode: password
tesla_refresh_token_part3:
name: "Tesla Refresh Token - Part 3"
max: 255
mode: password
tesla_refresh_token_part4:
name: "Tesla Refresh Token - Part 4"
max: 255
mode: password
tesla_access_token_part1:
name: "Tesla Access Token - Part 1"
max: 255
mode: password
tesla_access_token_part2:
name: "Tesla Access Token - Part 2"
max: 255
mode: password
tesla_access_token_part3:
name: "Tesla Access Token - Part 3"
max: 255
mode: password
tesla_access_token_part4:
name: "Tesla Access Token - Part 4"
max: 255
mode: password
tesla_energy_site_id:
name: "Tesla Energy Site ID"
unit_of_measurement: ""
icon: mdi:lightning-bolt-outline
- Spilt your refresh token into 4, and add your refresh token to your refresh token helper.
- Create the below rest_command.yaml.
tesla_refresh_token:
url: "https://auth.tesla.com/oauth2/v3/token"
method: POST
content_type: "application/x-www-form-urlencoded"
payload: "grant_type=refresh_token&client_id=ownerapi&refresh_token={{ (states('input_text.tesla_refresh_token_part1') or '') + (states('input_text.tesla_refresh_token_part2') or '') + (states('input_text.tesla_refresh_token_part3') or '') + (states('input_text.tesla_refresh_token_part4') or '') }}&scope=openid%20email%20offline_access"
tesla_api_get_products:
url: "https://owner-api.teslamotors.com/api/1/products"
method: GET
headers:
Authorization: "Bearer {{ (states('input_text.tesla_access_token_part1') or '') + (states('input_text.tesla_access_token_part2') or '') + (states('input_text.tesla_access_token_part3') or '') + (states('input_text.tesla_access_token_part4') or '') }}"
tesla_api_get_current_tariff:
url: "https://owner-api.teslamotors.com/api/1/energy_sites/{{ states('input_text.tesla_energy_site_id') }}/tariff_rate"
method: GET
headers:
Authorization: "Bearer {{ (states('input_text.tesla_access_token_part1') or '') + (states('input_text.tesla_access_token_part2') or '') + (states('input_text.tesla_access_token_part3') or '') + (states('input_text.tesla_access_token_part4') or '') }}"
tesla_api_set_export_now_tariff:
url: "https://owner-api.teslamotors.com/api/1/energy_sites/{{ states('input_text.tesla_energy_site_id') }}/time_of_use_settings"
method: POST
headers:
Authorization: "Bearer {{ (states('input_text.tesla_access_token_part1') or '') + (states('input_text.tesla_access_token_part2') or '') + (states('input_text.tesla_access_token_part3') or '') + (states('input_text.tesla_access_token_part4') or '') }}"
Content-Type: application/json
payload: >
{% set now = now() %}
{% set minute = now.minute %}
{% set start = now.replace(minute=0) if minute < 30 else now.replace(minute=30) %}
{% set end = start + timedelta(minutes=60) %}
{% set ns = namespace(super_off_peak_periods=[]) %}
{% if start.hour > 0 %}
{% set ns.super_off_peak_periods = ns.super_off_peak_periods + [{"fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 0, "fromMinute": 0, "toHour": start.hour, "toMinute": start.minute}] %}
{% endif %}
{% if end.hour > 0 %}
{% set ns.super_off_peak_periods = ns.super_off_peak_periods + [{"fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": end.hour, "fromMinute": end.minute, "toHour": 0, "toMinute": 0}] %}
{% endif %}
{
"tou_settings": {
"tariff_content_v2": {
"version": 1,
"utility": "Octopus Energy",
"code": "OCTO-IOG-CUSTOM",
"name": "Octopus IOG (Force Export Now)",
"currency": "GBP",
"monthly_minimum_bill": 0,
"min_applicable_demand": 0,
"max_applicable_demand": 0,
"monthly_charges": 0,
"daily_charges": [
{ "name": "Charge", "amount": 0 }
],
"daily_demand_charges": {},
"demand_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": { "rates": {} }
},
"energy_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": {
"rates": {
"SUPER_OFF_PEAK": 0.07,
"ON_PEAK": 0.31
}
}
},
"seasons": {
"AllYear": {
"fromMonth": 1,
"fromDay": 1,
"toMonth": 12,
"toDay": 31,
"tou_periods": {
"SUPER_OFF_PEAK": {
"periods": {{ ns.super_off_peak_periods | tojson }}
},
"ON_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": {{ start.hour }}, "fromMinute": {{ start.minute }}, "toHour": {{ end.hour }}, "toMinute": {{ end.minute }} }
]
}
}
}
},
"sell_tariff": {
"min_applicable_demand": 0,
"max_applicable_demand": 0,
"monthly_minimum_bill": 0,
"monthly_charges": 0,
"utility": "Octopus Energy",
"daily_charges": [
{ "name": "Charge", "amount": 0 }
],
"demand_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": { "rates": {} }
},
"energy_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": {
"rates": {
"SUPER_OFF_PEAK": 0.07,
"ON_PEAK": 0.30
}
}
},
"seasons": {
"AllYear": {
"fromMonth": 1,
"fromDay": 1,
"toMonth": 12,
"toDay": 31,
"tou_periods": {
"SUPER_OFF_PEAK": {
"periods": {{ ns.super_off_peak_periods | tojson }}
},
"ON_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": {{ start.hour }}, "fromMinute": {{ start.minute }}, "toHour": {{ end.hour }}, "toMinute": {{ end.minute }} }
]
}
}
}
}
}
}
}
}
tesla_api_set_iog_custom_tariff:
url: "https://owner-api.teslamotors.com/api/1/energy_sites/{{ states('input_text.tesla_energy_site_id') }}/time_of_use_settings"
method: POST
headers:
Authorization: "Bearer {{ (states('input_text.tesla_access_token_part1') or '') + (states('input_text.tesla_access_token_part2') or '') + (states('input_text.tesla_access_token_part3') or '') + (states('input_text.tesla_access_token_part4') or '') }}"
Content-Type: application/json
payload: >
{
"tou_settings": {
"tariff_content_v2": {
"version": 1,
"monthly_minimum_bill": 0,
"min_applicable_demand": 0,
"max_applicable_demand": 0,
"monthly_charges": 0,
"utility": "Octopus Energy",
"code": "OCTO-IOG-CUSTOM",
"name": "Octopus IOG (Custom-restored)",
"currency": "GBP",
"daily_charges": [
{ "name": "Charge", "amount": 0 }
],
"daily_demand_charges": {},
"demand_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": { "rates": {} }
},
"energy_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": {
"rates": {
"SUPER_OFF_PEAK": 0.07,
"PARTIAL_PEAK": 0.31,
"ON_PEAK": 0.31
}
}
},
"seasons": {
"AllYear": {
"fromMonth": 1,
"fromDay": 1,
"toMonth": 12,
"toDay": 31,
"tou_periods": {
"SUPER_OFF_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 0, "fromMinute": 0, "toHour": 5, "toMinute": 30 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 23, "fromMinute": 30, "toHour": 0, "toMinute": 0 }
]
},
"ON_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 2, "fromMinute": 0, "toHour": 3, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 5, "fromMinute": 30, "toHour": 16, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 16, "fromMinute": 0, "toHour": 19, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 19, "fromMinute": 0, "toHour": 23, "toMinute": 30 }
]
}
}
}
},
"sell_tariff": {
"min_applicable_demand": 0,
"max_applicable_demand": 0,
"monthly_minimum_bill": 0,
"monthly_charges": 0,
"utility": "Octopus Energy",
"daily_charges": [
{ "name": "Charge", "amount": 0 }
],
"demand_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": { "rates": {} }
},
"energy_charges": {
"ALL": { "rates": { "ALL": 0 } },
"AllYear": {
"rates": {
"SUPER_OFF_PEAK": 0.07,
"PARTIAL_PEAK": 0.30,
"ON_PEAK": 0.22
}
}
},
"seasons": {
"AllYear": {
"fromMonth": 1,
"fromDay": 1,
"toMonth": 12,
"toDay": 31,
"tou_periods": {
"SUPER_OFF_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 0, "fromMinute": 0, "toHour": 5, "toMinute": 30 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 23, "fromMinute": 30, "toHour": 0, "toMinute": 0 }
]
},
"ON_PEAK": {
"periods": [
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 2, "fromMinute": 0, "toHour": 3, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 5, "fromMinute": 30, "toHour": 16, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 16, "fromMinute": 0, "toHour": 19, "toMinute": 0 },
{ "fromDayOfWeek": 0, "toDayOfWeek": 6, "fromHour": 19, "fromMinute": 0, "toHour": 23, "toMinute": 30 }
]
}
}
}
}
}
}
}
}
- Add the below automations
#=====================================================================
Add this automation to trigger the token refresh
#=====================================================================
automation:
- alias: "Refresh Tesla Access Token"
description: "Refresh Tesla access token every 8 hours"
trigger:
platform: time_pattern
hours: "/8"
action:
- service: rest_command.tesla_refresh_token
response_variable: tesla_response
- service: input_text.set_value
target:
entity_id: input_text.tesla_access_token_part1
data:
value: "{{ tesla_response.content.access_token[0:250] }}"
- service: input_text.set_value
target:
entity_id: input_text.tesla_access_token_part2
data:
value: "{{ tesla_response.content.access_token[250:500] }}"
- service: input_text.set_value
target:
entity_id: input_text.tesla_access_token_part3
data:
value: "{{ tesla_response.content.access_token[500:750] }}"
- service: input_text.set_value
target:
entity_id: input_text.tesla_access_token_part4
data:
value: "{{ tesla_response.content.access_token[750:] }}"
- service: input_text.set_value
target:
entity_id: input_text.tesla_refresh_token_part1
data:
value: "{{ tesla_response.content.refresh_token[0:250] }}"
- service: input_text.set_value
target:
entity_id: input_text.tesla_refresh_token_part2
data:
value: "{{ tesla_response.content.refresh_token[250:500] }}"
- service: input_text.set_value
target:
entity_id: input_text.tesla_refresh_token_part3
data:
value: "{{ tesla_response.content.refresh_token[500:750] }}"
- service: input_text.set_value
target:
entity_id: input_text.tesla_refresh_token_part4
data:
value: "{{ tesla_response.content.refresh_token[750:] }}"
- service: persistent_notification.create
data:
title: "Tesla Tokens Updated"
message: "Access and refresh tokens have been successfully updated"
notification_id: "tesla_token_update"
#=====================================================================
Add this automation to trigger the energy site ID refresh
#=====================================================================
automation:
- alias: "Update Tesla Energy Site ID"
trigger:
- platform: homeassistant
event: start
- platform: time
at: "00:00:00"
action:
- service: rest_command.tesla_api_get_products
response_variable: products_response
- service: input_text.set_value
target:
entity_id: input_text.tesla_energy_site_id
data:
value: "{{ products_response.content.response[0].energy_site_id }}"
And updated apps.yaml below as some rest command i think have changed names since i last posted it.
pred_bat:
module: predbat
class: PredBat
prefix: predbat
timezone: Europe/London
run_every: 5
# Replace this with the total PV energy generated today from your inverter
pv_today: sensor.my_home_solar_generated
# Misc
charge_control_immediate: False
inverter_type: TESLA
inverter:
name: "Tesla Powerwall via Teslemetry"
has_rest_api: False
has_mqtt_api: False
output_charge_control: "none"
has_charge_enable_time: False
has_discharge_enable_time: False
has_target_soc: False
# While the Powerwall does have a reserve SoC, we don't need to
# leverage it for controlling charge/discharge
has_reserve_soc: False
has_ge_inverter_mode: False
charge_time_format: "S"
charge_time_entity_is_option: False
soc_units: "%"
num_load_entities: 1
time_button_press: False
clock_time_format: "%Y-%m-%d %H:%M:%S"
write_and_poll_sleep: 2
has_time_window: False
support_charge_freeze: False
support_discharge_freeze: False
has_idle_time: False
# ---- Live power ----
battery_power:
- sensor.my_home_battery_power
battery_power_invert:
- False
pv_power:
- sensor.my_home_solar_power
load_power:
- sensor.my_home_load_power
grid_power:
- sensor.my_home_grid_power
grid_power_invert:
- True
inverter_reserve_max: 80 #Anything between 80-100 will always be treated as 100
# ---- Daily energy (kWh, cumulative today) ----
load_today:
- sensor.my_home_home_usage
import_today:
- sensor.my_home_grid_imported
export_today:
- sensor.my_home_grid_exported
# ---- State of charge ----
soc_percent:
- sensor.my_home_percentage_charged
soc_max:
- "13.5" # ensure this matches your usable kWh
# ---- Powerwall controls via Teslemetry (must be writable) ----
allow_charge_from_grid:
- switch.my_home_allow_charging_from_grid
allow_export:
- select.my_home_allow_export
# ---- Solar forecast (Solcast) ----
pv_forecast_today: sensor.solcast_pv_forecast_forecast_today
pv_forecast_tomorrow: sensor.solcast_pv_forecast_forecast_tomorrow
currency_symbols: ['£','p']
threads: auto
forecast_hours: 48
# --- Predbat service hooks (Tesla / Teslemetry) ---
# These hooks are called when Predbat wants to change the current
# state of charge/discharge. They can tie-in to other HA entities.
#
# Tesla PW Operation modes can be one of:
# ['self_consumption','autonomous','backup']
#
# grid-charging=on, mode=backup: Powerwall will charge
# grid-charging=off, mode=backup: Powerwall will hold
# mode=self_consumption: Powerwall will discharge
charge_start_service:
# - service: switch.turn_on
# entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "autonomous"
repeat: True
- service: number.set_value
entity_id: number.my_home_backup_reserve
value: "100"
# repeat: True
# - service: select.select_option
# entity_id: select.my_home_allow_export
# option: "never"
# repeat: True
charge_hold_service:
# - service: switch.turn_on
# entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "backup"
repeat: True
# - service: number.set_value
# entity_id: number.my_home_backup_reserve
# value: "{{ states('sensor.my_home_percentage_charged') | float }}"
# repeat: True
# - service: select.select_option
# entity_id: select.my_home_allow_export
# option: "pv_only"
# repeat: True
# charge_freeze_service:
# - service: switch.turn_off
# entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
# - service: select.select_option
# entity_id: select.my_home_operation_mode
# option: "backup"
# repeat: True
# - service: number.set_value
# entity_id: number.my_home_backup_reserve
# value: "{{ states('sensor.my_home_percentage_charged') | float }}"
# repeat: True
# - service: select.select_option
# entity_id: select.my_home_allow_export
# option: "pv_only"
# repeat: True
charge_stop_service:
# - service: switch.turn_off
# entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "autonomous"
repeat: True
- service: number.set_value
entity_id: number.my_home_backup_reserve
value: "0"
repeat: True
# - service: select.select_option
# entity_id: select.my_home_allow_export
# option: "battery_ok"
# repeat: True
discharge_start_service:
- service: rest_command.tesla_api_set_export_now_tariff
repeat: True
# - service: switch.turn_on
# entity_id: switch.my_home_allow_charging_from_grid
# repeat: True
- service: select.select_option
entity_id: select.my_home_allow_export
option: "battery_ok"
repeat: True
# - service: number.set_value
# entity_id: number.my_home_backup_reserve
# value: "0"
# repeat: True
- service: select.select_option
entity_id: select.my_home_operation_mode
option: "autonomous"
repeat: True
discharge_stop_service:
- service: rest_command.tesla_api_set_iog_custom_tariff
repeat: True
# - service: select.select_option
# entity_id: select.my_home_operation_mode
# option: "self_consumption"
# repeat: True
- service: select.select_option
entity_id: select.my_home_allow_export
option: "never"
repeat: True
# Inverter max AC limit (one per inverter)
# If you have a second inverter for PV only please add the two values together
inverter_limit: 11950 # For a 6kW inverter
# Set the maximum charge/discharge rate of the battery
battery_rate_max:
- 5000
# Export limit is a software limit set on your inverter that prevents exporting above a given level
# When enabled Predbat will model this limit
#export_limit:
# - 3600
# - 3600
#
# The maximum rate the inverter can charge and discharge the battery can be overwritten, this will change
# the register programming and thus cap the max rates. The default is to use the maximum supported rates (recommended)
#
inverter_limit_charge:
- 5000
inverter_limit_discharge:
- 11000
All done, I have merged this into the documentation and updated the Tesla apps.yaml https://github.com/gcoan/batpred/blob/main/templates/tesla_powerwall.yaml
While the setup does work through Teslemetry, it also works through Tesla Fleet, not sure if its worth calling that out?
I assumed that the my_home_xxx HA entities all came from Teslemetry?
From the Ed Hull blog I thought Teslemetry was simpler than using the Fleet API, and would presumably require different YAML etc?
I assumed that the my_home_xxx HA entities all came from Teslemetry?
entities for teslemetry, and for tesla fleet, are both "my_home_xxxx", both integrations pull the same information too. i believe teslemetry essentially just runs the tesla fleet integration for you and charges you for it.
But yes, teslemetry is definately simpler to setup for novice users.
are you using Teslemetry or the Tesla Fleet integration? I'd rather say which it was developed and tested with.
I also need to note it is for powerwall 3, and you've offered help to a PW2 user, but that's a future iteration
Tesla Fleet, and yes for Powerwall 3
Hi @Slee2112 I've updated the documentation to highlight Tesla Fleet and PW3
if you can give it a final once-over and then I think we can close this one
Hi @Slee2112 I've updated the documentation to highlight Tesla Fleet and PW3
if you can give it a final once-over and then I think we can close this one
Looks good, 1 comment, and this could be my lack of HA knowledge. does the below not need to have the "!include" rest_command: !rest_command.yaml
rest_command: !include rest_command.yaml
otherwise all looks good i think
Looks good, 1 comment, and this could be my lack of HA knowledge. does the below not need to have the "!include" rest_command: !rest_command.yaml
you're absolutely right it does, that's what comes of typing something in and not copy/pasting!
since all the other entries in configuration.yaml have been done directly into the configuration file and not via !include files, I decided this was inconsistent and have now just made it a single block of yaml for all the REST commands to be copy/pasted. Makes it easier.
Personally I would prefer to create the input_text's and automations via the HA UI as its easier to edit that way, but embedding into configuration.yaml is easier to explain to others
Thanks again.
This will be in my next Predbat PR
Hi everyone 👋 I was the one who wrote the original blog post on configuring Predbat to work with a Tesla Powerwall via Teslemetry, I've stumbled across this and it's been very interesting to read!
Firstly, apologies for not raising a PR to have merged back into the Predbat knowledge base. It all started very much as a "what if" but has obviously picked up some traction. Thank you @gcoan for picking that up!
Since that blog post I've moved to Tesla Fleet rather than Teslemetry which has been more reliable and does not come with an on-going cost, and have also managed to implement a workable solution via Predbat to force the Powerwall to export (although with a different approach to what has been outlined above). I will be releasing an updated blog post covering these changes as well as additional changes to properly reflect the charge rate (and charge curve) of the Powerwall, and I'll also make sure to reference this particular discussion 😄
Thank you again to all those who have contributed and continue to support Predbat, it's been an incredibly useful tool to leverage and hopefully the lessons learned from that blog post have been insightful for others.
Thanks Ed, nice to hear from you.
Yours was a good start point that certainly got things going, so thanks for that.
If you have any bits of your solution that are worth incorporating, please do share. I will reopen this github issue, only closed it because it looked like we were all done.
Charge curve would be interesting, particularly if it uses Predbat's logic to determine it.
Teslemetry vs Fleet, at the moment I've left it as either can be used, but do wonder whether we should just say Fleet integration as that's what both you and @Slee2112 are using.
Is yours a PW3 as well? Be good if we could have a solution for all PW models, or at least document where the differences are.
Thanks Geoffrey, much appreciated! I do also have a Powerwall 3, but I suspect the same mechanisms could be leveraged with a Powerwall 2. Some additional config which may be worth incorporating is as follows (I'm also still experimenting with these settings, so any feedback is much appreciated!). For reference in the details below, my G99 approved export rate is 7.2kW.
It's been trial and error with getting Predbat to correctly reflect the charge and discharge rate of the Powerwall, as well as the charge tapering (the Powerwall will taper charging after hitting 80% and get progressively slower as you near 100%).
The Powerwall will charge at different rates based which mode it's in:
- Autonomous mode : 5kW
- Backup mode : 3.3kW
- Via the 'reserve' slider : 1.7kW
I leverage switching the Powerwall to backup mode to force it to charge during a Predbat charge window (which means that it should charge at 3.3kW) but I found that Predbat was incorrectly modelling both my export and import rate at 2.6kW even after setting charge_rate and discharge_rate. I'm interested to get your thoughts on why that may be. Instead, I've ended up with the following:
pred_bat:
...
battery_rate_max: 10000
battery_rate_max_scaling: 0.33 # 3.3kw
battery_rate_max_scaling_discharge: 0.72 # 7.2kw
where I exploit use of battery_rate_max to model my different import and export rates (3.3kW in Backup mode, 7.2kW when exporting in Autonomous mode). It will correctly model charging at 3.3kW with this in place, and this is how it appears in the Predbat logs now:
Similarly, I've been experimenting with having the charge tapering >80% reflected. The Powerwall doesn't natively expose a soc_kw value required by Predbat to automatically model the curve, so instead (for now) I've manually modelled the charge curves based on previous charge cycles with the following:
pred_bat:
...
battery_charge_power_curve:
80: 0.8
85: 0.7
90: 0.5
95: 0.35
100: 0.25
However, this is not currently being reflected in the Predbat output and I suspect it's due to use of battery_rate_max instead of using charge_rate and discharge_rate. Again, I'm interested to get your thoughts on this.
FYI I've also noticed that the charging tapering (for me at least) became much more aggressive after the Powerwall updated to software version 25.34.3 (I was seeing tapering from charge levels as low as 25%). I've raised a case with Tesla about this, but the behaviour has since returned to normal without a software update, so I'm wondering whether it may be because my Powerwall is outside and we had period of colder weather which affected the charge rate around the same time 🤷♂️ I'm continuing to monitor this and will update the charge curve if it returns.
Is it meant to turn one off to enable the other? If it is, ill have to make sure they dont clash