Skip to content

Commit 78dd9ea

Browse files
committed
Initial commit
0 parents  commit 78dd9ea

6 files changed

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

0 commit comments

Comments
 (0)