244 lines
11 KiB
Markdown
244 lines
11 KiB
Markdown
# 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)*
|
|
|
|

|
|
|
|
## 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 |
|
|
|---|---|---|
|
|
|  |  |  |
|
|
|
|
| Plant | Plant commands | Settings | Cover |
|
|
|---|---|---|---|
|
|
|  |  |  |  |
|
|
|
|
## 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-<version>.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<version> build <date>`, 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.
|