Mastodon

Agile Home: Automating Appliances Around Octopus Agile Pricing

Agile Home: Automating Appliances Around Octopus Agile Pricing

Background

Octopus Agile is a half-hourly electricity tariff where prices vary based on wholesale market conditions. Rates can range from negative (Octopus literally pays you to use electricity) to over 60p/kWh during peak demand. The average is around 15–25p, but the spread is wide enough to make smart scheduling genuinely worthwhile.

I built Agile Home — a native Android app backed by a small Python server — to automate two appliances around these variable rates: a hot water heater and a Miele chest freezer.


The Setup

  • Hot water heater — a Tuya-compatible immersion heater controlled via the Tuya Cloud API
  • Miele FN 4722 E WS chest freezer — controlled via the Miele 3rd Party Developer REST API
  • Server — a Flask API running on a Hetzner VPS, with cron jobs firing every 30 minutes
  • App — a native Android app (Java, minSdk 26) that displays status and allows manual overrides

Electricity rates are fetched from the Octopus Energy GraphQL API and cached in a local JSON file.


Hot Water Heater Logic

The heater cron script runs every 30 minutes and makes one of several decisions:

Normal operation

The script finds the cheapest upcoming half-hour slot and turns the heater on during that window for a fixed duration (default 15 minutes). A configurable threshold (skip_above_pence, currently 25p) prevents heating during expensive slots entirely.

Negative rates

When negative-rate slots exist, the script identifies the most negative slots up to the number needed to heat the tank, and runs exclusively during those. Octopus is paying you to heat your water.

Cooldown

After a scheduled heating cycle, a configurable cooldown period (default 3 hours) prevents re-firing too soon — a full tank doesn’t need heating again immediately. Importantly, a manual boost (a short 15-minute override) is excluded from the cooldown timer, since it’s just a quick top-up rather than a full scheduled cycle.

Boost and manual modes

The app provides a boost button (runs for 15 minutes regardless of rate), a lock-on mode, and a holiday lock-off mode.


Freezer Pre-cooling Logic

A chest freezer is essentially a thermal battery. Running it colder before an expensive peak means the compressor can stay off during the peak, shifting energy consumption to cheaper periods. This is the core idea behind the freezer logic.

Two pre-cooling triggers

1. Lead-time trigger When an expensive slot (>30p/kWh) is approaching within a configurable lead window (default 90 minutes), the freezer target temperature is lowered to pre-cool. The target is calculated dynamically.

2. Negative rate trigger When a negative-rate slot is current or imminent (within 2 hours) and an expensive peak is forecast later the same day, the freezer immediately drops to its minimum setpoint (-26°C). This is the best possible scenario: Octopus pays you to run the compressor, building up thermal headroom for the expensive evening peak.

Dynamic pre-cool setpoint

Rather than a fixed pre-cool target, the setpoint is calculated from two factors:

Warming rate — how fast the freezer drifts toward ambient temperature when the compressor is off:

warming_rate = k × (T_ambient − T_normal)

where k = 0.07 °C/hr per °C differential, calibrated from observation (3°C/hr drift at 25°C ambient with a -18°C normal setpoint). Ambient temperature is fetched daily from the Open-Meteo API (no key required).

Price differential — how aggressively to pre-cool, scaled by the difference between the average pre-cool rate and the average peak rate:

coast_fraction = min(1.0, (avg_peak_rate − avg_precool_rate) / 50p)

A 50p differential triggers maximum pre-cooling; smaller differentials scale proportionally.

Combining them:

desired_coast_hours = coast_fraction × peak_duration_hours
precool_depth       = warming_rate × desired_coast_hours
precool_setpoint    = max(−26°C, round(−18°C − precool_depth))

On a typical summer day (15°C ambient, 30-minute lead, 30p peak): warming rate ≈ 2.3°C/hr, which gives a precool depth of around 4–6°C and a target of -22°C to -24°C.

On a day with negative rates ahead of a 60p peak: the differential exceeds 50p, coast fraction = 1.0, and the target clamps to the minimum -26°C.

During the peak

At the start of an expensive slot, the freezer target is restored to -18°C. With the compressor’s thermostat now targeting -18°C and the actual temperature well below that, the compressor stays off — the freezer coasts on its thermal mass. The app shows:

  • “Coasting through expensive peak” — when actual temperature is more than 1°C below target (compressor off, genuine coasting)
  • “In expensive peak — compressor maintaining” — when the freezer has warmed back to equilibrium

Estimated savings

Each pre-cooling session is logged with an estimated saving:

energy_kwh   = scaled_watts / 1000 × peak_hours
saving_pence = energy_kwh × (avg_peak_rate − avg_precool_rate)

Freezer power consumption is scaled by ambient temperature relative to the 20°C rating condition, since the compressor works harder in summer.


The Android App

The app has four screens:

  • Heater — current status, rate, reason for current decision, boost button, lock controls
  • Freezer — current and target temperature, pre-cooling status, ambient temp and warming rate parameters, manual setpoint control, SuperFreeze toggle
  • History — 7-day bar chart of heater energy/cost or freezer pre-cooling hours, with estimated total savings
  • Settings — server URL, API key, skip threshold

The server exposes a REST API (Flask, port 5000, X-API-Key auth) that the app polls every 30 seconds. Freezer status is a combination of live Miele API data and the pre-cooling state written by the cron job.


Results

The system has been running for a few weeks. On days with negative rates ahead of 30–60p peaks, the pre-cooling strategy shifts the freezer’s compressor load almost entirely into negative or very cheap slots. Estimated savings per event are modest (1–3p), but across a year and with the water heater consistently hitting the cheapest slots, the cumulative benefit is meaningful — and the engineering is the point.