Managed loads

Appliances the weekday-average forecast cannot follow — a pool heat pump, a sauna, a hot water tank, your heating. What they are, which profile to pick, and what each setting does.

What this is for

  • I want my pool, sauna or hot water tank heated in the cheapest hours, and a signal I can wire to a relay.
  • I want the load forecast to stop being wrong about an appliance the weekday average cannot follow.
  • I already model my heating or air conditioning elsewhere and want to hand EOS Connect the result.

You need a power sensor in watts for the appliance, and for a heated store a temperature sensor as well. An outdoor store also needs the outside-temperature forecast.

Managed loads

The household load forecast is built from what the same weekday drew 7 and 14 days ago. That models a base load well and anything weather- or state-driven badly: a heat pump switched to cooling, a cold snap, a pool heated to a target temperature. Settings › Managed Loads is where you add those, one card each.

You get two things out of it. The forecast stops being wrong about that appliance — which is worth having on its own, because every decision about your battery is made against it. And for an appliance whose timing is yours to choose, you get a released / blocked signal to wire to a relay, so the thing actually runs when the energy is cheap rather than merely being predicted accurately.

Two kinds

  • Scheduled — “this needs a certain amount of energy, you pick when”. EOS Connect works out how much, places it in the cheapest hours the appliance is allowed to run in, and publishes a released / blocked signal you can wire to a relay. Pool heat pump, sauna, hot water tank, buffer tank, external energy budget.
  • Forecast only — “this is what it will draw, I cannot move it”. The values are added to the forecast exactly as given, either fetched from an entity you name or pushed in by your own automation. There is no release signal, because the timing was never ours to choose. External load profile — use it for space heating and air conditioning.

How a managed load reaches the optimizer

Whichever kind it is, a managed load enters the optimization by one of two routes, and which one it takes depends on the optimizer you run rather than on anything you set. It is worth knowing which, because it is what decides how much the price limit can actually promise.

  • Added to the load forecast. EOS Connect works out the energy the appliance needs, places it itself in the hours the appliance is allowed to run, and adds the result onto the household load curve it sends to the optimizer. The optimizer sees a house that will draw more at those hours — not a separate appliance, and nothing it may move. This is the route for eos_server and for a remote EVopt, and it is the only route a forecast only load ever takes, since there was never anything to place.
  • Handed to the solver as a device of its own. The built-in optimizer schedules managed loads itself. A scheduled load is then kept out of the household curve and passed beside it instead — the energy it needs, the power it draws, which slots it may use, how long it must run once started — so the solver places it alongside the battery rather than after it. That is what lets a load run in an expensive hour off a battery charged cheaply for the purpose, which no amount of placing it beforehand could arrange.

Either way the appliance's own measured consumption comes out of the household base load before its forecast is added back, so it is never counted twice. That is what the power sensor is for.

The power sensor

Every sensor field in EOS Connect takes watts, and this one is no exception. It is not only used to subtract the appliance from the base load — it answers two questions a meter reading cannot: is this running right now, and how fast is the store warming for the power going in. That second one is where the measured efficiency comes from.

If you only have a kWh counter, add a derivative helper in Home Assistant and point the field at that. Pointing it straight at the counter is caught and reported on the alerts panel: 1234 W and 1234 kWh are the same number, so it would otherwise look like an appliance that never switches off.

A managed load with a sensor has its own consumption removed from the household base load before its forecast is added back, so the same appliance is never counted twice. A heated store does this from its power sensor; an external load profile only when you point Replaces sensor at a meter, because what you send it is often just an extra on top rather than the whole appliance. This is what makes a managed load different from Additional Load, which models a shiftable appliance such as a dishwasher — a runtime and a total, for the optimizer to place. Do not configure the same sensor as both.

Choosing a profile

A load's type is the first thing you set, and it decides which of the settings below even appear. It is not six separate implementations: four of the six are the same heated-store model with different numbers in it. What a type actually selects is a set of starting values, where the ambient temperature comes from, and whether a cover and a season mean anything at all.

