mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-11 01:55:43 +00:00
dd0889e3d6
Introduce a SessionManager for relay-based remote sessions in the Tauri MGMT client: new SessionCommand API, start/stop/session input routing, clipboard/recording/quality controls, and notification read/dismiss state. Wire AppState with new mutexes and show main window on startup. CI: add SBOM generation (anchore) and Trivy vulnerability scan steps. Misc: change console Docker DB path, large README/CHANGELOG updates (chat E2E, unattended access/WOL, i18n expansion, CDAP/SDK docs), and many web-nodejs assets/locales/routes/views/services and server-side changes.
5.7 KiB
5.7 KiB
Building a CDAP Bridge
A CDAP bridge connects non-BetterDesk devices (industrial equipment, IoT sensors, network gear) to the BetterDesk management platform via the CDAP protocol.
What is a Bridge?
A bridge is a lightweight process that:
- Communicates with external hardware/software using its native protocol (Modbus, SNMP, REST, MQTT, etc.)
- Translates data into CDAP widget values
- Exposes CDAP commands that map to hardware actions
┌──────────────┐ Modbus/TCP ┌──────────┐ CDAP/WS ┌──────────────┐
│ PLC / RTU │ ◄═══════════════► │ Bridge │ ◄═════════════► │ BetterDesk │
│ (hardware) │ │ (Python) │ │ Server │
└──────────────┘ └──────────┘ └──────────────┘
Reference Bridges
| Bridge | Protocol | Directory | Description |
|---|---|---|---|
| Modbus | Modbus TCP/RTU | bridges/modbus/ |
Register polling, data type encode/decode, write-back |
| SNMP | SNMPv2c/v3 | bridges/snmp/ |
OID polling, timetick formatting, counter rates |
| REST/Webhook | HTTP | bridges/rest-webhook/ |
REST polling + aiohttp webhook listener |
Creating a Bridge (Python)
1. Install the SDK
pip install betterdesk-cdap # or: pip install -e sdks/python/
2. Define Your Bridge
import asyncio
from betterdesk_cdap import CDAPBridge, Widget
class TemperatureBridge(CDAPBridge):
"""Bridge for a network temperature sensor."""
def __init__(self, sensor_ip, sensor_port=502):
super().__init__(
server_url="ws://betterdesk-server:21122/cdap",
device_id=f"TEMP-{sensor_ip.replace('.', '-')}",
device_name=f"Temp Sensor {sensor_ip}",
device_type="temperature_sensor",
auth_method="api_key",
api_key=os.environ["CDAP_API_KEY"]
)
self.sensor_ip = sensor_ip
self.sensor_port = sensor_port
# Define widgets
self.add_widget(Widget.gauge("temp", "Temperature", group="Readings",
unit="°C", min_val=-40, max_val=125,
danger=80, warning=60))
self.add_widget(Widget.text("firmware", "Firmware", group="Info"))
self.add_widget(Widget.led("alarm", "High Temp Alarm", group="Status"))
async def on_connected(self):
# Read firmware version once
fw = await self._read_firmware()
await self.update_widget("firmware", fw)
async def collect_metrics(self):
"""Called every heartbeat interval. Read sensor data."""
temp = await self._read_temperature()
alarm = temp > 80
return {
"temp": round(temp, 1),
"alarm": alarm,
}
async def on_command(self, command_id, command, args):
if command == "set_threshold":
threshold = args.get("value", 80)
await self._write_threshold(threshold)
await self.send_command_response(command_id, True, f"Threshold set to {threshold}")
else:
await self.send_command_response(command_id, False, f"Unknown command: {command}")
async def _read_temperature(self):
# Your hardware-specific code here
...
asyncio.run(TemperatureBridge("192.168.1.100").run())
3. Configuration
Use a JSON or YAML config file:
{
"server_url": "ws://betterdesk:21122/cdap",
"api_key": "${CDAP_API_KEY}",
"device_id": "MODBUS-PLC-001",
"device_name": "Production Line PLC",
"device_type": "modbus_plc",
"poll_interval": 5,
"registers": [
{ "address": 0, "type": "float32", "widget_id": "pressure", "label": "Pressure", "unit": "bar" },
{ "address": 2, "type": "uint16", "widget_id": "rpm", "label": "Motor RPM", "unit": "RPM" }
]
}
Bridge Best Practices
- Reconnection — The SDK handles WebSocket reconnection with exponential backoff. Don't implement your own retry logic.
- Error isolation — Catch hardware communication errors in
collect_metrics(). Return last known values on failure. - Poll intervals — Match the bridge's poll interval to the hardware's update rate. Don't poll a 1Hz sensor at 100ms.
- Bulk updates — Use
bulk_updateto send all widget values at once, reducing WebSocket messages. - Device IDs — Use deterministic IDs based on hardware address (IP, serial number). Don't use random UUIDs.
- Graceful shutdown — Handle
SIGINT/SIGTERMto disconnect cleanly from both hardware and server.
Node.js Bridge
const { CDAPBridge, Widget } = require('betterdesk-cdap');
const bridge = new CDAPBridge({
serverUrl: 'ws://betterdesk:21122/cdap',
deviceId: 'REST-SENSOR-001',
deviceName: 'REST API Sensor',
deviceType: 'rest_sensor',
authMethod: 'api_key',
apiKey: process.env.CDAP_API_KEY,
heartbeatSec: 10
});
bridge.addWidget(Widget.gauge('humidity', 'Humidity', {
group: 'Environment', unit: '%', min: 0, max: 100
}));
bridge.on('collectMetrics', async () => {
const resp = await fetch('http://sensor-device/api/data');
const data = await resp.json();
return { humidity: data.humidity };
});
bridge.connect();
Deployment
Bridges are typically deployed as:
- systemd services on Linux
- Docker containers alongside the BetterDesk stack
- NSSM services on Windows
See bridges/modbus/Dockerfile and bridges/modbus/bridge.service for examples.