ILS Glide Slope approach to Powerwall export timing on Octopus Flux

Is your Powerwall refusing to export on Octopus Flux? There’s a reason — and a fix.

I spent several months building a Home Assistant automation system to properly optimise Powerwall export on Octopus Flux. What started as a simple time-based switch evolved into something I’m calling an ILS Glide Slope system — borrowed from aviation approach logic — that calculates precisely when to start and stop exporting so the battery hits 0% at exactly 19:00, not before and not after.

The guide covers everything I learned the hard way:

The dummy rate plan — Tesla’s Time-Based Control algorithm compares export rate (29.32p) against import rate (38.26p) and concludes exporting loses money. It won’t cooperate no matter what HA tells it. The fix is a fake rate plan in the Tesla app that makes peak export look profitable. This alone will unblock a lot of people.

The ILS intercept logic — instead of guessing a start time upfront, the automation waits for actual export to be confirmed, then calculates exactly how long to hold before releasing for the final run to zero. Think of it as the battery intercepting a glide slope rather than aiming directly at the runway.

Seven automations, one YAML file — pre-flight check, INIT with verify-and-retry, export detection, intercept release, 30-minute monitoring, 18:45 safety stop, 19:00 reset, and restart recovery if HA goes down mid-window.

The full failure history — every version from naive fixed-time switching through to the current build, what broke, and why the design changed.

Full guide and YAML attached. Entity names are Teslemetry convention — adjust for your integration.


Tesla Powerwall Export Optimisation with Home Assistant and Octopus Flux

An ILS Glide Slope Approach to Precision Battery Discharge

February 2026 | v3.7


Overview

This guide documents a Home Assistant automation system that maximises export revenue from a Tesla Powerwall on the Octopus Flux tariff. It assumes you are comfortable with Home Assistant YAML and already have your Powerwall integrated via Teslemetry or a similar API.

The core insight — and the one that unlocks everything else — is that Tesla’s Time-Based Control algorithm will not export unless it perceives a financial incentive. Before solving the timing problem, you must first solve the motivation problem.

This guide covers:

  1. The dummy rate plan — why Tesla won’t export without it and how to fix it
  2. The ILS glide slope concept — how aviation approach logic maps onto battery discharge
  3. The YAML automation system — structure and what each part does
  4. The evolution — what failed at each stage and why the current design works

1. The Dummy Rate Plan Problem

Why Tesla refuses to export

Octopus Flux rates (late 2025):

  • Import peak 16:00–19:00: 38.26p/kWh
  • Export peak 16:00–19:00: 29.32p/kWh
  • Charge rate 02:00–05:00: 16.40p/kWh

On paper, the arbitrage is attractive: charge at 16.4p, export at 29.32p, net 12.92p/kWh. But Tesla’s Time-Based Control algorithm evaluates this differently. It compares the export rate against what it would cost to import at the same moment:

Export rate:   29.32p/kWh
Import rate:   38.26p/kWh

Tesla conclusion: exporting loses 8.94p vs keeping energy for import avoidance.
Decision: do not export.

No amount of Home Assistant commands will reliably override this. Even if you set mode to autonomous and reserve to 0%, Tesla will often revert or simply not discharge aggressively because its own algorithm sees no financial case.

The fix: a dummy rate plan

Inside the Tesla app, go to Powerwall > Time-Based Control and configure a custom rate plan that inverts the economics:

Period Dummy Buy Dummy Sell
Super Off-Peak 02:00–05:00 £0.05 £0.05
Off-Peak (all other hours) £0.25 £0.10
Peak 16:00–19:00 £0.40 £0.50

With these rates, Tesla sees peak sell (50p) > peak buy (40p) and discharges aggressively. The actual Octopus settlement uses your real rates — the dummy plan only influences Tesla’s internal decision-making.

Also set in the Tesla app:

  • Mode: Time-Based Control
  • Backup reserve: 0% (or as low as permitted)
  • Allow Export: Everything (not Solar Only)

Without “Allow Export: Everything”, the Powerwall will not export battery energy regardless of other settings.


2. The ILS Glide Slope Concept

The problem with naive export automation

The export window is 16:00–19:00 — three hours. A fully charged 13.5 kWh Powerwall discharging at 5 kW will be empty in roughly 2h 42min. If export starts at 16:00, you hit zero around 18:42 and spend the last 18 minutes importing from the grid at peak rates, eroding a meaningful portion of export revenue.

The naive fix — start exporting later — creates a different problem. Delay to 16:18 and any unexpected consumption spike means the battery still runs out before 19:00, and you have lost the earlier window for no gain.

The aviation analogy

An Instrument Landing System (ILS) guides an aircraft down a fixed glide slope to the runway threshold. The aircraft does not aim for the runway immediately — it intercepts the slope at altitude and follows it down, arriving at the threshold at the correct height, not before and not after.