That is deliberate. A pool, a sauna, a hot water tank and a buffer tank are one problem with different constants, so modelling them once means the calibration that measures a pool's heat loss measures a sauna's too. Pick the one closest to your appliance.

Profile What it is for Scheduled Ambient temperature Particular to it
Pool heat pump An outdoor pool held at a target temperature yes outdoor forecast cover, season, solar gain
Sauna Heated to a target for a session at a particular time yes indoor starts with a deadline
Hot water tank Domestic hot water held at a target yes indoor small store, low losses
Buffer tank Heating buffer for a heat pump or boiler yes indoor larger volume, lower target
External energy budget “Needs 8 kWh within twelve hours”, from your own automation yes not used no sensors, no physics
External load profile A ready-made curve — space heating, air conditioning no — injected as given not used fetched or pushed; no release signal

Each table below lists only the settings that profile actually has, with the value that profile starts from — a sauna aims at 90 °C where a pool aims at 28. That is what the Every setting list cannot show you: a field four appliance types share carries one default there, and it is the pool's. Raise the detail level above to see more of the settings.

Pool heat pump

An outdoor pool, heated to a target and losing heat to the air and the sky. It is the only profile with a cover and a season, because it is the only one where either means anything, and the only one that reads the outdoor temperature forecast rather than an indoor figure.

Its starting values assume an air-to-water pump: daylight hours only, a floor of 12 °C and a frost trip, because such a pump barely works in the cold and its heat exchanger can freeze. The surface area is the water surface — that is where a pool loses its heat, not through the walls.

Sauna

Air and stones rather than water, heated quickly and wanted at a time rather than merely cheaply. That is why it is the only profile that starts with a deadline of six hours and with the cheapest_slots strategy, and why it starts with no allowed window and no daily cap: a sauna is wanted when it is wanted.

Its volume_m3 is a water equivalent fitted to a typical heat-up time, not a real volume, and its efficiency starts at 1.0 because a sauna heater is a resistive element rather than a heat pump. Both are corrected from the first real session onwards.

Hot water tank

Domestic hot water. A small, well-insulated store, so the settings that matter are the target and the deadband rather than the physics — a 3 K deadband is what stops it being topped up continuously for the sake of a tenth of a degree.

Ambient is indoor, so it needs no temperature forecast and no outdoor sensor. If the tank is heated by a heat pump rather than an immersion element, raise cop_nominal; the calibration will find the real figure, but a closer start reaches it sooner.

Buffer tank

A heating buffer. The same model as the hot water tank with a larger volume and a lower target, and lower losses again because it normally stands inside the heated envelope — what leaks out of it is not really lost.

Worth saying what this profile is not: it models the store, not the heating system drawing from it. If what you want is the house's heat demand over the next two days, that is an external load profile.

External energy budget

For an appliance EOS Connect cannot model but you can. You send it “this needs 8 kWh within the next twelve hours” and it places that energy in the cheapest allowed hours and publishes the released / blocked signal, exactly as it would for a heated store.

There are no sensors and no physics here: the demand is whatever you last pushed, and it expires after ttl_minutes rather than being re-derived. See Pushing it in for the payload.

External load profile

For something whose timing was never yours to choose — space heating, air conditioning, anything you already model elsewhere. You hand over a ready-made curve, either fetched from an entity or pushed, and it is added to the forecast exactly as given.

This is the one profile that is not scheduled, so it has no release signal and none of the operating settings apply to it. What it does have instead is a source and a unit; see Feeding a load profile in.

When a load may run

