Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
5399f044a1
|
|||
|
16fa5143dd
|
|||
|
cff154c247
|
|||
|
038664ec94
|
@@ -1,301 +0,0 @@
|
|||||||
# Home Automation API Client
|
|
||||||
|
|
||||||
Wiederverwendbare JavaScript-API-Client-Bibliothek für das Home Automation UI.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Füge die folgenden Script-Tags in deine HTML-Seiten ein:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script src="/static/types.js"></script>
|
|
||||||
<script src="/static/api-client.js"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Konfiguration
|
|
||||||
|
|
||||||
Der API-Client nutzt `window.API_BASE`, das vom Backend gesetzt wird:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
window.API_BASE = '{{ api_base }}'; // Jinja2 template
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verwendung
|
|
||||||
|
|
||||||
### Globale Instanz
|
|
||||||
|
|
||||||
Der API-Client erstellt automatisch eine globale Instanz `window.apiClient`:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Layout abrufen
|
|
||||||
const layout = await window.apiClient.getLayout();
|
|
||||||
|
|
||||||
// Geräte abrufen
|
|
||||||
const devices = await window.apiClient.getDevices();
|
|
||||||
|
|
||||||
// Gerätestatus abrufen
|
|
||||||
const state = await window.apiClient.getDeviceState('kitchen_light');
|
|
||||||
|
|
||||||
// Gerätesteuerung
|
|
||||||
await window.apiClient.setDeviceState('kitchen_light', 'light', {
|
|
||||||
power: true,
|
|
||||||
brightness: 80
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Verfügbare Methoden
|
|
||||||
|
|
||||||
#### `getLayout(): Promise<Layout>`
|
|
||||||
Lädt die Layout-Daten (Räume und ihre Geräte).
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const layout = await window.apiClient.getLayout();
|
|
||||||
// { rooms: [{name: "Küche", devices: ["kitchen_light", ...]}, ...] }
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `getDevices(): Promise<Device[]>`
|
|
||||||
Lädt alle Geräte mit ihren Features.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const devices = await window.apiClient.getDevices();
|
|
||||||
// [{device_id: "...", name: "...", type: "light", features: {...}}, ...]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `getDeviceState(deviceId): Promise<DeviceState>`
|
|
||||||
Lädt den aktuellen Status eines Geräts.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const state = await window.apiClient.getDeviceState('kitchen_light');
|
|
||||||
// {power: true, brightness: 80, ...}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `getAllStates(): Promise<Object>`
|
|
||||||
Lädt alle Gerätestatus auf einmal.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const states = await window.apiClient.getAllStates();
|
|
||||||
// {"kitchen_light": {power: true, ...}, "thermostat_1": {...}, ...}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `setDeviceState(deviceId, type, payload): Promise<void>`
|
|
||||||
Sendet einen Befehl an ein Gerät.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Licht einschalten
|
|
||||||
await window.apiClient.setDeviceState('kitchen_light', 'light', {
|
|
||||||
power: true,
|
|
||||||
brightness: 80
|
|
||||||
});
|
|
||||||
|
|
||||||
// Thermostat einstellen
|
|
||||||
await window.apiClient.setDeviceState('thermostat_1', 'thermostat', {
|
|
||||||
target_temp: 22.5
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rollladen steuern
|
|
||||||
await window.apiClient.setDeviceState('cover_1', 'cover', {
|
|
||||||
position: 50
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `getDeviceRoom(deviceId): Promise<{room: string}>`
|
|
||||||
Ermittelt den Raum eines Geräts.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const { room } = await window.apiClient.getDeviceRoom('kitchen_light');
|
|
||||||
// {room: "Küche"}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `getScenes(): Promise<Scene[]>`
|
|
||||||
Lädt alle verfügbaren Szenen.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const scenes = await window.apiClient.getScenes();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `activateScene(sceneId): Promise<void>`
|
|
||||||
Aktiviert eine Szene.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
await window.apiClient.activateScene('evening');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Realtime-Updates (SSE)
|
|
||||||
|
|
||||||
#### `connectRealtime(onEvent, onError): EventSource`
|
|
||||||
Verbindet sich mit dem SSE-Stream für Live-Updates.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
window.apiClient.connectRealtime(
|
|
||||||
(event) => {
|
|
||||||
console.log('Update:', event.device_id, event.state);
|
|
||||||
// event = {device_id: "...", type: "state", state: {...}}
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.error('Connection error:', error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `onDeviceUpdate(deviceId, callback): Function`
|
|
||||||
Registriert einen Listener für spezifische Geräte-Updates.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Für ein bestimmtes Gerät
|
|
||||||
const unsubscribe = window.apiClient.onDeviceUpdate('kitchen_light', (event) => {
|
|
||||||
console.log('Kitchen light changed:', event.state);
|
|
||||||
updateUI(event.state);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Für alle Geräte
|
|
||||||
const unsubscribeAll = window.apiClient.onDeviceUpdate(null, (event) => {
|
|
||||||
console.log('Any device changed:', event.device_id, event.state);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Später: Listener entfernen
|
|
||||||
unsubscribe();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `disconnectRealtime(): void`
|
|
||||||
Trennt die SSE-Verbindung und entfernt alle Listener.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
window.apiClient.disconnectRealtime();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Helper-Methoden
|
|
||||||
|
|
||||||
#### `findDevice(devices, deviceId): Device|null`
|
|
||||||
Findet ein Gerät in einem Array.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const devices = await window.apiClient.getDevices();
|
|
||||||
const device = window.apiClient.findDevice(devices, 'kitchen_light');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `findRoom(layout, roomName): Room|null`
|
|
||||||
Findet einen Raum im Layout.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const layout = await window.apiClient.getLayout();
|
|
||||||
const room = window.apiClient.findRoom(layout, 'Küche');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `getDevicesForRoom(layout, devices, roomName): Device[]`
|
|
||||||
Gibt alle Geräte eines Raums zurück.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const layout = await window.apiClient.getLayout();
|
|
||||||
const devices = await window.apiClient.getDevices();
|
|
||||||
const kitchenDevices = window.apiClient.getDevicesForRoom(layout, devices, 'Küche');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `api(path): string`
|
|
||||||
Konstruiert eine vollständige API-URL.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const url = window.apiClient.api('/devices');
|
|
||||||
// "http://172.19.1.11:8001/devices"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backward Compatibility
|
|
||||||
|
|
||||||
Die globale `api()` Funktion ist weiterhin verfügbar:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function api(url) {
|
|
||||||
return window.apiClient.api(url);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Typen (JSDoc)
|
|
||||||
|
|
||||||
Die Datei `types.js` enthält JSDoc-Definitionen für alle API-Typen:
|
|
||||||
|
|
||||||
- `Room` - Raum mit Geräten
|
|
||||||
- `Layout` - Layout-Struktur
|
|
||||||
- `Device` - Gerätedaten
|
|
||||||
- `DeviceFeatures` - Geräte-Features
|
|
||||||
- `DeviceState` - Gerätestatus (Light, Thermostat, Contact, etc.)
|
|
||||||
- `RealtimeEvent` - SSE-Event-Format
|
|
||||||
- `Scene` - Szenen-Definition
|
|
||||||
- `*Payload` - Command-Payloads für verschiedene Gerätetypen
|
|
||||||
|
|
||||||
Diese ermöglichen IDE-Autocomplete und Type-Checking in modernen Editoren (VS Code, WebStorm).
|
|
||||||
|
|
||||||
## Beispiel: Vollständige Seite
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>My Page</title>
|
|
||||||
<script src="/static/types.js"></script>
|
|
||||||
<script src="/static/api-client.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="status"></div>
|
|
||||||
<button id="toggle">Toggle Light</button>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window.API_BASE = 'http://172.19.1.11:8001';
|
|
||||||
const deviceId = 'kitchen_light';
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
// Load initial state
|
|
||||||
const state = await window.apiClient.getDeviceState(deviceId);
|
|
||||||
updateUI(state);
|
|
||||||
|
|
||||||
// Listen for updates
|
|
||||||
window.apiClient.onDeviceUpdate(deviceId, (event) => {
|
|
||||||
updateUI(event.state);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect to realtime
|
|
||||||
window.apiClient.connectRealtime((event) => {
|
|
||||||
console.log('Event:', event);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle button clicks
|
|
||||||
document.getElementById('toggle').onclick = async () => {
|
|
||||||
const currentState = await window.apiClient.getDeviceState(deviceId);
|
|
||||||
await window.apiClient.setDeviceState(deviceId, 'light', {
|
|
||||||
power: !currentState.power
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUI(state) {
|
|
||||||
document.getElementById('status').textContent =
|
|
||||||
state.power ? 'ON' : 'OFF';
|
|
||||||
}
|
|
||||||
|
|
||||||
init();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
Alle API-Methoden werfen Exceptions bei Fehlern:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
try {
|
|
||||||
const state = await window.apiClient.getDeviceState('invalid_id');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('API error:', error);
|
|
||||||
showErrorMessage(error.message);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Auto-Reconnect
|
|
||||||
|
|
||||||
Der SSE-Client versucht automatisch, nach 5 Sekunden wieder zu verbinden, wenn die Verbindung abbricht.
|
|
||||||
|
|
||||||
## Verwendete Technologien
|
|
||||||
|
|
||||||
- **Fetch API** - Für HTTP-Requests
|
|
||||||
- **EventSource** - Für Server-Sent Events
|
|
||||||
- **JSDoc** - Für Type Definitions
|
|
||||||
- **ES6+** - Modern JavaScript (Class, async/await, etc.)
|
|
||||||
1
apps/static/static/index.html
Normal file
1
apps/static/static/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
empty
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Home Automation",
|
|
||||||
"short_name": "Home",
|
|
||||||
"description": "Smart Home Automation System",
|
|
||||||
"start_url": "/",
|
|
||||||
"display": "standalone",
|
|
||||||
"background_color": "#667eea",
|
|
||||||
"theme_color": "#667eea",
|
|
||||||
"orientation": "portrait",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/static/apple-touch-icon-180x180.png",
|
|
||||||
"sizes": "180x180",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "any maskable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/static/apple-touch-icon-152x152.png",
|
|
||||||
"sizes": "152x152",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/static/apple-touch-icon-120x120.png",
|
|
||||||
"sizes": "120x120",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/static/apple-touch-icon-76x76.png",
|
|
||||||
"sizes": "76x76",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/static/apple-touch-icon-32x32.png",
|
|
||||||
"sizes": "32x32",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/static/apple-touch-icon-16x16.png",
|
|
||||||
"sizes": "16x16",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
<meta name="apple-mobile-web-app-title" content="Dashboard">
|
<meta name="apple-mobile-web-app-title" content="Dashboard">
|
||||||
<meta name="theme-color" content="#667eea">
|
<meta name="theme-color" content="#667eea">
|
||||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
<meta name="apple-mobile-web-app-title" content="Gerät">
|
<meta name="apple-mobile-web-app-title" content="Gerät">
|
||||||
<meta name="theme-color" content="#667eea">
|
<meta name="theme-color" content="#667eea">
|
||||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
<meta name="apple-mobile-web-app-title" content="Garage">
|
<meta name="apple-mobile-web-app-title" content="Garage">
|
||||||
<meta name="theme-color" content="#667eea">
|
<meta name="theme-color" content="#667eea">
|
||||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
<meta name="apple-mobile-web-app-title" content="Home Automation">
|
<meta name="apple-mobile-web-app-title" content="Home Automation">
|
||||||
<meta name="theme-color" content="#667eea">
|
<meta name="theme-color" content="#667eea">
|
||||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
<meta name="apple-mobile-web-app-title" content="{{ room_name }}">
|
<meta name="apple-mobile-web-app-title" content="{{ room_name }}">
|
||||||
<meta name="theme-color" content="#667eea">
|
<meta name="theme-color" content="#667eea">
|
||||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
<meta name="apple-mobile-web-app-title" content="Räume">
|
<meta name="apple-mobile-web-app-title" content="Räume">
|
||||||
<meta name="theme-color" content="#667eea">
|
<meta name="theme-color" content="#667eea">
|
||||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ data:
|
|||||||
# UI specific environment variables
|
# UI specific environment variables
|
||||||
UI_UI_PORT: "8002"
|
UI_UI_PORT: "8002"
|
||||||
UI_API_BASE: "https://homea2-api.hottis.de"
|
UI_API_BASE: "https://homea2-api.hottis.de"
|
||||||
UI_STATIC_BASE: "http://homea2-static.hottis.de"
|
UI_STATIC_BASE: "https://homea2-static.hottis.de"
|
||||||
UI_BASE_PATH: "/"
|
UI_BASE_PATH: "/"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ spec:
|
|||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
name: home-automation-environment
|
name: home-automation-environment
|
||||||
key: UI_API_BASE
|
key: UI_API_BASE
|
||||||
|
- name: STATIC_BASE
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: home-automation-environment
|
||||||
|
key: UI_STATIC_BASE
|
||||||
- name: BASE_PATH
|
- name: BASE_PATH
|
||||||
valueFrom:
|
valueFrom:
|
||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
|
|||||||
Reference in New Issue
Block a user