Applied to battery discharge:

  • Runway threshold = 0% battery at exactly 19:00
  • Glide slope = the ideal discharge rate given current battery and time remaining
  • Intercept point = the moment when exporting at full rate lands you at 0% at precisely 19:00
  • Hold mode = the aircraft maintaining altitude before intercepting the slope

How the intercept calculation works

When export is first confirmed (grid_power < -1 kW), the automation captures actual battery level and calculates:

available_for_export = battery_kWh - expected_consumption_kWh
discharge_time_minutes = (available_for_export / 5.0) * 60
hold_minutes = minutes_remaining_until_19:00 - discharge_time_minutes
intercept_time = now + hold_minutes

If discharge_time_minutes >= minutes_remaining: no hold needed, export continues.

If a hold is needed: switch to self_consumption (minimal discharge), release back to autonomous at the calculated intercept time.

Expected consumption is configurable per day type (weekday / Saturday / Sunday).

Progressive tolerance bands

Early versions overcorrected — minor sensor noise triggered adjustments that cost more than they saved. From v3.3 onwards, tolerance bands tighten as the window progresses, mirroring how an ILS approach becomes more precise on final:

Phase Tolerance Rationale
Hour 1 (16:00–17:00) ±30 min Plenty of time to correct later
Hour 2 (17:00–18:00) ±15 min Tighter but still room to adjust
Hour 3 (18:00–18:45) ±5 min Precision required — corrections are expensive
Final (18:45–19:00) ±3 min Emergency stop only

3. The Automation System

Design principles

  • Never block export attempt — commands sent regardless of sensor state
  • Blind fallback — if verification fails, send commands anyway and alert the user
  • Fail towards export — any ambiguous state prefers attempting export over not
  • Restart resilience — if HA restarts during the window, the system recovers

Automations overview

ID Trigger Purpose
0 15:59 (time) Pre-flight — log state of all entities and sensor freshness
1 16:00 (time) INIT — apply all export settings, verify, retry, blind fallback if still failing
2 Every 5 min Detect export — watch for grid_power < -1 kW, calculate intercept
3 Every 1 min Intercept check — release to autonomous when intercept_time reached
4 Every 30 min Monitor — log battery state and export progress
5 18:45 (time) Safety — emergency stop if battery critically low
6 19:00 (time) End — log results, reset flags, dismiss notifications
7 HA start Restart recovery — pick up correct state if HA restarts mid-window

Key entities

Controls (write):

select.home_energy_gateway_operation_mode     → self_consumption | autonomous
number.home_energy_gateway_backup_reserve     → 0–100%
switch.home_energy_gateway_allow_charging_from_grid
select.home_energy_gateway_allow_export       → solar_only | battery_ok

Sensors (read):

sensor.home_energy_gateway_percentage_charged
sensor.home_energy_gateway_grid_power         (negative = exporting)

State tracking (input helpers):

input_boolean.glide_slope_enabled
input_boolean.export_protection_export_detected
input_datetime.export_protection_intercept_time   ← persists across restarts
input_text.export_protection_last_decision
input_number.export_window_consumption_weekday/saturday/sunday

Automation 1: INIT (16:00)

The most critical automation. Applies all four export settings, waits 90 seconds, verifies. If any setting failed, retries those specific settings only, waits another 120 seconds, verifies again. If still failing, sends a blind fallback (all commands, twice, 30 seconds apart) and creates a persistent notification.

Key design decision: sensor unavailability does not block this automation:

# Only condition: master enable toggle — no sensor availability checks
condition:
  - condition: template
    value_template: "{{ is_state('input_boolean.glide_slope_enabled', 'on') }}"

Automation 2: Export detection (every 5 min)

On first confirmed export (grid_power < -1 kW):

  1. Records current battery as export_window_starting_battery_kwh
  2. Calculates intercept time
  3. Stores in input_datetime.export_protection_intercept_time
  4. If hold needed: switches to self_consumption
  5. Sets export_protection_export_detected = on
  6. Dismisses any pending manual verification notification

Automation 3: Intercept release (every 1 min)

Fires only when export_detected = on and current time has reached the stored intercept time:

- condition: template
  value_template: >
    {% set it = states('input_datetime.export_protection_intercept_time') %}
    {% set ih = it[0:2] | int %}
    {% set im = it[3:5] | int %}
    {{ now().hour > ih or (now().hour == ih and now().minute >= im) }}

Automation 7: Restart recovery (homeassistant: start)

Added in v3.7. On HA start, checks if 16:00 < now < 19:00 and applies one of three recovery paths:

Scenario A — Pre-export (export_detected = off): Re-sends all export commands. The 5-minute detect loop resumes on its next tick.