These apply to every scheduled profile — the four heated stores and the external energy budget. They are limits the optimizer is given, not preferences it may trade away.

  • Allowed window and season — a pool pump defaults to daylight hours in the swimming season. Nobody wants it audible at 03:00 for two cents.
  • Minimum runtime — the plan is recalculated every few minutes; without this a compressor would be cycled every time the cheapest hour moved.
  • Minimum ambient temperature — an air source heat pump barely works in the cold and its heat exchanger can freeze.
  • Frost protection — below this the appliance runs immediately and price stops mattering.
  • Strategy — Combined uses the grid price but values your own PV surplus at the feed-in tariff, so free solar beats cheap grid energy.
  • Shared power budget (Settings › Load) — without it a pool pump and a sauna both take the single cheapest hour and the forecast shows a peak your house cannot draw. Every optimizer honours it. Which load yields when they compete depends on who is placing them: lower priority numbers go first where this module plans, while the built-in optimizer weighs them by what each is worth — its Max Price — and priority does not apply.

Those last two are not per-load settings; they bound every managed load together and live under Load rather than on a card:

What the price limit means

Max Price is a limit on what you pay per kWh for this load’s energy. What it can guarantee depends on which route the load takes into the optimization, and the overlay says which rule is in force:

  • Built-in optimizer — the load stops being part of the household forecast and becomes something the solver schedules, alongside the battery and everything else. It runs in any hour where the energy genuinely costs less than your limit — including an expensive hour served from a battery that was charged cheaply for the purpose, which is a trade only something seeing both sides can make. What you actually pay per kWh comes in under the figure you set, and the overlay reports it — measured, by solving the same horizon again without the load and taking the difference in your own bill. On a sunny day that falls towards your feed-in tariff rather than to zero, because self-consumed solar still costs you the export you gave up; on a dark one it rises towards the limit. With more than one load scheduled the total is split between them by energy, which the card says.
  • External optimizer — EOS Connect cannot reach inside EOS Server or a remote EVopt, so the rule is simpler: skip any hour whose own price is above the limit, and hand the resulting plan over as part of the load forecast. Safe, but blunt — it refuses a dear hour even when the rest of the day was nearly free, and it cannot arrange cheap energy in advance. The overlay then shows the tariff of the hours the load runs in, which is a rougher guide: it cannot see that the energy came off your roof, so a sunny day can read dearer than a dark one.

Everything else about a managed load behaves the same either way: the allowed window, the minimum outside temperature, the minimum runtime and frost protection are limits the solver is given, not rules it may trade away. The one setting that stops applying under the built-in optimizer is Priority: loads are placed together rather than one after another, so there is no queue to be near the front of.

Set it to 0 for no limit; frost protection ignores it either way.

How long it is allowed to run

Three settings bound the runtime and they stack, so the tightest one wins. It is worth knowing which is binding before changing anything:

  • Window Start / Window End — the hours of the day it may run at all. 8 to 22 gives fourteen hours.
  • Max Runtime Hours Per Day — a cap inside that window. Set to 12 with a 14-hour window, twelve is what you get; the other two hours are simply never used. Set it to 0 for no cap.
  • Min Ambient Temp — removes individual slots the air is too cold for, wherever they fall.

Compare Energy needed with Planned in the overlay to see whether they bind:

  • Planned equals needed — the plan covers the demand; the settings are not in the way.
  • Planned is lower — the load cannot get all the energy it needs, and the card names the setting responsible: the price cap, the allowed hours, the minimum outside temperature, the season, the daily runtime cap or the shared power limit, whichever ruled out the most slots — unless no setting would help, in which case it says that instead. A load can want more energy than the appliance could deliver running flat out through every slot left, and naming the setting that excluded the most of them is then true and useless: you raise it and the store still never reaches target. The strip colours each slot by what became of it — blue where the load will run, with the bar height showing how much, amber where you are rationing it (price cap, daily runtime cap, shared power limit), green where it is simply not allowed to run then (outside the hours, out of season, too cold), and faint grey where nothing was needed. Hovering any bar gives the exact reason for that slot.
  • Planned is lower and the pump runs flat out anyway — the appliance is undersized for the losses rather than short of time. A cover changes that faster than a bigger pump.

Energy needed covers the rest of the optimization horizon, not a day. It shrinks through the day and steps back up at midnight, so compare it with Planned beside it rather than with yesterday's figure.

How a heated store is modelled

Almost nothing in this section is configured. It is here because a prediction you cannot account for is hard to trust, and because two of the things that are configured — the cover sensor and the ambient temperature — only make sense once you know what they are for.

