bridge nel repo: sorgenti, unit systemd e istruzioni per i tester
- bridge/ con aira_bridge.py, setup_creds.py, aira_probe.py, test offline - percorsi di configurazione via AIRA_CONFIG_DIR (default invariato) - README del bridge in inglese: installazione, servizio, firewall, sicurezza - README principale aggiornato (il bridge non è più 'fuori dal repo')
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
# Aira bridge — let the Sailfish app talk to your heat pump
|
||||
|
||||
The app cannot reach Aira directly: Aira's cloud API is **gRPC over HTTP/2**, and
|
||||
Sailfish's Qt 5.6 speaks HTTP/1.1 only. This small service sits on a Linux box in
|
||||
your LAN, answers the phone in plain HTTP/1.1 + JSON and speaks gRPC to Aira.
|
||||
A side effect worth having: your Aira credentials and session tokens never leave
|
||||
this machine, so nothing sensitive is stored on the phone.
|
||||
|
||||
Tested against an Aira Home heat pump (Europe). Python 3.9+.
|
||||
|
||||
## What you need
|
||||
|
||||
- a machine that stays on, on the same LAN as the phone (a Raspberry Pi is plenty)
|
||||
- an Aira account — the same one you use in the Aira app
|
||||
- outbound internet access to the Aira cloud
|
||||
- **no** port forwarding, no public exposure: this is a LAN service
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
git clone https://git.hackatoniclife.com/kaneda/harbour-aira.git
|
||||
cd harbour-aira/bridge
|
||||
|
||||
python3 -m venv .venv
|
||||
. .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
# where credentials and bridge configuration will live
|
||||
export AIRA_CONFIG_DIR="$HOME/.config/aira"
|
||||
mkdir -p "$AIRA_CONFIG_DIR"
|
||||
|
||||
# your Aira email + password: typed without echo, saved in a file readable
|
||||
# only by your user (600). Nothing is printed, nothing is logged.
|
||||
python setup_creds.py
|
||||
|
||||
# sanity check: logs in and prints your plant state (read-only)
|
||||
python aira_probe.py
|
||||
```
|
||||
|
||||
If `aira_probe.py` prints your temperatures and modes, the hard part is done.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
python aira_bridge.py --host 0.0.0.0 --port 8790
|
||||
```
|
||||
|
||||
On first start the bridge creates `$AIRA_CONFIG_DIR/aira_bridge.json` with a
|
||||
random token (600). To read it:
|
||||
|
||||
```bash
|
||||
python -c "import json,pathlib,os; print(json.load(open(pathlib.Path(os.environ['AIRA_CONFIG_DIR'])/'aira_bridge.json'))['token'])"
|
||||
```
|
||||
|
||||
Then in the app: pull the menu down, *Impostazioni*, and enter
|
||||
`http://<LAN-IP-of-this-machine>:8790` plus that token.
|
||||
|
||||
## Run as a service (recommended)
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/systemd/user
|
||||
cp harbour-aira-bridge.service ~/.config/systemd/user/
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now harbour-aira-bridge.service
|
||||
loginctl enable-linger "$USER" # keeps it up after logout and at boot
|
||||
journalctl --user -u harbour-aira-bridge.service -f
|
||||
```
|
||||
|
||||
The unit uses `%h` and expects the repo in `~/harbour-aira`:
|
||||
edit `WorkingDirectory` / `ExecStart` if you cloned it elsewhere, and make sure
|
||||
`Environment=AIRA_CONFIG_DIR=...` matches what you used with `setup_creds.py`.
|
||||
Verified resilience: killing the process brings it back in about ten seconds.
|
||||
|
||||
## Opening the port (LAN only)
|
||||
|
||||
The bridge listens on 8790 and checks a bearer token on every route except
|
||||
`/health`, but it is meant for a trusted network. Do **not** forward this port
|
||||
from the internet.
|
||||
|
||||
```bash
|
||||
# ufw
|
||||
sudo ufw allow from 192.168.1.0/24 to any port 8790 proto tcp
|
||||
```
|
||||
|
||||
```nftables
|
||||
# nftables: inside your input chain
|
||||
ip saddr 192.168.1.0/24 tcp dport 8790 accept
|
||||
```
|
||||
|
||||
On a host with a default-drop firewall the bridge looks dead from the phone even
|
||||
though it is running: check the firewall before debugging anything else, and
|
||||
test from the phone, not from the bridge machine itself (loopback traffic does
|
||||
not traverse the input chain, so it always works).
|
||||
|
||||
## Security
|
||||
|
||||
- Credentials (`aira.json`) and Cognito session tokens (`aira_tokens.json`) live
|
||||
in `$AIRA_CONFIG_DIR` with mode 600, on this machine only.
|
||||
- `aira_bridge.json` holds the bearer token, which is the only credential stored
|
||||
on the phone. To rotate it: stop the bridge, delete the file, start it again,
|
||||
and paste the new token in the app.
|
||||
- Traffic goes to Aira's own API and nowhere else. Nothing is collected, nothing
|
||||
is sent to the author.
|
||||
- Only a whitelist of commands is exposed (see below). Factory reset, firmware
|
||||
updates, reboots and Wi-Fi provisioning are deliberately not reachable.
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Route | Auth | Purpose |
|
||||
|---|---|---|
|
||||
| `GET /health` | none | `{ok, version}` — liveness, used above all for debugging |
|
||||
| `GET /summary` | token | dashboard values (house/DHW temperatures, targets, modes, errors) |
|
||||
| `GET /state` | token | full raw plant state, exactly as the cloud reports it |
|
||||
| `GET /commands` | token | allowed commands with their parameters and ranges |
|
||||
| `POST /command` | token | `{"id": "...", "value": 21.5, "zone": 1}` |
|
||||
|
||||
Commands exposed: hot water target and boost, heating/cooling setpoint per zone,
|
||||
away mode, night mode for one hour, force heating, heating function, legionella
|
||||
cycle, plant check. Protocol details and field names: `../docs/PROTOCOL.md`.
|
||||
|
||||
Two things learned on a real pump, both worth knowing:
|
||||
|
||||
- **A zone command always carries every zone.** `SetZoneSetpoints` takes the full
|
||||
set of zones, so sending only the zone you want to change would zero the other
|
||||
one. The bridge remembers the last states it read and sends the untouched zone
|
||||
back with its current value.
|
||||
- **`force_heating` is not a boolean** in the state but an object
|
||||
`{enabled, remaining_time}`. Reading it as a truthy value makes "forced heating:
|
||||
yes" appear while the pump is idle. Same shape for `hot_water.heating_enabled`.
|
||||
|
||||
## Offline tests
|
||||
|
||||
```bash
|
||||
python test_bridge_commands.py
|
||||
```
|
||||
|
||||
27 checks on the protobuf payloads and on the summary parsing — no network, and
|
||||
**no command is sent to the pump**.
|
||||
|
||||
## If login fails
|
||||
|
||||
Your account may live in a different Cognito user pool: a wrong pool answers
|
||||
"credentials not valid" even with the right password. Try:
|
||||
|
||||
```bash
|
||||
python aira_probe.py --pool 0 # then 1, then 2
|
||||
```
|
||||
|
||||
or write `{"user_pool_index": N}` into `$AIRA_CONFIG_DIR/aira_config.json`.
|
||||
Try one pool at a time: repeated failures trigger a temporary lockout.
|
||||
|
||||
## Files
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `aira_bridge.py` | the service: HTTP/1.1 + JSON in, gRPC to Aira out |
|
||||
| `setup_creds.py` | asks for the Aira credentials and saves them (600) |
|
||||
| `aira_probe.py` | read-only check that login and the cloud channel work |
|
||||
| `test_bridge_commands.py` | offline tests of the command payloads |
|
||||
| `harbour-aira-bridge.service` | systemd user unit |
|
||||
| `requirements.txt` | `pyairahome`, pinned to the tested version |
|
||||
|
||||
## Reporting your pump
|
||||
|
||||
If you are testing this on your own unit, the useful things to send me are: pump
|
||||
model and firmware versions, number of heating zones (1 or 2), whether cooling is
|
||||
configured, the output of `python aira_probe.py --json`, and anything that looks
|
||||
wrong next to what the Aira app shows at that moment. The probe output contains
|
||||
no credentials.
|
||||
Reference in New Issue
Block a user