Skip to content

The control screen

Monika edited this page Aug 21, 2026 · 3 revisions

The control screen

One unit per screen — swipe left/right to switch. Each unit fills the screen, no scrolling, tinted to the active mode.

  • Temperature — a big readout with a stepped slider (0.5° steps) and − / + buttons on each side for single steps.
  • Indoor / outdoorI: 27.0 °C / O: 33.0 °C when the unit has an outdoor probe, above a bar showing how the three temperatures relate. See the temperature bar below.
  • Mode — a colourful segmented picker: auto · cool · dry · heat · fan.
  • Fan — a Low→High slider that detaches to Auto.
  • Flap — one big pill split in two, vertical and horizontal, each toggled on its own: neither, either, or both.
  • Eco / Turbo — large, colourful switches.
  • Power — a big switch in the header: faintly red when off, faintly green when on, so the unit's state is never in doubt.
  • The hourglass, just left of the power switch — a sleep timer.

How it stays in sync

Every change is optimistic with haptic feedback: it reflects instantly, then reconciles with the server. The screen never flickers while refreshing — state merges in place, and a small indicator appears only while you have triggered a command. That indicator sits next to the status dot, in a slot reserved whether or not it is visible: it used to sit where the hourglass now is, and appearing and vanishing mid-command shifted the unit's name sideways.

On Breeze Core ≥ 3.0.0 the app receives live updates over SSE, so the server pushes changes — including ones made by a schedule or another client — and the phone stops polling. Against an older server, or if the stream drops, it falls back to a 5-second poll and retries. An offline banner appears with backed-off polling when the server is unreachable.

The app reopens on the unit you last viewed, remembered by unit rather than by position, so it survives adding, removing or reordering units.

Haptics

Feedback is named by meaning rather than intensity, so it stays consistent as things get added:

when
tick a notch moved — temperature ±, a slider detent, swiping to another unit
select a setting chosen — mode, fan, a flap half, eco/turbo
toggle power on/off, deliberately the heaviest: the one action with a physical result you might be across the room from
success / failure pairing accepted, or a rejected key — failure is two beats, so it is distinguishable without looking

The temperature slider ticks only when the value crosses a notch, not on every pixel of the drag.

The sleep timer

The hourglass beside the power switch: tap it, pick how long the unit should stay on, and the server switches it off. It keeps its promise with the phone asleep, flat, or in another country — the app only places the order.

Presets are 15, 30, 45, 60, 90 and 120 minutes, plus a custom box (1 minute to 24 hours). While one is pending the hourglass fills in and shows the time left ("42m"), and tapping it again offers to change or cancel.

Two details that are deliberate rather than incidental:

  • It asks the server for minutes, never for a time of day, and counts down from the seconds_remaining the server reports. Your phone's clock is used only to measure elapsed time, never to decide what time it is. A phone in a different timezone from its server, or with a clock that is simply wrong, still gets the right result — and this app has been bitten by trusting the phone's clock before (see Security).
  • Setting a timer replaces that unit's existing one. Asking for "off in 30" when one is already running means you changed your mind, not that you want two competing promises about the same unit.

The hourglass only appears when the server supports it (Breeze Core 3.2.0 or newer, advertising the sleep_timer feature), rather than appearing everywhere and failing. Server-side behaviour — what happens if the unit is unreachable, or the server was rebooting when the timer came due — is in Timers.

While it loads

Whenever the app is waiting on the server — at launch, or connecting to a unit — it shows a cat fact. They are bundled, not fetched: an app whose whole point is that nothing leaves your network would look absurd calling a cat-fact API, and it would fail on a LAN with no route out anyway.

The temperature bar

Your units report an outdoor temperature and the app used to throw it away. The bar could be described as three separate behaviours, which is how such things drift apart, so it is implemented as one rule:

The warmest of indoor / outdoor / target sets the top of the scale. The other two are drawn from the floor as overlapping fills — the warmer of them lighter, the cooler darker on top.

  • Cooling (outdoor 33, indoor 27, target 24) — outdoor is the ceiling, indoor the lighter fill, target the darkest.
  • Heating (outdoor 5, indoor 20, target 23) — the target becomes the ceiling and the outdoor reading the darkest fill.
  • Indoor below target — the shades swap by themselves, because warmer is always lighter. No special case.

A temperature sensor has many ways to lie, and each of these is covered by a test:

  • A unit without an outdoor probe may report a sentinel rather than null, so anything outside −50…80 °C is treated as missing. A bar scaled to 255 °C renders as an empty sliver with no clue why.
  • NaN and infinities are dropped the same way.
  • Below-zero outdoors extend the scale floor beneath 0 °C rather than clamping the reading to nothing, which would look like a dead sensor every winter.
  • Fewer than two real readings is not a relationship: the numbers still show, the bar does not.
  • Two readings draw a single full-strength fill; the pale shade only makes sense with something darker on it.
  • Identical readings still get a 1 °C span, so nothing divides by zero.
  • Genuine extremes are kept — 45 °C on a sun-baked outdoor unit and −30 °C in a real winter are weather, not faults.

Geometry is computed in Celsius whatever the display unit, so °F users see identical proportions.

Clone this wiki locally