What is measured, and what you supply

You supply the volume, the surface losing heat, the rated power and a rough efficiency. Those are a starting point, not an answer — nobody knows their pool's heat loss coefficient. EOS Connect measures them from ordinary operation.

Every pair of consecutive readings is one equation of the same energy balance: what the store gained equals what the appliance put in, minus what leaked out. The heat loss and the efficiency are both unknowns in it, and they are solved for together across the recorded history rather than one after the other.

That matters because the obvious order does not work. Measuring the losses while the appliance is off and then using them to measure the efficiency while it is on sounds right, but a pool held at its target barely changes temperature while heating — so the assumed loss dominates the efficiency measurement, and a wrong starting guess stays wrong. Solving both at once has no such bootstrap.

Readings are grouped into windows long enough for the temperature to have actually moved, rather than paired cycle by cycle. This matters more than it sounds: a store's temperature is read to some finite precision, and the calculation divides that reading by the elapsed time. On an 18 m³ pool sampled every five minutes, one 0.1 °C step of the sensor works out as 25 kW — against a real heat loss of about 1 kW. Every reading was then rounding noise many times larger than the thing being measured. The sensor's precision is measured from the data, so the window sizes itself: minutes for a sauna, hours for a pool.

Two things keep it honest where the history is thin. The configured values act as a weak prior worth a couple of samples, so an under-determined fit falls back to what you entered instead of to noise. And the appliance has to be seen both running and idle, across a range of outdoor temperatures, before the two can really be separated: a colder day raises the losses and lowers the efficiency at the same time, so a pump that never stops cannot tell you which.

What is fitted is two loss coefficients, not one: the store uncovered, and the store covered. The fraction between them is the one number nobody can look up — a supplier’s data sheet is for still air over new material — so it is measured rather than trusted. Holding it at the configured guess turned out to be the worst place to be wrong: a pool covered five nights in six gives a fit that is almost entirely about the covered state, so the error in that one constant had nowhere to accumulate but in the uncovered coefficient, which then climbed refit after refit until the predicted standing losses made the target unreachable. Both states have to actually occur before they separate; until then the calibration panel says the cover is still assumed rather than measured.

The fit learns from the sensor, never from the forecast. Both are recorded on every sample, and they are for different jobs: the bias-corrected forecast is the right input for a slot nobody has measured yet, and the wrong one for a slot that has already happened. The correction only removes the average error for that hour of the day, and what it leaves would go straight into the temperature difference that fixes the loss coefficient — worst of all overnight, where the correction is largest and where the idle windows that pin the losses down mostly are.

The confidence figure accounts for all of that: how many windows there are, whether they include both running and idle periods, and how much of the measured behaviour the fit actually explains. If the readings do not fit — most often a temperature sensor too coarse to see how slowly the store changes — confidence stays low and the overlay says so, rather than reporting a confident wrong answer.

The overlay says where the calibration stands: learning with a count while it still needs observations, then measured, followed by whatever remains assumed — a cover it has not seen both on and off cannot be measured, and is named rather than hidden. There is no percentage to wait on: part of a pool's behaviour is never explainable (rain, wind, swimmers), so a bar would never fill. A numeric confidence is still on the API and MQTT for anything that wants one. It survives restarts, and is seeded from up to two weeks of recorded samples.

If the inputs it was learning from turn out to have been wrong — an ambient temperature that was not what you thought, say — use Reset on the overlay card, or POST /api/managed_loads/<id>/calibration/reset. The recorded samples carry those inputs, so without a reset they keep dragging the fit until they age out of the retention window on their own.

The sun

An outdoor store gains heat from the sun, and on a pool that is worth kilowatts — enough that a model without a term for it blames the difference on the heat loss coefficient and gets that wrong instead. So the calibration learns what the sun adds, alongside everything else.

