Skip to content

Commit f2fd639

Browse files
committed
Initial commit
0 parents  commit f2fd639

6 files changed

Lines changed: 7341 additions & 0 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
deploy:
19+
environment:
20+
name: github-pages
21+
url: ${{ steps.deployment.outputs.page_url }}
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
27+
- name: Setup Pages
28+
uses: actions/configure-pages@v5
29+
30+
- name: Upload artifact
31+
uses: actions/upload-pages-artifact@v3
32+
with:
33+
path: .
34+
35+
- name: Deploy to GitHub Pages
36+
id: deployment
37+
uses: actions/deploy-pages@v4

README.md

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
# Loxone API Documentation
2+
3+
Reverse-engineered API documentation extracted from the Loxone app bundle (`ftp://<loxone-miniserver>:21/web/commonv2.agz`) and the
4+
[Lox AudioServer](https://github.com/lox-audioserver/lox-audioserver) TypeScript source (`src/`) which is a community project — not an
5+
official Loxone document.
6+
7+
**⚠️ Not an official Loxone document ⚠️** Use at your own risk.
8+
9+
---
10+
11+
## There are four distinct APIs
12+
13+
```
14+
┌─────────────────────────────────────────────────────────────────────────────┐
15+
│ │
16+
│ Loxone App ──── (1) ────► Loxone MiniServer │
17+
│ │ │ │
18+
│ │ (2) │ (3) │
19+
│ ▼ ▼ │
20+
│ Loxone AudioServer ◄──── (3) ──┘ │
21+
│ │ │
22+
│ │ (4) │
23+
│ ▼ │
24+
│ Lox AudioServer Admin UI (browser) │
25+
│ │
26+
└─────────────────────────────────────────────────────────────────────────────┘
27+
```
28+
29+
| # | Link | Protocol | Spec file |
30+
| --- | ---------------------------------------------- | ------------------------------------------- | ------------------------------------ |
31+
| 1 | **Loxone App ↔ Loxone MiniServer** | WebSocket `jdev/sps/io/…` | `miniserver-api.yaml` |
32+
| 2 | **Loxone App ↔ Loxone AudioServer** | HTTP GET + WebSocket `audio/…` | `openapi.yaml` (App subset) |
33+
| 3 | **Loxone MiniServer ↔ Loxone AudioServer** | HTTP GET + WebSocket `audio/…` + `secure/…` | `openapi.yaml` (full, superset of 2) |
34+
| 4 | **Lox AudioServer Admin UI ↔ Lox AudioServer** | REST `/admin/api/…` with cookie auth | `audioserver-admin-api.yaml` |
35+
36+
**Key insight for APIs 2 and 3:** The App and the MiniServer use the **same `audio/…` command protocol**, but connect on **different ports**
37+
and the MiniServer additionally sends a set of config-sync and pairing commands that the App never sends.
38+
39+
---
40+
41+
## API 1: Loxone App ↔ Loxone MiniServer (`miniserver-api.yaml`)
42+
43+
The Loxone App controls smart home devices by sending commands to the **Loxone MiniServer** over WebSocket or plain HTTP GET.
44+
45+
**WebSocket endpoint:** `ws://{miniserverHost}/ws/rfc6455` **HTTP mirror:** `GET http://{miniserverHost}/{command}`
46+
47+
### Authentication flow
48+
49+
```
50+
1. GET /jdev/cfg/api → discover firmware version & key algorithm
51+
2. GET /jdev/cfg/getPublicKey → fetch RSA-2048 public key
52+
3. GET /jdev/sys/gettoken/{hash}/{user}/{permission}/{clientId}/{clientName}
53+
→ obtain auth token (hash = HMAC-SHA1 of credentials)
54+
4. GET /jdev/sys/authwithtoken/{token}/{user}
55+
→ re-authenticate on reconnect
56+
5. GET /jdev/sys/refreshtoken/{token}/{user}
57+
→ extend token before expiry
58+
6. GET /jdev/sys/killtoken/{token}/{user}
59+
→ invalidate token on logout
60+
```
61+
62+
### Control command format
63+
64+
```
65+
jdev/sps/io/{uuid}/{command}
66+
jdev/sps/io/{uuid}/{command}/{value}
67+
```
68+
69+
`{uuid}` is the control's UUID from the structure file (`GET /data/LoxAPP3.json`).
70+
71+
### State update push (WebSocket)
72+
73+
The MiniServer pushes state changes as text frames:
74+
75+
```
76+
{stateUuid}:{value}
77+
```
78+
79+
State UUIDs are defined in `control.states` inside the structure file.
80+
81+
### Selected control types and commands
82+
83+
| Control type | Commands |
84+
| ----------------------- | -------------------------------------------------------------------------- |
85+
| `Jalousie` (blinds) | Up, Down, Stop, FullUp, FullDown, Auto, shade/{pos} |
86+
| `LightControllerV2` | On, Off, plus, minus, setMoodToId/{id}, addMood, removeMood, learn |
87+
| `Dimmer` | On, Off, setBrightness/{0-100} |
88+
| `ColorPickerV2` | color/{hsv\|lxt}, setFavoriteColor/{index}, setSaturation/{val} |
89+
| `IRoomControllerV2` | setOperatingMode/{0-5}, setComfortTemperature/{°C}, override/{mode}/{secs} |
90+
| `Switch` / `Pushbutton` | On, Off, pulse, pulse/{durationMs} |
91+
| `Alarm` | arm, disarm/{code}, delayedOn/{secs}, acknowledge |
92+
| `Gate` / `GarageDoor` | open, close, stop |
93+
| `DoorLock` | lock, unlock |
94+
| `Counter` | reset, set/{value} |
95+
96+
---
97+
98+
## APIs 2 & 3: Loxone App ↔ Loxone AudioServer and Loxone MiniServer ↔ Loxone AudioServer (`openapi.yaml`)
99+
100+
The AudioServer runs **two HTTP + WebSocket servers** that share identical route handling. The App connects to one port; the MiniServer
101+
connects to the other.
102+
103+
| Server | Env var | Default port | Client |
104+
| --------- | ------------------------ | ------------ | ----------------- |
105+
| `appHttp` | `LOXONE_APP_PORT` | **7091** | Loxone App |
106+
| `msHttp` | `LOXONE_MINISERVER_PORT` | **7090** | Loxone MiniServer |
107+
108+
The WebSocket **identification string** sent on connect differs:
109+
110+
```
111+
App: "LWSS V 16.1.10.01 | ~API:1.6~ | Session-Token: …"
112+
MiniServer: "MINISERVER V LWSS V 16.1.10.01 {mac} | ~API:1.6~ | Session-Token: …"
113+
```
114+
115+
When the **MiniServer** WebSocket connects, the AudioServer also emits a **server heartbeat** with current zone state — the App does not
116+
trigger this.
117+
118+
### Shared commands (App + MiniServer)
119+
120+
Both clients use these. All are HTTP GET; parameters are slash-separated.
121+
122+
```
123+
# Zone control
124+
GET /audio/{zoneId}/play
125+
GET /audio/{zoneId}/pause
126+
GET /audio/{zoneId}/volume/{0-100}
127+
GET /audio/{zoneId}/shuffle/{enable|disable}
128+
GET /audio/{zoneId}/repeat/{0|1|3}
129+
GET /audio/{zoneId}/queueplus
130+
GET /audio/{zoneId}/queueminus
131+
GET /audio/{zoneId}/position/{seconds}
132+
GET /audio/{zoneId}/status
133+
GET /audio/{zoneId}/getqueue/{start}/{limit}
134+
GET /audio/{zoneId}/serviceplay/{service}/{user}/{audiopath}
135+
GET /audio/{zoneId}/roomfav/play/{slot}
136+
137+
# Config / management
138+
GET /audio/cfg/getservices
139+
GET /audio/cfg/getplaylists2
140+
GET /audio/cfg/getroomfavs/{playerId}
141+
GET /audio/cfg/search/{service}/{user}/{tag}/{query}/{limit}/{offset}
142+
GET /audio/cfg/globalsearch
143+
GET /audio/cfg/spotifyconnect/enable|disable
144+
GET /audio/cfg/airplay/enable|disable
145+
GET /audio/cfg/tunein/enable|disable
146+
… (95+ shared endpoints total — see openapi.yaml)
147+
```
148+
149+
### MiniServer-only commands
150+
151+
These are sent **only by the MiniServer** on its dedicated port — the App never sends them.
152+
153+
#### Pairing & Auth (`secure/…`)
154+
155+
Sent immediately after WebSocket connect as a bootstrap handshake:
156+
157+
| Command | Purpose |
158+
| -------------------------- | --------------------------------- |
159+
| `secure/info/pairing` | Query paired MiniServer serial |
160+
| `secure/hello/{publicKey}` | Key-exchange bootstrap |
161+
| `secure/authenticate` | Auth acknowledgement |
162+
| `secure/init` | Session init (returns legacy JWT) |
163+
164+
#### Config Sync (`audio/cfg/…`)
165+
166+
Sent by the MiniServer to push or pull configuration state:
167+
168+
| Command | Purpose |
169+
| -------------------------------------- | ----------------------------------------------------------- |
170+
| `audio/cfg/ready` | MiniServer signals startup complete; gets session timestamp |
171+
| `audio/cfg/getconfig` | Fetch config CRC-32 to detect stale cache |
172+
| `audio/cfg/setconfig` | Push zone UUIDs and names to AudioServer |
173+
| `audio/cfg/setconfigtimestamp` | Sync config version timestamp |
174+
| `audio/cfg/miniservertime` | Sync wall-clock time |
175+
| `audio/cfg/volumes` | Bulk-set initial zone volumes |
176+
| `audio/cfg/playername` | Set zone display name |
177+
| `audio/cfg/identify` | Send MiniServer serial + firmware version |
178+
| `audio/cfg/defaultvolume/{zone}/{vol}` | Set startup volume |
179+
| `audio/cfg/maxvolume/{zone}/{vol}` | Set volume cap |
180+
| `audio/cfg/eventvolumes/{zone}/{vol}` | Set alert volume |
181+
| `audio/cfg/miniserverip` | _(placeholder — ignored)_ |
182+
| `audio/cfg/miniserverversion` | _(placeholder — ignored)_ |
183+
| `audio/cfg/timezone` | _(placeholder — ignored)_ |
184+
| `audio/cfg/presencemode` | _(placeholder — ignored)_ |
185+
| `audio/cfg/groupopts` | _(placeholder — ignored)_ |
186+
| `audio/cfg/speakertype` | _(placeholder — ignored)_ |
187+
| `audio/cfg/playeropts` | _(placeholder — ignored)_ |
188+
189+
### Response format (both clients)
190+
191+
```json
192+
{ "{command}_result": <payload>, "command": "<original-path>" }
193+
```
194+
195+
### WebSocket events (server → all connected clients)
196+
197+
| Event key | Trigger |
198+
| ----------------------------- | ----------------------- |
199+
| `audio_event` | Zone state changed |
200+
| `audio_queue_event` | Queue contents changed |
201+
| `roomfavchanged_event` | Room favorites changed |
202+
| `recentlyplayedchanged_event` | Recently played changed |
203+
| `rescan_event` | Library scan progress |
204+
| `globalsearch_result` | Search result ready |
205+
| `audio_sync_event` | Sync group changed |
206+
| `lineinchanged_event` | Line-in inputs changed |
207+
208+
---
209+
210+
## API 4: Lox AudioServer Admin UI ↔ Loxone AudioServer (`audioserver-admin-api.yaml`)
211+
212+
Internal REST API used by the AudioServer's browser-based Admin UI.
213+
214+
**Base URL:** `http://{host}:7091` **Auth:** Cookie session via `POST /admin/api/auth/login` (TTL 12 h)
215+
216+
| Group | Base path | Notes |
217+
| ------------------- | ----------------------------------------------------------- | -------------------------------------------------------------- |
218+
| Auth | `/admin/api/auth/…` | login, logout, me |
219+
| Server info | `/admin/api/info` | public, no auth |
220+
| Zone admin states | `/admin/api/zones/…` | tech details, bulk purge |
221+
| Zone groups | `/admin/api/groups` | group listing |
222+
| Transport discovery | `/admin/api/transports/…` | AirPlay, Cast, DLNA, Sonos, Snapcast, Spotify, Music Assistant |
223+
| Snapcast clients | `/admin/api/snapcast/…` | stream assignment |
224+
| Spotify | `/admin/api/spotify/…` | OAuth, librespot, bridges |
225+
| Apple Music | `/admin/api/applemusic/…` | Widevine key upload |
226+
| Line-In bridges | `/api/linein/…` | bridge register/status/ingest |
227+
| Audio streams | `/streams/{zone}/{id}.{format}` | mp3/wav/pcm/aac/flac |
228+
| Media library | `/admin/api/content/library/…` | upload, rescan, storages |
229+
| Custom radio | `/admin/api/content/radio/…` | add/delete/validate |
230+
| Logs | `/admin/api/logs/…` | list, SSE stream, level control |
231+
| Config | `/admin/api/config/…` | CRUD for server config |
232+
| Alert sounds | `/admin/api/alerts/…` | upload, list, revert |
233+
| Updates | `/admin/api/adminui/update`, `/admin/api/components/update` | |
234+
235+
---
236+
237+
## Viewing the docs
238+
239+
Open `index.html` in a browser. It loads the YAML specs locally via [Swagger UI](https://swagger.io/tools/swagger-ui/) (fetched from
240+
unpkg.com — requires internet).
241+
242+
The landing page has **three tabs**:
243+
244+
| Tab | Spec file | Covers |
245+
| -------------------------------------- | ---------------------------- | ------------------- |
246+
| **MiniServer API** | `miniserver-api.yaml` | API 1 |
247+
| **AudioServer – App & MiniServer API** | `openapi.yaml` | APIs 2 & 3 combined |
248+
| **AudioServer – Admin API** | `audioserver-admin-api.yaml` | API 4 |
249+
250+
In the **App & MiniServer API** tab, routes are grouped by tag:
251+
252+
- Tags **without a prefix** → used by both App and MiniServer
253+
- Tags starting with **`MiniServer ›`** → MiniServer only
254+
255+
---
256+
257+
## Files
258+
259+
```
260+
api-docs/
261+
├── index.html # Swagger UI with 3-tab spec switcher
262+
├── openapi.yaml # APIs 2 & 3 — App + MiniServer audio protocol
263+
├── miniserver-api.yaml # API 1 — MiniServer smart home commands
264+
├── audioserver-admin-api.yaml # API 4 — AudioServer Admin REST API
265+
└── README.md # This file
266+
```
267+
268+
## Sources
269+
270+
| File / directory | Used for |
271+
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
272+
| `ftp://<loxone-miniserver>:21/web/commonv2.agz` | MiniServer control types, `audio/…` schemas, WebSocket events |
273+
| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/loxone/commands/router/routeRegistry.ts` | Exact route list for both App and MiniServer |
274+
| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/loxone/http/loxoneHttpService.ts` | Two-port server architecture, heartbeat behaviour |
275+
| `https://github.com/lox-audioserver/lox-audioserver/src/config/loxone.ts` | Port names (`appHttp`/`msHttp`), identification strings |
276+
| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/loxone/commands/handlers/` | Handler logic, MiniServer-only config sync |
277+
| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/http/adminApi/adminApiHandler.ts` | Admin API routes |
278+
| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/http/streams/` | Audio stream and proxy endpoints |
279+
| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/http/lineInApi/` | Line-In bridge API |

0 commit comments

Comments
 (0)