Realtime Guide¶
Django DeviceHub can push live device events to web clients via WebSocket (Django Channels) or Server-Sent Events (SSE). This guide covers both backends, the bridge architecture, and frontend integration.
Architecture¶
The realtime system has three layers:
- Signals --
device_data_receivedanddevice_status_changedfire when theMessageRouterprocesses MQTT messages - Bridge --
RealtimeBridgelistens to signals and broadcasts events to the configured backend - Transport -- Either Django Channels (WebSocket) or SSE delivers events to connected web clients
MQTT Broker
|
v
MessageRouter --> Django Signal --> RealtimeBridge
|
+--------------------+--------------------+
| |
Channels backend SSE backend
(channel layer) (in-process queues)
| |
WebSocket consumer SSE streaming view
| |
Browser Browser
The bridge is a module-level singleton that auto-connects during app startup:
Configuration¶
Choose a backend in your settings:
| Backend | Requirements | Pros | Cons |
|---|---|---|---|
channels |
Django Channels + Redis | Bidirectional, scales across processes | Requires ASGI server + Redis |
sse |
None (built-in) | Zero dependencies, works with WSGI | Unidirectional, single-process only |
WebSocket Backend (Django Channels)¶
Installation¶
This installs Django Channels and channels-redis.
Django settings¶
INSTALLED_APPS = [
"daphne", # or uvicorn
"channels",
"django_devicehub",
...
]
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("localhost", 6379)],
},
},
}
DJANGO_DEVICEHUB = {
"REALTIME": {
"BACKEND": "channels",
},
}
ASGI routing¶
Add WebSocket URL patterns to your ASGI application:
# asgi.py
import os
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from django.core.asgi import get_asgi_application
from django_devicehub.realtime.routing import websocket_urlpatterns
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(websocket_urlpatterns)
),
})
WebSocket URL patterns¶
Two URL patterns are registered:
| Pattern | Description |
|---|---|
ws://host/ws/iot/{device_type}/ |
All devices of a type |
ws://host/ws/iot/{device_type}/{device_id}/ |
Specific device |
DeviceConsumer¶
The DeviceConsumer is a JsonWebSocketConsumer that:
- Rejects anonymous connections with close code
4403 - Joins channel groups based on the URL:
iot_{device_type}and optionallyiot_{device_type}_{device_id} - Forwards events from the bridge to the client as JSON messages
- Supports ping/pong: send
{"action": "ping"}and receive{"event": "pong"}
Messages sent to the client have this shape:
{
"event": "data",
"device_type": "weatherstation",
"device_id": "ws-001",
"data": {"temperature": 25.3, "humidity": 80.1},
"timestamp": "2025-01-15T12:00:00+00:00"
}
For status changes:
{
"event": "status",
"device_type": "weatherstation",
"device_id": "ws-001",
"data": {"old_status": "offline", "new_status": "online"},
"timestamp": "2025-01-15T12:00:05+00:00"
}
SSE Backend¶
The SSE backend requires no additional dependencies. It works with any WSGI server that supports streaming responses (gunicorn with sync workers, Django's dev server, etc.).
Configuration¶
DJANGO_DEVICEHUB = {
"REALTIME": {
"BACKEND": "sse",
"SSE_KEEPALIVE_INTERVAL": 30, # seconds between keepalive comments
},
}
URL setup¶
Include the SSE URLs in your project:
This provides four endpoints:
| URL | Description |
|---|---|
/iot/stream/{device_type}/ |
SSE stream -- all devices of a type |
/iot/stream/{device_type}/{device_id}/ |
SSE stream -- specific device |
/iot/poll/{device_type}/ |
Polling fallback -- all devices of a type |
/iot/poll/{device_type}/{device_id}/ |
Polling fallback -- specific device |
All endpoints require authentication (@login_required).
SSE wire format¶
Events are sent in standard SSE format:
event: connected
data: {"group": "iot_weatherstation"}
event: data
data: {"device_id": "ws-001", "data": {"temperature": 25.3}, ...}
: keepalive
event: status
data: {"device_id": "ws-001", "data": {"old_status": "offline", ...}, ...}
The keepalive comment (: keepalive) is sent every SSE_KEEPALIVE_INTERVAL seconds to prevent proxies and browsers from closing the connection due to inactivity.
Polling fallback¶
The /iot/poll/{device_type}/ endpoint returns a JSON array of any events that occurred since the last poll. This is a simple fallback for clients that cannot use SSE or WebSockets:
Nginx configuration for SSE¶
Disable response buffering so events are delivered immediately:
location /iot/stream/ {
proxy_pass http://django-app:8000;
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding off;
}
The SSE view already sets X-Accel-Buffering: no and Cache-Control: no-cache headers.
Frontend Integration¶
Connecting via SSE (JavaScript)¶
const source = new EventSource("/iot/stream/weatherstation/");
source.addEventListener("connected", (e) => {
console.log("Connected:", JSON.parse(e.data));
});
source.addEventListener("data", (e) => {
const msg = JSON.parse(e.data);
console.log(`Device ${msg.device_id}:`, msg.data);
// Update dashboard UI
updateChart(msg.device_id, msg.data.temperature);
});
source.addEventListener("status", (e) => {
const msg = JSON.parse(e.data);
console.log(`${msg.device_id} status: ${msg.data.new_status}`);
updateDeviceStatus(msg.device_id, msg.data.new_status);
});
source.onerror = () => {
console.warn("SSE connection lost, reconnecting...");
};
Connecting via WebSocket (JavaScript)¶
const ws = new WebSocket("ws://localhost:8000/ws/iot/weatherstation/");
ws.onopen = () => {
console.log("WebSocket connected");
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.event === "data") {
updateChart(msg.device_id, msg.data);
} else if (msg.event === "status") {
updateDeviceStatus(msg.device_id, msg.data.new_status);
}
};
// Ping to keep connection alive
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({action: "ping"}));
}
}, 30000);
Subscribing to a specific device¶
// SSE -- specific device
const source = new EventSource("/iot/stream/weatherstation/STATION-001/");
// WebSocket -- specific device
const ws = new WebSocket("ws://localhost:8000/ws/iot/weatherstation/STATION-001/");
Manual Broadcasting¶
You can broadcast events programmatically without going through MQTT:
from django_devicehub.realtime.bridge import realtime_bridge
realtime_bridge.broadcast(
device_type="weatherstation",
device_id="ws-001",
event_type="data",
data={"temperature": 25.3, "humidity": 80.1},
)
This pushes the event to all connected WebSocket/SSE clients subscribed to the weatherstation type or the specific ws-001 device.