Nobody has an irradiance sensor, so PV generation stands in for one. The fitted figure absorbs the array size, the water’s absorptivity and whatever the cover lets through — none of which you need to know or enter. Two sources, in order of preference:

  • A cumulative PV counter, if you have set one under PV Auto-Scaling. Differenced across a measurement window it gives the exact average for that window, integrating passing cloud rather than sampling it. Nothing extra to configure on the load — it is the same household fact, read once.
  • The PV forecast, otherwise. Weaker, since it is a forecast and read at one moment, but quite enough to tell a bright window from a dark one. Nothing is wrong without a counter; the overlay simply says a meter would be sharper.

The term stays at zero until the history holds both bright and dark windows. A run of overcast days says nothing about what full sun does, and a store that can never separate the two behaves exactly as it did before this existed.

The cover

A pool cover is worth roughly two thirds of the heat loss, and it goes on and off on a daily rhythm. Reading the switch once and assuming that state for the whole two-day horizon gets most of it wrong — planning at nine in the evening, with the cover just pulled over, predicts a covered pool through the whole of the next afternoon.

Two sources answer better than one, and they are good over different distances:

  • the switch, for the next couple of hours. It is reliable about now and says little about tomorrow;
  • the habit beyond that — how often the cover has actually been on at that hour of the day, learned from the samples already recorded for calibration. Nothing to configure, and nothing to keep in step by hand.

The habit is blended rather than switched: an hour covered four nights in five is planned as four fifths of a cover, which is more honest than rounding it either way. Recent days count for more, so a cover that starts being left off shows up within a week. An hour with too little history — fewer than about three days — defers to the switch instead of being guessed at.

Outdoor loads need an outdoor temperature

A pool's losses and its heat pump's efficiency both turn on the air temperature, so an outdoor managed load is only as good as the temperature it is given. It takes the best of three, in order:

  1. the outside-temperature forecast, which is the only one that can see into tomorrow — and the horizon is two days long;
  2. the load's own Ambient Temp Sensor, held flat. It cannot see ahead, but it measures this actual site;
  3. a fixed fallback, which is a guess and is reported as one on the alerts panel.

When you have both, they are combined rather than ranked. A regional forecast knows the shape — that tonight drops to 11 °C and tomorrow reaches 22 — while your own thermometer knows the level at your site. The forecast is shifted so it agrees with your sensor, which keeps both. On a pool a 3 K difference is around a quarter of the standing loss, so it is not a rounding matter.

The shift is learned per hour of the day, not taken from the latest reading and not averaged into one number. That matters because such a gap is rarely constant: at one installation the station read 4 K below every model tried — ICON-D2, ICON-EU, ECMWF and GFS all agreed with each other and none with the garden — which is what cold air draining into a hollow overnight looks like. By mid-afternoon the same site sat close to the model. A single average is wrong at both ends of that, and applied across a two-day horizon it puts a night-time correction on tomorrow's afternoon.

An hour needs a few days of agreement before its own offset is used; until then it falls back to the site's all-day average, and before there is even that, to no correction. Recent days count for more, so a sensor being moved works its way in within a week. The offset is bounded at 8 K, and a reading more than 20 K from the forecast is discarded rather than learned from — the two are not describing the same air — with the alerts panel saying so. The overlay shows the measurement, the value the model used, and the offset applied for that hour.

If your own thermometer disagrees with the forecast, it is usually right: a weather model resolves a grid cell of a few kilometres and cannot see your hollow, your ridge or your courtyard. The same discrepancy is familiar from phone weather apps. Nothing needs configuring — give it a day and the correction appears on its own.

The forecast is fetched for coordinates, which normally come from your first PV installation. Sources that are not location-based — EVCC, Solcast, Victron, timeseries — need no installations, so set Latitude and Longitude under Settings › System instead. Leave them at 0 if you do not need them.

If you switched to one of those sources after configuring installations, the old entries stay in the database and the PV Installations section stops showing them. A site location under System then wins: the coordinates in use are the ones you can see. With no site location set, a stored entry is still used rather than losing the forecast altogether — but that is a location you can no longer read or edit, so PV Source says so and the log warns once.

