Real-time Dashboard
The Multiflexmeter includes a real-time web dashboard for monitoring sensor data from one or more devices. The dashboard uses Server-Sent Events (SSE) for live updates and Chart.js for interactive data visualization.
Overview
Section titled “Overview”The dashboard provides real-time monitoring with:
- Live Updates: Server-Sent Events stream data as it arrives
- Interactive Charts: Sensor data visualization with Chart.js
- Device Status: Online/offline indicators and activity monitoring
- Activity Log: Real-time event stream with timestamps
- Multi-Device Support: Filter by device or view all devices
Accessing the Dashboard
Section titled “Accessing the Dashboard”Devices Overview
Section titled “Devices Overview”Navigate to /devices to see all registered devices:
http://your-domain/devicesFeatures:
- List of all devices with status indicators
- Hardware and firmware version information
- Last update timestamps
- Quick links to individual device dashboards
Individual Device Dashboard
Section titled “Individual Device Dashboard”Navigate to /dashboard with optional device filter:
http://your-domain/dashboardhttp://your-domain/dashboard?device=0000000000000001Features:
- Real-time sensor data visualization
- Generic sensor data charts (format depends on sensor module)
- Device information panel
- Activity log with recent events
Dashboard Components
Section titled “Dashboard Components”AS IS (Version 3.7.0)
Section titled “AS IS (Version 3.7.0)”The current dashboard displays data from the sensor passthrough firmware. The firmware transmits sensor module responses directly via LoRaWAN FPort 1 without firmware-level interpretation.
Key Characteristics:
- Passthrough architecture: Firmware does not decode sensor data
- Data format depends on the sensor module at I²C address 0x36
- Dashboard receives raw bytes encoded in base64
- Application-level decoding required for meaningful visualization
- No standardized data structure at firmware level
TO BE (Version 3.8.0)
Section titled “TO BE (Version 3.8.0)”Future versions will implement structured data handling with standardized formats:
- Defined
tx_pkt_tstructure (distance_to_water, air_temperature) - Firmware-level data validation
- Standardized visualization across all devices
Sensor Chart
Section titled “Sensor Chart”Interactive time-series chart for sensor data visualization:
- Generic Data Display: Visualizes sensor readings based on connected module
- Time Axis: Automatically scales based on data range
- Zoom/Pan: Interactive chart controls
- Auto-Update: New data points added in real-time
Chart Configuration (generic example):
{ type: 'line', data: { datasets: [ { label: 'Sensor Reading', data: sensorData, borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', } ] }, options: { scales: { x: { type: 'time', time: { unit: 'minute' } } } }}Device Information Panel
Section titled “Device Information Panel”Shows device metadata:
- Device EUI: LoRaWAN device identifier
- Hardware Version: Current hardware revision
- Firmware Version: Current firmware version
- Last Update: Timestamp of most recent reading
- Signal Quality: RSSI and SNR values
Activity Log
Section titled “Activity Log”Real-time stream of device events:
- Timestamp: Precise event time
- Event Type: Measurement, join, status change
- Event Details: Decoded sensor values
- Auto-Scroll: Newest events appear at the top
Data Flow Architecture
Section titled “Data Flow Architecture”Client-Server Communication
Section titled “Client-Server Communication”┌──────────────┐ ┌──────────────┐ ┌──────────────┐│ Device │ │ TTN │ │ Server ││ (Hardware) │────────>│ (LoRaWAN) │────────>│ /api/uplink │└──────────────┘ └──────────────┘ └──────┬───────┘ │ v┌──────────────┐ ┌──────────────┐ ┌──────────────┐│ Dashboard │<────────│ SSE Stream │<────────│ Data Store ││ (Browser) │ │ /api/stream │ │ (Redis/SQLite)│└──────────────┘ └──────────────┘ └──────────────┘Data Processing Pipeline
Section titled “Data Processing Pipeline”- Device Transmission: Multiflexmeter sends LoRaWAN uplink
- TTN Processing: The Things Network receives and forwards to webhook
- Uplink Endpoint:
/api/uplinkreceives and validates data - Data Storage: Reading stored in database (Redis in production, SQLite in dev)
- SSE Broadcast: New reading broadcast to all connected dashboard clients
- Dashboard Update: Browser receives event and updates UI in real-time
API Endpoints
Section titled “API Endpoints”POST /api/uplink
Section titled “POST /api/uplink”Receives uplink messages from TTN webhook or device simulator.
Authentication:
- Production: Basic Authentication required (validates
WEBHOOK_USERNAMEandWEBHOOK_PASSWORD) - Development: Authentication automatically skipped when
import.meta.env.DEV === trueto allow local simulator testing
Request Format (TTN Webhook):
{ "end_device_ids": { "dev_eui": "0000000000000001", "dev_addr": "00000001" }, "uplink_message": { "f_port": 1, "frm_payload": "NjEBAAEwP2Y=", "received_at": "2025-11-02T10:30:00Z", "rx_metadata": [{ "rssi": -85, "snr": 8.5 }], "decoded_payload": { "raw_data": "raw sensor bytes", "module_address": "0x36", "note": "Actual format depends on sensor module" } }, "locations": { "user": { "latitude": 52.0107, "longitude": 4.7093, "altitude": 5 } }}Request Format (Local Simulator):
{ "devEui": "0000000000000001", "devAddr": "00000001", "fPort": 1, "payload": "NjEBAAEwP2Y=", "receivedAt": "2025-11-02T10:30:00Z", "decoded": { "raw_data": "generic test data", "note": "Simulator sends test data for development" }, "rssi": -85, "snr": 8.5, "latitude": 52.0107, "longitude": 4.7093, "altitude": 5}Response:
{ "success": true, "message": "Uplink processed"}GET /api/uplink
Section titled “GET /api/uplink”Returns current system status and recent readings.
Response:
{ "status": "running", "devices": [ { "devEui": "0000000000000001", "devAddr": "00000001", "hwVersion": "1.2.0", "fwVersion": "3.7.0", "lastSeen": "2025-11-02T10:30:00Z" } ], "recentReadings": [...], "totalReadings": 245}GET /api/stream
Section titled “GET /api/stream”Server-Sent Events stream for real-time updates.
Event Types:
1. Connected Event:
event: connecteddata: {"message":"Connected to Multiflexmeter stream"}2. History Event (sent immediately after connection):
event: historydata: {"readings":[...last 100 readings...]}3. Reading Event (sent when new data arrives):
event: readingdata: { "type": "reading", "reading": { "devEui": "0000000000000001", "timestamp": "2025-11-02T10:30:00Z", "fPort": 1, "payload": "base64_encoded_raw_data", "decoded": { "note": "Format depends on sensor module at 0x36" }, "rssi": -85, "snr": 8.5 }}Client-Side Connection:
const eventSource = new EventSource('/api/stream');
eventSource.addEventListener('connected', (event) => { console.log('Connected to stream');});
eventSource.addEventListener('history', (event) => { const data = JSON.parse(event.data); populateInitialData(data.readings);});
eventSource.addEventListener('reading', (event) => { const data = JSON.parse(event.data); updateDashboard(data.reading);});
eventSource.onerror = (error) => { console.error('SSE connection error:', error);};Data Storage
Section titled “Data Storage”Development Environment
Section titled “Development Environment”SQLite Database:
- Location:
data/multiflexmeter.db - Automatic creation on first run
- Persistent across server restarts
- No configuration required
Tables:
CREATE TABLE devices ( id INTEGER PRIMARY KEY AUTOINCREMENT, devEui TEXT UNIQUE NOT NULL, devAddr TEXT, hwVersion TEXT, fwVersion TEXT, lastSeen TEXT, locationName TEXT, locationCity TEXT, latitude REAL, longitude REAL, altitude REAL);
CREATE TABLE readings ( id INTEGER PRIMARY KEY AUTOINCREMENT, devEui TEXT NOT NULL, timestamp TEXT NOT NULL, fPort INTEGER, payload TEXT, decoded TEXT, rssi REAL, snr REAL, FOREIGN KEY (devEui) REFERENCES devices(devEui));Production Environment
Section titled “Production Environment”Redis Storage:
- Native Redis via
ioredislibrary - Serverless-friendly persistence
- Environment variable:
REDIS_URL
Data Structures:
devices:{devEui} Hash of device metadatareadings:recent Sorted set of recent readings (last 200)reading:{id} Hash of individual reading dataExample:
# Store deviceHSET devices:0000000000000001 devEui "0000000000000001" \ hwVersion "1.2.0" fwVersion "3.7.0" lastSeen "2025-11-02T10:30:00Z"
# Store readingZADD readings:recent 1730548200 "reading:12345"HSET reading:12345 devEui "0000000000000001" payload "base64data" \ decoded "{...}" timestamp "2025-11-02T10:30:00Z"Hybrid Caching
Section titled “Hybrid Caching”Both environments use an in-memory cache for optimal performance:
{ recentReadings: [], // Last 200 readings in memory maxCacheSize: 200, // Automatic trimming updateInterval: 1000 // 1 second debounce for SSE}Deployment
Section titled “Deployment”Environment Variables
Section titled “Environment Variables”Required for production deployment:
# Redis connection (production only)REDIS_URL=redis://default:password@host:6379
# Webhook authentication (production only)WEBHOOK_USERNAME=multiflexmeterWEBHOOK_PASSWORD=your-secure-random-password
# Site URLSITE_URL=https://your-domain.comGenerate secure webhook password:
openssl rand -base64 32TTN Webhook Configuration
Section titled “TTN Webhook Configuration”Configure The Things Network to send data to your dashboard:
-
Navigate to your application in TTN Console
-
Go to Integrations → Webhooks
-
Create new webhook with:
- Webhook ID:
multiflexmeter-dashboard - Webhook format: JSON
- Base URL:
https://your-domain.com/api/uplink - Enabled messages: Check “Uplink message”
- Webhook ID:
-
Add custom header for authentication:
- Authorization:
Basic <base64 encoded credentials>
- Authorization:
-
Test webhook connection
Generate Authorization Header:
echo -n "username:password" | base64# Returns: dXNlcm5hbWU6cGFzc3dvcmQ=Use in webhook:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=Vercel Deployment
Section titled “Vercel Deployment”The dashboard is optimized for Vercel deployment:
-
Install Vercel CLI:
Terminal window npm install -g vercel -
Deploy:
Terminal window vercel -
Configure Environment Variables in Vercel dashboard:
REDIS_URLWEBHOOK_USERNAMEWEBHOOK_PASSWORDSITE_URL
-
Create Redis Database:
- Vercel: Project → Storage → Create Redis Database
- Or use external provider (Upstash, Redis Cloud, etc.)
-
Redeploy to apply environment variables
Development Workflow
Section titled “Development Workflow”Running Locally
Section titled “Running Locally”Start the development server:
npm run devThe dashboard is available at:
- Main dashboard:
http://localhost:4321/dashboard - Devices overview:
http://localhost:4321/devices
Using the Simulator
Section titled “Using the Simulator”Start the device simulator to generate test data:
npm run simulatorThe simulator will:
- Generate generic test sensor data
- Send uplink messages to
/api/uplink - Simulate multiple devices with different patterns
- Update every 10 seconds (configurable via
INTERVALenv var)
Custom Interval:
INTERVAL=5000 npm run simulator # Update every 5 secondsMultiple Devices:
Run multiple simulators with different device IDs:
# Terminal 1DEV_EUI=0000000000000001 npm run simulator
# Terminal 2DEV_EUI=0000000000000002 npm run simulatorDebugging
Section titled “Debugging”Enable debug logging in the browser console:
// Enable SSE debug logginglocalStorage.setItem('debug:sse', 'true');
// View cached readingsconsole.log(window.dashboardData.recentReadings);
// View Chart.js instanceconsole.log(window.sensorChart);Server-side logging is automatic in development mode.
Next Steps
Section titled “Next Steps”- Device Simulator - Testing with simulated devices
- Data Formats - Understanding payload structures
- Deployment Configuration - Production setup