Scenario B — Pre-intercept (export_detected = on, before intercept_time): Restores autonomous mode. Intercept automation resumes on next 1-minute tick.

Scenario C — Post-intercept (export_detected = on, after intercept_time): Restores self_consumption. 18:45 safety check and 19:00 reset run as normal.

A 30-second delay is built in on startup to let Teslemetry reconnect before commands are issued. The intercept time persists in input_datetime across restarts — no recalculation needed.


4. The Evolution — What Failed and Why

v1: Fixed time switching

Hard-coded: autonomous at 16:00, self_consumption at 18:30. Failed whenever battery was below ~85% or consumption varied. No adaptation.

v2: Shadow mode with fixed glide slope

Compared actual battery against a theoretical slope every 30 minutes. Problems: 30-minute checks too infrequent (battery could cross the slope undetected), template triggers unreliable (fixed by switching to time_pattern), static consumption estimate often wrong.

v3.0–3.2: Intercept logic

Replaced slope-tracking with the intercept calculation. Eliminated acting on a theoretical slope. Remaining issue: blocking conditions on sensor availability caused silent failures — if Teslemetry was slow, the whole automation would abort at the condition check with no notification.

v3.3: Inverted logic — export first, hold second

Previously: calculate hold period, delay export start. This meant calculation errors wasted the early window entirely.

v3.3 inverted this: always start exporting at 16:00, detect actual export, then calculate how long to hold before final discharge. Benefits:

  • Export revenue starts at 16:00 regardless
  • Intercept calculation uses real battery data from an actual export state
  • Errors in consumption estimation affect only hold timing, not whether export happens

v3.4–3.5: Verification and pre-flight

Added 15:59 pre-flight logging. Added verify-and-retry to INIT — failed settings trigger a targeted retry before escalating to blind fallback.

v3.6: Never block export

Root cause of a January 2026 failure: a sensor availability condition in INIT evaluated false (Teslemetry reconnecting after a brief outage). The automation did not run. No commands sent. No notification. Silent loss of the window.

v3.6 removed all sensor availability conditions from INIT. If sensors are unavailable, commands are sent anyway. A notification is always created on failure. Rationale: attempting commands when uncertain is better than doing nothing.

v3.7: Restart resilience

Following an unplanned HA restart at ~16:14 on a live export day, Automation 7 was added. Time-based triggers 0–6 had already fired or were scheduled for later — HA had no mechanism to catch up. The homeassistant: start trigger with a time condition fills this gap.


5. Lessons Learned

On Tesla integration:

  • Configure the dummy rate plan first. Without it, no automation will reliably unlock export.
  • Allow Export must be “Everything” in the Tesla app, not “Solar Only”.
  • Tesla’s algorithm can override HA commands. The dummy rates keep it cooperative.
  • Design automations to act even when sensor feedback is delayed or unavailable.

On Home Assistant automation design:

  • time_pattern triggers are more reliable than template triggers for time-based firing.
  • Never gate export commands behind sensor availability checks — prefer blind send with notification.
  • homeassistant: start with a time condition is the correct pattern for restart recovery.
  • input_datetime helpers persist across restarts — use them for state that recovery logic needs.
  • continue_on_error: true on logging and notification services prevents a failed log write from halting the main action sequence.

On the economics:

  • Overcorrection costs real money — tolerance bands prevent unnecessary interventions.
  • Household consumption during the export window is the key variable. Measure it per day type.
  • Starting export at 16:00 and holding in the middle beats delaying the start.

6. Getting Started

Prerequisites

  • Tesla Powerwall with Teslemetry (or equivalent HA integration exposing the entities above)
  • Octopus Flux tariff (or adapt the rate logic for your tariff)
  • Home Assistant with shell_command configured for CSV logging (optional but recommended)

Recommended setup order

  1. Configure the dummy rate plan in the Tesla app first. Confirm the Powerwall exports aggressively during 16:00–19:00 before adding any automation.
  2. Create the input helper entities (input_boolean, input_datetime, input_text, input_number) from the entity list in Section 3.
  3. Install the automation YAML. Enable glide_slope_enabled and run one window in monitoring-only mode by commenting out the select.select_option service calls.
  4. Review the CSV logs after the first live window. Tune consumption estimates (weekday/Saturday/Sunday) based on actual data.
  5. Enable full control mode and monitor for a week before considering the system stable.

Tuning the consumption estimate

Start with a conservative (high) estimate — this causes the hold to start earlier than strictly necessary, which is safe. Adjust down as you gather data.

A useful heuristic: during the hold phase (self_consumption), check grid_power. If it’s close to zero, your estimate is broadly correct. If the Powerwall is consistently importing during the hold, your estimate is too low.


Entity names use the Teslemetry naming convention — adjust to match your integration.