Settings › PV Source › Temperature source chooses who supplies the curve:

  • Open-Meteo (default) publishes the air temperature directly, needs no API key, and returns whole local days — which is already how EOS Connect indexes a forecast.
  • Akkudoktor derives it from a PV forecast query against api.akkudoktor.net/forecast, which proxies a weather provider that rate-limits it. When that provider refuses, the API relays a 429 and nobody gets a forecast, whatever their own request rate — and because the temperature rides on a solar query, a solar rate limit takes it down too. EOS Connect backs off with a doubling hold rather than asking every fifteen minutes, reports it once rather than once a cycle, and resumes on its own.

One curve is fetched and shared by the optimizer and by every outdoor managed load, so this is a single choice for both rather than one each. While no forecast is available, an outdoor managed load falls back to its Ambient Temp Sensor — which is the best reason to configure one.

The forecast used to be fetched only for the EOS Server backend, since EVopt does not consume temperature. Managed loads changed that: an outdoor load now asks for the curve on its own account, whichever optimizer you run. eos.temperature_forecast_enabled is still the way to stop the requests entirely.

Feeding a load profile in

If you already model something elsewhere — a heating curve against the outdoor temperature forecast in Home Assistant, say — add an External load profile and hand EOS Connect the result. It does not need to know anything about heat pumps, cooling or washing days.

There are two ways round, and Profile source on the load chooses between them:

  • Fetched — you name an entity and EOS Connect reads it every few minutes. Pick this if what you have is a template sensor holding the numbers. Nothing to write, and nothing to keep running.
  • Pushed — your automation sends the numbers when it has them. Pick this if the forecast is produced on its own schedule, or by something outside Home Assistant.

Fetching it from an entity

Set Profile source to timeseries and give it the entity, the path to the array inside it, and what the values are measured in. It is the same arrangement as the price and PV sources, and it takes the same format — see Home Assistant Template Snippets for ready-made templates you can adapt.

Entity      sensor.heat_pump_forecast
Path        attributes.forecast
Unit        W                          — or kW, Wh, kWh

attributes:
  forecast:
    - start: "2026-01-15T00:00:00+01:00"
      value: 0
    - start: "2026-01-15T01:00:00+01:00"
      value: 1400
    ...

Put the array in an attribute, not the entity's state: a Home Assistant state is capped at 255 characters, which a 48-value series will not fit. Each entry needs a start; end is worked out from the next one. Publish as far ahead as you can model — EOS Connect plans 48 hours, and uses whatever part of that you cover.

If the entity cannot be read, the last good forecast stays in place rather than the load dropping out of the plan — a Home Assistant restart should not move your battery. It is only given up after TTL minutes, at which point the load simply stops contributing. The first failure is written to the log; repeats are not, so one dead sensor cannot bury everything else.

Pushing it in

Leave Profile source on push and send it:

POST /api/managed_loads/<id>/push

{"value_wh": 1500}                       one hourly average, applied across the horizon
{"values": [0, 0, 1200, 1400, ...]}      24 or 48 hourly values, or 96 or 192 quarter-hourly
{"total_wh": 8000, "deadline_hours": 12} an energy budget for EOS Connect to place

The resolution is inferred from how many values you send and resampled to whatever the optimizer is running on, so your sender keeps working if you change the slot length. Add "unit": "W" for average power, "start": "now" if the series begins at the current slot rather than midnight, and "ttl_minutes" to control how long it stays valid. The response echoes the normalised series back so you can check the alignment.

The same payload can be published to eos_connect/managed_load/<id>/set, including as a bare number. Retained messages are honoured on this topic, so a pushed forecast survives a restart.

Values may be negative, whichever way they arrive. That is how you correct the forecast downwards — the difference between a heat pump's heating and cooling modes, for instance.

The third form — total_wh with a deadline — is what an External energy budget takes. It is the one pushed shape that is scheduled rather than injected, so it gets a released / blocked signal like a heated store.

Extra load, or the whole appliance?

