# harbour-aira Native Sailfish OS app to monitor and control an **Aira Home** heat pump: plant status, domestic hot water, and the house target temperature per zone. *Versione italiana: [README.it.md](README.it.md)* ![architecture](docs/architettura.svg) ## What it does - **Home**, one screen to look at: a coloured dot with a one-line status ("Everything is fine", "Heating the hot water"), the time of the last update, the two readings that matter (house and domestic hot water, each with its target) side by side and **tappable**, and three quick actions: boost, away mode and the plant commands. It fits without scrolling — the controls live in their own pages - **Setpoints** page: house target and hot water target, each with the value the plant is keeping right now, a slider (the proposal, the plant value does not move), a row of quick values for cold fingers and a confirmation button that only lights up when there is something to change - **Data** page: heat produced, electricity used, efficiency (COP) for hot water and heating, savings on the bill, CO₂ avoided and the Smart Tariff Control saving, month by month. These are the numbers Aira computes in the cloud, exactly as in the official app's *Data* tab - **Plant** page: status (connection, mode, away/night/forced heating, manual mode, defrost, signature lights, pump state, allowed modes, outdoor and indoor temperature, zones, last reading), accessories (the wall thermostat with temperature, humidity, signal, battery), software versions, the error count and the plant check. The extended status sentence ("Relax: the heat pump is running smoothly") lives here - **Plant commands** page: four switches (away mode, forced heating, heating, hot water active) that show the state of the plant and change it in one tap, plus the buttons for what has no state: legionella cycle and night mode for one hour. The switches follow the plant — they never anticipate it - **Cover page** showing the house temperature - Address and bridge token live in the app settings (QSettings), never in the binary - **Languages: English and Italian.** The interface follows the phone language by default and falls back to English when there is no catalogue for it. The language can also be picked in the settings; the app reloads to apply it, and numbers follow the chosen language too (decimal comma in Italian, dot in English) Layout previews (rendered with the real values the plant returned, **not** screenshots — see *Status*): | Home | Setpoints | Data | |---|---|---| | ![home](docs/anteprima/dashboard.png) | ![setpoints](docs/anteprima/regolazioni.png) | ![data](docs/anteprima/dati.png) | | Plant | Plant commands | Settings | Cover | |---|---|---|---| | ![plant](docs/anteprima/impianto.png) | ![commands](docs/anteprima/comandi.png) | ![settings](docs/anteprima/impostazioni.png) | ![cover](docs/anteprima/copertina.png) | ## Why there is a bridge Aira's cloud API is **gRPC over HTTP/2**. Qt 5.6 — the toolkit every Sailfish app runs on — **has no HTTP/2**, so a native client cannot talk to it directly. The alternatives (packaging `grpcio` for aarch64 with PyOtherSide, or rewriting Cognito + gRPC in C++) are fragile and expensive. So the logic stays **on a server**, where Python and the tokens already work, and the app is a thin client speaking HTTP/1.1 + JSON. A side effect worth having: credentials and session tokens never leave that machine. ``` Jolla (QML/C++ Qt 5.6) --HTTP/1.1 + JSON--> bridge (Linux, LAN) --gRPC--> cloud Aira bridge/aira_bridge.py ``` ## Requirements - a Sailfish OS phone - an Aira Home heat pump, with an account in the Aira app - the bridge running on a machine that stays on, in the same LAN (a Raspberry Pi is plenty) ## The bridge Sources, systemd user unit and full instructions: [`bridge/README.md`](bridge/README.md). Short version: ```sh cd bridge python3 -m venv .venv && . .venv/bin/activate pip install -r requirements.txt export AIRA_CONFIG_DIR="$HOME/.config/aira" python setup_creds.py # Aira email + password, saved in a 600 file python aira_bridge.py --port 8790 ``` The first start writes a random token to `$AIRA_CONFIG_DIR/aira_bridge.json` (mode 600): that value goes in the app settings, together with the LAN address of the machine. Full API: [docs/PROTOCOL.md](docs/PROTOCOL.md). ## App settings Address and token are configured in the **Settings** page (QSettings in `~/.config/harbour/aira/aira.conf`): no personal data in the binary. ## Build Requires the Sailfish SDK (`sfdk`) and an aarch64 target (Jolla Phone 2026): ```sh sfdk target install SailfishOS-5.0.0.62-aarch64 sfdk target use SailfishOS-5.0.0.62-aarch64 sfdk build # RPM in RPMS/ sfdk deploy # to the connected device ``` From a source tarball (`harbour-aira-.tar.gz`, if you were handed one): extract it into a **new** directory (never on top of a previous extraction: the Makefiles stay stale), or simply clone this repository. The app log starts with `harbour-aira v build `, which tells a fresh build from a stale one without guessing. ### Local checks, no SDK needed ```sh cd tests && mkdir -p build && cd build ~/Qt/5.15.2/gcc_64/bin/qmake ../core_test.pro && make && ./core_test ``` Covers the pure logic (`src/airajson.cpp`): address normalisation, summary and statistics parsing, mode labels, command payloads, error messages, and the number/currency/month formatting used by the Data page. **The Silica UI can only be verified on a device.** A second check compares the keys the QML reads with the keys the bridge really returns (26 from `/summary`, 15 from `/stats`). It needs the bridge running, and it exists because the UI cannot be run here: a typo in a key would show up as a permanent “—” on the phone, with nothing in the log. ```sh python3 tests/ui_contract_check.py # bridge on 127.0.0.1:8790 python3 tests/ui_contract_check.py http://192.168.3.21:8790 ``` ## Status Verified so far: - **79/79** core checks (`tests/core_test.cpp`), compiled with no warnings — including the one-line and extended status sentences, so what the home says about the plant is verified without a phone - **48/48** bridge checks, offline (`bridge/test_bridge_commands.py`) — including that a zone command always carries every zone, that absurd values are refused (40 °C, zone 3, text), and the Wh→kWh / money conversions of the statistics - **191/191** strings translated in the Italian catalogue, checked by loading the compiled `.qm` in the test the same way `main.cpp` does: source English in, Italian out, and back to English when the catalogue is removed - numbers, amounts and month names formatted through `QLocale`, so the decimal separator and the currency position follow the language (verified for both) - `qmllint` clean on every QML file. Note it validates syntax only: without the Silica module an invalid Silica property passes silently - **no duplicate property assignment** in the QML (`tests/qml_check_bindings.py`, 13 files): assigning the same property twice in one object — two `Component.onCompleted` in a page, typically — makes Qt 5.6 fail to create the object, and all you see on the device is `Type MainPage unavailable` with no file named. The check runs in milliseconds and names the line - read-only queries against one real Aira Home unit, through the bridge — including the cloud statistics, which match what the official app shows for the same period (heat produced 70.8 kWh, electricity 29.3 kWh, hot-water COP 4.42, Smart Tariff Control saving €2.71) - the bridge runs as a systemd user unit and comes back by itself after a kill Not verified: **the app has not been built or run on a phone yet** — the first build is up to whoever has the Sailfish SDK. The images in `docs/anteprima/` are layout previews built from the real plant values, not screenshots. A Qt 5.6 note on the language switch: `QQmlEngine::retranslate()` only exists from Qt 5.10, so installing a translator is not enough to re-translate what is already on screen. The app therefore reloads the QML root when the language changes: the setting applies immediately, at the cost of landing back on the home page. Caveats: - **Unofficial client**, not in the Jolla Store: it talks to Aira's API with your own account. Terms of service and trademarks remain Aira's business — use it at your own risk, on your own pump - tested against **one** unit so far (firmware: outdoor unit 1.30.0, system 6.10.0, climate control 3.9.12); other units and other countries may expose different fields - cooling is reachable from the bridge but has no UI yet; schedules/curves are not implemented, and firmware versions are shown read-only - the Data page depends on Aira's own statistics service: on a very new installation some months have no data yet (the app then writes *no data* instead of a number) - only a whitelist of commands is exposed: factory reset, firmware updates, reboots and Wi-Fi provisioning are deliberately unreachable - no command is ever sent to the plant by the tests ## Looking for testers If you own an Aira heat pump and a Sailfish phone, and you are willing to run the bridge on a machine at home, I would like to hear how it behaves on your unit: - pump model and firmware versions (visible in the Aira app) - how many heating zones you have (1 or 2), and whether cooling is configured - the output of `python aira_probe.py --json` (it contains no credentials) - anything that looks wrong next to what the Aira app shows at the same moment - your country, since Aira may not expose the same fields in every market ## Layout ``` harbour-aira.pro qmake project (icons, qml, docs) src/airajson.{h,cpp} pure logic, testable without the SDK src/apiclient.{h,cpp} QNetworkAccessManager + timeout watchdog src/settings.{h,cpp} QSettings in AppConfigLocation (SailJail sandbox) src/translator.{h,cpp} UI language: system locale, English fallback, reload src/main.cpp context properties: appSettings, api, translator qml/harbour-aira.qml ApplicationWindow + cover qml/pages/MainPage.qml home: one-line status, two readings, quick actions qml/pages/SetpointsPage.qml house and water targets, quick values, boost qml/pages/DataPage.qml energy, efficiency and savings qml/pages/PlantPage.qml status, accessories, versions, errors, diagnostics qml/pages/CommandsPage.qml the plant commands, state shown by switches qml/pages/SettingsPage.qml qml/components/InfoRow.qml label on the left, value on the right qml/components/ValueRow.qml title + subtitle left, big value right qml/components/ReadingCell.qml tappable reading: title, big value, charge bar qml/components/NoticeBar.qml command messages, anchored to the bottom edge qml/components/ActionButton.qml command button, optional confirmation tests/core_test.cpp core checks (headless) tests/ui_contract_check.py QML keys vs bridge keys (bridge must be running) tests/qml_check_bindings.py duplicate property assignments in the QML bridge/ local HTTP bridge -> see bridge/README.md translations/ harbour-aira-it.ts (English is the source language) docs/PIANO.md project plan and decisions (Italian) docs/PROTOCOL.md bridge API docs/UX-STUDIO.md UX review and what changed in 0.6.0 (Italian) docs/ux/ UX study wireframes docs/anteprima/ layout previews (English; docs/anteprima/it/ Italian) ``` ## License MIT.