This is the one thing worth getting right, and it is invisible from the sending end. What you send is added to the household load forecast, slot by slot. The household forecast is built from your own measured history, which already contains whatever the appliance was doing over the last fortnight.

  • Sending only the extra — the difference cooling makes over heating, say — is the ordinary case. Leave Replaces sensor empty.
  • Sending the appliance's whole consumption means its history has to come out of the household forecast first, or it is counted twice. Set Replaces sensor to the meter measuring it, in watts.

Setting Replaces sensor while sending only the extra bit is the mistake to watch for: the whole measured consumption leaves the base load and only the difference comes back, so the forecast ends up far too low.

Watching it work

On the dashboard

Configured managed loads get their own tile, the same size as the four it shares the row with, showing one line per load with its state and, in the header, the energy planned across all of them. It is a summary by design. Click the header figure, or pick Menu › Managed Loads, for the full picture: why each load is in the state it is, what it still needs, when it will next run, how far the calibration has settled, and which slots of the next two days it is planned for — with the time of day along the axis, and the exact window, energy and running total on hovering a bar.

Two things on that card are worth reading carefully. Energy needed is split into what it takes to reach the target and what it takes to hold it for the rest of the horizon — for a pool the second part dominates, which is why a one-degree rise can read as tens of kWh. And the outside temperature names not just the air but where it came from: the forecast corrected to your site, your own sensor held flat, or a fixed guess. A prediction standing on a guess looks exactly like one standing on a forecast, so it says which.

Under the planned slots, Temperatures draws the two curves the plan is built on, against the same clock. The upper one is the store itself — solid where it was measured, dashed where the plan takes it, with the target marked; that pair is the answer to “why does it want so much energy?”, because the climb and the standing loss are one number in the demand and two quite different shapes here. The lower one is the air: solid for the figure the model uses, dashed for the forecast as it arrived, so the gap between them is what the site has taught it. Hovering either gives the readings and, on the air, the difference between them. They are two plots rather than one because a pool moves four kelvin where the air outside moves twenty-three.

How this was worked out at the foot of the card folds away the workings — efficiency, power, where the forecast came from, and the calibration with its Reset. An override, an error, or an ambient figure that is a fixed guess never folds: those change what you should do.

What it publishes

  • MQTT, per load, discovered by Home Assistant automatically: managed_load/<id>/released (a binary sensor), /state, /reason, /next_release_start, /energy_needed_wh, /temperature, /target_temperature and /calibration_confidence.
  • GET /api/managed_loads for the full picture: the plan slot by slot, the release state and the calibration.
  • POST /api/managed_loads/<id>/override with {"mode": "release" | "block" | "clear", "minutes": 60}, or publish the same to eos_connect/managed_load/<id>/override/set. An override wins over everything, frost protection included.

Why did it run then?

A released/blocked signal tells you that something changed and never which plan said so — and by the time you look, that plan has been replaced several times over. So every release decision is written to the database as it is made, together with the plan behind it:

GET /api/managed_loads/<id>/decisions?hours=24

{
  "count": 712,
  "distinct_plans": 34,        — how many different plans it held
  "release_changes": 9,        — how often the appliance was switched
  "decisions": [
    {"timestamp": "...", "origin": "optimizer", "released": true,
     "reason": "planned cheap slot", "current_slot": 77, "planned_now": true,
     "plan_hash": "4fa96012", "planned_slots": [150, 151, 160, 161, 162],
     "planned_wh": 2000.0, "demand_wh": 35733.5}
  ]
}

The two summary numbers are usually the whole answer. A high distinct_plans against a low release_changes means the optimizer keeps changing its mind but the minimum runtime is absorbing it. Both high means that churn is reaching the appliance.

origin separates the two things that look identical from the appliance: optimizer is a schedule the solver produced, self is EOS Connect falling back on its own plan because the solver went quiet. planned_now says whether the slot that was current at that moment actually carried energy — a release with planned_now: false is being held by the minimum runtime or an override, not by the plan.

Kept for seven days and purged on start. It is written for reading after the fact; nothing uses it to run the house.