Complete IoT monitoring platform for Acuvim II power meters via ESP32. Firmware (Phases 1-7): - ESP32-WROVER-B (TTGO T-Call v1.4) with RS485 Modbus RTU - WiFi STA+AP concurrent mode with GSM/GPRS failover - Transport abstraction layer with 4 priority modes - MQTT protocol with 20 commands, LWT, QoS, exponential backoff - SD card offline buffering with JSONL rotation and non-blocking drain - OTA firmware updates with dual partition rollback protection - Watchdog timer, crash loop detection, Acuvim health monitoring - Captive portal provisioning with AP mode Console backend (Phase 8): - .NET 10 minimal API with PostgreSQL + EF Core - JWT authentication, SignalR real-time updates - MQTTnet 5.x bridge service with health monitoring - Device, telemetry, firmware, alert, group management - Rate limiting, security headers, Swagger/OpenAPI Frontend (Phase 9): - React 18 + TypeScript + Vite with Ant Design 5 - ECharts telemetry visualization, TanStack Query - SignalR live updates, device management UI - Dashboard, fleet management, firmware deployment Testing & Production (Phase 10): - 28 firmware unit tests (Modbus, JSON, config, version) - 23 xUnit backend tests (device, telemetry, command, alert) - Docker Compose with nginx, TLS MQTT, PostgreSQL - Production deployment, commissioning, and troubleshooting docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
660 lines
21 KiB
Markdown
660 lines
21 KiB
Markdown
# Phase 8: Console Application Backend
|
|
|
|
## Objective
|
|
|
|
Build the server-side backend for the console application that manages the ESP32 device fleet. The backend handles device registration, configuration management, telemetry ingestion, OTA firmware distribution, health monitoring, and exposes a REST API for the frontend (Phase 9).
|
|
|
|
## Prerequisites
|
|
|
|
- Phase 7 complete (device firmware feature-complete)
|
|
- Server/VPS for hosting (Linux recommended)
|
|
- Domain name (optional, can use IP for development)
|
|
- PostgreSQL database
|
|
- Mosquitto MQTT broker
|
|
|
|
## Deliverables
|
|
|
|
1. .NET 10 Web API application
|
|
2. PostgreSQL database schema and migrations
|
|
3. MQTT client bridge (subscribe to device topics, publish commands)
|
|
4. REST API for frontend and external integrations
|
|
5. Firmware binary hosting and version management
|
|
6. Authentication and authorization
|
|
7. Deployment configuration (Docker)
|
|
|
|
---
|
|
|
|
## 8.1 Technology Stack
|
|
|
|
| Component | Technology | Justification |
|
|
|-----------|-----------|---------------|
|
|
| Runtime | .NET 10 | Strong typing, high performance, built-in DI |
|
|
| Web Framework | ASP.NET Core Minimal API | Lightweight, fast, less boilerplate |
|
|
| Database | PostgreSQL 16 | Robust, JSON support, time-series extensions |
|
|
| ORM | Entity Framework Core 8 | Migrations, LINQ queries, strong typing |
|
|
| MQTT Client | MQTTnet | .NET-native, high-performance MQTT v5 client |
|
|
| Auth | ASP.NET Identity + JWT | Built-in user management, token-based API auth |
|
|
| File Storage | Local filesystem + optional S3 | Firmware binaries |
|
|
| Caching | In-memory + optional Redis | Device status, frequent lookups |
|
|
| Logging | Serilog | Structured logging |
|
|
| API Docs | Swagger / OpenAPI | Auto-generated API documentation |
|
|
| Containerization | Docker + Docker Compose | Reproducible deployment |
|
|
|
|
## 8.2 Project Structure
|
|
|
|
```
|
|
console/
|
|
├── src/
|
|
│ ├── Tau.Acuvim.Console/ # Main Web API project
|
|
│ │ ├── Program.cs
|
|
│ │ ├── appsettings.json
|
|
│ │ ├── Controllers/ # API endpoints (if using controllers)
|
|
│ │ ├── Endpoints/ # Minimal API endpoint groups
|
|
│ │ │ ├── DeviceEndpoints.cs
|
|
│ │ │ ├── TelemetryEndpoints.cs
|
|
│ │ │ ├── FirmwareEndpoints.cs
|
|
│ │ │ ├── ConfigEndpoints.cs
|
|
│ │ │ ├── AlertEndpoints.cs
|
|
│ │ │ └── AuthEndpoints.cs
|
|
│ │ ├── Services/
|
|
│ │ │ ├── MqttBridgeService.cs # MQTT client (background service)
|
|
│ │ │ ├── DeviceService.cs
|
|
│ │ │ ├── TelemetryService.cs
|
|
│ │ │ ├── FirmwareService.cs
|
|
│ │ │ ├── CommandService.cs
|
|
│ │ │ ├── HealthMonitorService.cs
|
|
│ │ │ └── AlertService.cs
|
|
│ │ ├── Models/
|
|
│ │ │ ├── Device.cs
|
|
│ │ │ ├── TelemetryRecord.cs
|
|
│ │ │ ├── FirmwareVersion.cs
|
|
│ │ │ ├── DeviceConfig.cs
|
|
│ │ │ ├── Alert.cs
|
|
│ │ │ ├── Command.cs
|
|
│ │ │ └── User.cs
|
|
│ │ ├── Data/
|
|
│ │ │ ├── AppDbContext.cs
|
|
│ │ │ └── Migrations/
|
|
│ │ ├── DTOs/ # Request/response models
|
|
│ │ ├── Middleware/
|
|
│ │ │ └── ApiKeyMiddleware.cs
|
|
│ │ └── Hubs/
|
|
│ │ └── DeviceHub.cs # SignalR for real-time UI updates
|
|
│ │
|
|
│ └── Tau.Acuvim.Console.Tests/ # Unit and integration tests
|
|
│
|
|
├── docker-compose.yml
|
|
├── Dockerfile
|
|
└── README.md
|
|
```
|
|
|
|
## 8.3 Database Schema
|
|
|
|
### Entity Relationship
|
|
|
|
```
|
|
Devices ──< TelemetryRecords
|
|
Devices ──< Alerts
|
|
Devices ──< Commands
|
|
Devices ──< DeviceConfigs (1:1 current, history)
|
|
FirmwareVersions (standalone)
|
|
Users (standalone)
|
|
```
|
|
|
|
### Tables
|
|
|
|
#### `devices`
|
|
|
|
```sql
|
|
CREATE TABLE devices (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
device_id VARCHAR(32) UNIQUE NOT NULL, -- e.g., "ACV-AABBCCDDEEFF"
|
|
name VARCHAR(128), -- Friendly name
|
|
description TEXT,
|
|
mac_address VARCHAR(17),
|
|
imei VARCHAR(15),
|
|
iccid VARCHAR(20),
|
|
hardware VARCHAR(64), -- "TTGO T-Call v1.4"
|
|
firmware_version VARCHAR(16),
|
|
capabilities JSONB, -- ["wifi","gsm","sd","modbus"]
|
|
status VARCHAR(16) DEFAULT 'offline', -- online, offline, degraded
|
|
last_heartbeat TIMESTAMPTZ,
|
|
last_telemetry TIMESTAMPTZ,
|
|
connection_type VARCHAR(8), -- wifi, gsm
|
|
signal_strength INTEGER,
|
|
ip_address VARCHAR(45),
|
|
boot_count INTEGER DEFAULT 0,
|
|
group_id UUID REFERENCES device_groups(id),
|
|
tags JSONB DEFAULT '[]',
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_devices_device_id ON devices(device_id);
|
|
CREATE INDEX idx_devices_status ON devices(status);
|
|
```
|
|
|
|
#### `telemetry_records`
|
|
|
|
```sql
|
|
CREATE TABLE telemetry_records (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
device_id VARCHAR(32) NOT NULL REFERENCES devices(device_id),
|
|
timestamp TIMESTAMPTZ NOT NULL,
|
|
data JSONB NOT NULL, -- Full telemetry payload
|
|
source VARCHAR(8) DEFAULT 'live', -- live, sd (buffered)
|
|
connection VARCHAR(8), -- wifi, gsm
|
|
received_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_telemetry_device_ts ON telemetry_records(device_id, timestamp DESC);
|
|
CREATE INDEX idx_telemetry_timestamp ON telemetry_records(timestamp DESC);
|
|
|
|
-- Partitioning by month (for large-scale deployments)
|
|
-- Consider TimescaleDB extension for automatic time-series management
|
|
```
|
|
|
|
#### `alerts`
|
|
|
|
```sql
|
|
CREATE TABLE alerts (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
device_id VARCHAR(32) NOT NULL REFERENCES devices(device_id),
|
|
alert_type VARCHAR(32) NOT NULL, -- overvoltage, modbus_loss, etc.
|
|
severity VARCHAR(16) NOT NULL, -- info, warning, critical
|
|
message TEXT NOT NULL,
|
|
value DOUBLE PRECISION,
|
|
threshold DOUBLE PRECISION,
|
|
metadata JSONB, -- Additional context
|
|
acknowledged BOOLEAN DEFAULT FALSE,
|
|
acknowledged_by VARCHAR(128),
|
|
acknowledged_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
resolved_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX idx_alerts_device ON alerts(device_id, created_at DESC);
|
|
CREATE INDEX idx_alerts_unresolved ON alerts(device_id) WHERE resolved_at IS NULL;
|
|
```
|
|
|
|
#### `commands`
|
|
|
|
```sql
|
|
CREATE TABLE commands (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
device_id VARCHAR(32) NOT NULL REFERENCES devices(device_id),
|
|
request_id VARCHAR(64) UNIQUE NOT NULL,
|
|
command VARCHAR(32) NOT NULL,
|
|
params JSONB,
|
|
status VARCHAR(16) DEFAULT 'pending', -- pending, sent, ack, success, error, timeout
|
|
response JSONB,
|
|
created_by VARCHAR(128),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
sent_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX idx_commands_device ON commands(device_id, created_at DESC);
|
|
CREATE INDEX idx_commands_pending ON commands(status) WHERE status IN ('pending', 'sent');
|
|
```
|
|
|
|
#### `firmware_versions`
|
|
|
|
```sql
|
|
CREATE TABLE firmware_versions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
version VARCHAR(16) UNIQUE NOT NULL, -- "1.2.0"
|
|
filename VARCHAR(128) NOT NULL,
|
|
file_path VARCHAR(256) NOT NULL,
|
|
file_size INTEGER NOT NULL,
|
|
checksum VARCHAR(128) NOT NULL, -- SHA256
|
|
release_notes TEXT,
|
|
hardware VARCHAR(64), -- Target hardware (null = all)
|
|
mandatory BOOLEAN DEFAULT FALSE,
|
|
active BOOLEAN DEFAULT TRUE, -- Available for deployment
|
|
uploaded_by VARCHAR(128),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
#### `device_groups`
|
|
|
|
```sql
|
|
CREATE TABLE device_groups (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(64) UNIQUE NOT NULL,
|
|
description TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
## 8.4 MQTT Bridge Service
|
|
|
|
### `MqttBridgeService.cs`
|
|
|
|
A background service that maintains an MQTT connection to the broker, subscribes to all device topics, processes incoming messages, and publishes commands.
|
|
|
|
```csharp
|
|
public class MqttBridgeService : BackgroundService
|
|
{
|
|
// Subscribe to:
|
|
// acuvim/+/telemetry -> TelemetryService.Ingest()
|
|
// acuvim/+/heartbeat -> HealthMonitorService.ProcessHeartbeat()
|
|
// acuvim/+/resp -> CommandService.ProcessResponse()
|
|
// acuvim/+/status -> DeviceService.UpdateStatus()
|
|
// acuvim/+/alerts -> AlertService.ProcessAlert()
|
|
// devices/register -> DeviceService.Register()
|
|
|
|
// Publish to:
|
|
// acuvim/{device_id}/cmd -> CommandService.Send()
|
|
}
|
|
```
|
|
|
|
### Topic Routing
|
|
|
|
```csharp
|
|
private async Task HandleMessage(MqttApplicationMessage msg)
|
|
{
|
|
var topic = msg.Topic;
|
|
var payload = Encoding.UTF8.GetString(msg.PayloadSegment);
|
|
|
|
// Parse topic: acuvim/{device_id}/{type}
|
|
var parts = topic.Split('/');
|
|
|
|
if (topic == "devices/register")
|
|
{
|
|
await _deviceService.Register(payload);
|
|
return;
|
|
}
|
|
|
|
if (parts.Length >= 3)
|
|
{
|
|
var deviceId = parts[1];
|
|
var type = parts[2];
|
|
|
|
switch (type)
|
|
{
|
|
case "telemetry":
|
|
await _telemetryService.Ingest(deviceId, payload);
|
|
break;
|
|
case "heartbeat":
|
|
await _healthMonitor.ProcessHeartbeat(deviceId, payload);
|
|
break;
|
|
case "resp":
|
|
await _commandService.ProcessResponse(deviceId, payload);
|
|
break;
|
|
case "status":
|
|
await _deviceService.UpdateConnectionStatus(deviceId, payload);
|
|
break;
|
|
case "alerts":
|
|
await _alertService.ProcessAlert(deviceId, payload);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 8.5 REST API Endpoints
|
|
|
|
### Devices
|
|
|
|
```
|
|
GET /api/devices List all devices (with filtering, pagination)
|
|
GET /api/devices/{deviceId} Get device details
|
|
PUT /api/devices/{deviceId} Update device (name, description, group, tags)
|
|
DELETE /api/devices/{deviceId} Remove device from console
|
|
|
|
GET /api/devices/{deviceId}/status Get current device status
|
|
POST /api/devices/{deviceId}/command Send command to device
|
|
GET /api/devices/{deviceId}/commands Command history
|
|
```
|
|
|
|
### Telemetry
|
|
|
|
```
|
|
GET /api/devices/{deviceId}/telemetry Latest telemetry
|
|
GET /api/devices/{deviceId}/telemetry/history Historical data (with time range, aggregation)
|
|
GET /api/devices/{deviceId}/telemetry/export Export as CSV
|
|
```
|
|
|
|
### Firmware
|
|
|
|
```
|
|
GET /api/firmware List all firmware versions
|
|
POST /api/firmware Upload new firmware version
|
|
GET /api/firmware/{version} Get firmware details
|
|
DELETE /api/firmware/{version} Delete firmware version
|
|
GET /api/firmware/download/{version} Download firmware binary (used by devices)
|
|
GET /api/firmware/check Check for update (used by devices)
|
|
POST /api/firmware/deploy Deploy firmware to device(s)
|
|
```
|
|
|
|
### Alerts
|
|
|
|
```
|
|
GET /api/alerts List alerts (with filtering)
|
|
GET /api/devices/{deviceId}/alerts Device-specific alerts
|
|
PUT /api/alerts/{id}/acknowledge Acknowledge alert
|
|
```
|
|
|
|
### Configuration
|
|
|
|
```
|
|
GET /api/devices/{deviceId}/config Get device configuration
|
|
POST /api/devices/{deviceId}/config Push configuration to device
|
|
GET /api/devices/{deviceId}/config/wifi/scan Trigger WiFi scan on device
|
|
```
|
|
|
|
### Groups
|
|
|
|
```
|
|
GET /api/groups List device groups
|
|
POST /api/groups Create group
|
|
PUT /api/groups/{id} Update group
|
|
DELETE /api/groups/{id} Delete group
|
|
POST /api/groups/{id}/command Send command to all devices in group
|
|
POST /api/groups/{id}/deploy Deploy firmware to group
|
|
```
|
|
|
|
### Auth
|
|
|
|
```
|
|
POST /api/auth/login Login, get JWT token
|
|
POST /api/auth/register Register new user (admin only)
|
|
POST /api/auth/refresh Refresh JWT token
|
|
GET /api/auth/me Get current user info
|
|
```
|
|
|
|
### Dashboard
|
|
|
|
```
|
|
GET /api/dashboard/summary Fleet summary (total, online, offline, alerts)
|
|
GET /api/dashboard/stats Aggregate statistics
|
|
```
|
|
|
|
## 8.6 Command Service Flow
|
|
|
|
### Sending a Command
|
|
|
|
```
|
|
1. Frontend calls POST /api/devices/{deviceId}/command
|
|
Body: { "command": "wifi_scan" }
|
|
2. CommandService generates request_id, saves to DB (status: pending)
|
|
3. CommandService publishes to MQTT: acuvim/{deviceId}/cmd
|
|
4. Device receives command, processes, publishes to acuvim/{deviceId}/resp
|
|
5. MqttBridge receives response
|
|
6. CommandService matches request_id, updates DB (status: success, response: {...})
|
|
7. SignalR notifies frontend of command completion
|
|
8. If no response in 60 seconds: mark as timeout
|
|
```
|
|
|
|
### Command Timeout
|
|
|
|
```csharp
|
|
public class CommandTimeoutService : BackgroundService
|
|
{
|
|
// Every 30 seconds, check for commands where:
|
|
// status == 'sent' AND sent_at < (now - 60 seconds)
|
|
// Mark as 'timeout'
|
|
}
|
|
```
|
|
|
|
## 8.7 Health Monitor Service
|
|
|
|
### `HealthMonitorService.cs`
|
|
|
|
Background service that monitors device health based on heartbeats:
|
|
|
|
```csharp
|
|
public class HealthMonitorService : BackgroundService
|
|
{
|
|
// Every 30 seconds:
|
|
// - Check all devices where last_heartbeat < (now - 3 * heartbeat_interval)
|
|
// -> Mark as "degraded"
|
|
// - Check all devices where last_heartbeat < (now - 5 * heartbeat_interval)
|
|
// -> Mark as "offline"
|
|
// - Optionally send notification (email, webhook) for offline devices
|
|
|
|
public async Task ProcessHeartbeat(string deviceId, string payload)
|
|
{
|
|
// Parse heartbeat JSON
|
|
// Update device: status = "online", last_heartbeat, firmware_version,
|
|
// connection_type, signal_strength, ip_address, boot_count
|
|
// Update health metrics
|
|
// Check for anomalies (high error rate, low memory, brownout resets)
|
|
// Notify frontend via SignalR
|
|
}
|
|
}
|
|
```
|
|
|
|
## 8.8 Firmware Service
|
|
|
|
### Upload Flow
|
|
|
|
```csharp
|
|
public async Task<FirmwareVersion> Upload(IFormFile file, string version,
|
|
string releaseNotes, bool mandatory)
|
|
{
|
|
// 1. Validate file is a valid ESP32 binary (check magic bytes)
|
|
// 2. Calculate SHA256 checksum
|
|
// 3. Save to /firmware/{version}/firmware.bin
|
|
// 4. Create DB record
|
|
// 5. Return firmware version info
|
|
}
|
|
```
|
|
|
|
### Deployment Flow
|
|
|
|
```csharp
|
|
public async Task Deploy(string version, List<string> deviceIds)
|
|
{
|
|
var firmware = await GetVersion(version);
|
|
|
|
foreach (var deviceId in deviceIds)
|
|
{
|
|
await _commandService.Send(deviceId, "ota_update", new {
|
|
url = $"{_baseUrl}/api/firmware/download/{version}",
|
|
version = firmware.Version,
|
|
checksum = firmware.Checksum
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
### Device Check Endpoint
|
|
|
|
```csharp
|
|
// GET /api/firmware/check?device_id=X¤t_version=Y&hardware=Z
|
|
public async Task<FirmwareCheckResponse> Check(string deviceId,
|
|
string currentVersion, string hardware)
|
|
{
|
|
var latest = await _db.FirmwareVersions
|
|
.Where(f => f.Active)
|
|
.Where(f => f.Hardware == null || f.Hardware == hardware)
|
|
.OrderByDescending(f => f.CreatedAt)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (latest != null && IsNewer(latest.Version, currentVersion))
|
|
{
|
|
return new FirmwareCheckResponse
|
|
{
|
|
UpdateAvailable = true,
|
|
Version = latest.Version,
|
|
Url = $"{_baseUrl}/api/firmware/download/{latest.Version}",
|
|
Size = latest.FileSize,
|
|
Checksum = latest.Checksum,
|
|
ReleaseNotes = latest.ReleaseNotes,
|
|
Mandatory = latest.Mandatory
|
|
};
|
|
}
|
|
|
|
return new FirmwareCheckResponse { UpdateAvailable = false };
|
|
}
|
|
```
|
|
|
|
## 8.9 Real-Time Updates (SignalR)
|
|
|
|
### `DeviceHub.cs`
|
|
|
|
SignalR hub for pushing real-time updates to the frontend:
|
|
|
|
```csharp
|
|
public class DeviceHub : Hub
|
|
{
|
|
// Methods the server calls on clients:
|
|
// - DeviceStatusChanged(deviceId, status)
|
|
// - TelemetryReceived(deviceId, data)
|
|
// - HeartbeatReceived(deviceId, data)
|
|
// - AlertCreated(deviceId, alert)
|
|
// - CommandCompleted(deviceId, requestId, response)
|
|
// - DeviceRegistered(device)
|
|
}
|
|
```
|
|
|
|
The frontend subscribes to these events for live dashboard updates without polling.
|
|
|
|
## 8.10 Authentication
|
|
|
|
### JWT-Based Auth
|
|
|
|
- Users register/login to get a JWT token
|
|
- Token included in `Authorization: Bearer <token>` header
|
|
- Token expiry: 24 hours
|
|
- Refresh token: 7 days
|
|
- Roles: `Admin`, `Operator`, `Viewer`
|
|
|
|
### Device API Key
|
|
|
|
Devices authenticate firmware downloads using their device_id as an API key or via MQTT credentials. The firmware check/download endpoints require either a valid JWT or a valid device_id.
|
|
|
|
## 8.11 Docker Deployment
|
|
|
|
### `docker-compose.yml`
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
console:
|
|
build: .
|
|
ports:
|
|
- "5000:5000"
|
|
environment:
|
|
- ConnectionStrings__DefaultConnection=Host=db;Database=acuvim;Username=acuvim;Password=secret
|
|
- Mqtt__Broker=mqtt
|
|
- Mqtt__Port=1883
|
|
depends_on:
|
|
- db
|
|
- mqtt
|
|
volumes:
|
|
- firmware-data:/app/firmware
|
|
|
|
db:
|
|
image: postgres:16
|
|
environment:
|
|
- POSTGRES_DB=acuvim
|
|
- POSTGRES_USER=acuvim
|
|
- POSTGRES_PASSWORD=secret
|
|
volumes:
|
|
- postgres-data:/var/lib/postgresql/data
|
|
ports:
|
|
- "5432:5432"
|
|
|
|
mqtt:
|
|
image: eclipse-mosquitto:2
|
|
ports:
|
|
- "1883:1883"
|
|
- "9001:9001"
|
|
volumes:
|
|
- mosquitto-data:/mosquitto/data
|
|
- ./mosquitto.conf:/mosquitto/config/mosquitto.conf
|
|
|
|
volumes:
|
|
postgres-data:
|
|
mosquitto-data:
|
|
firmware-data:
|
|
```
|
|
|
|
## 8.12 Configuration
|
|
|
|
### `appsettings.json`
|
|
|
|
```json
|
|
{
|
|
"ConnectionStrings": {
|
|
"DefaultConnection": "Host=localhost;Database=acuvim;Username=acuvim;Password=secret"
|
|
},
|
|
"Mqtt": {
|
|
"Broker": "localhost",
|
|
"Port": 1883,
|
|
"Username": "console",
|
|
"Password": "",
|
|
"TopicPrefix": "acuvim",
|
|
"ClientId": "acuvim-console"
|
|
},
|
|
"Firmware": {
|
|
"StoragePath": "./firmware",
|
|
"BaseUrl": "https://console.example.com"
|
|
},
|
|
"Jwt": {
|
|
"Secret": "your-secret-key-min-32-chars-long-here",
|
|
"Issuer": "Tau.Acuvim.Console",
|
|
"ExpiryHours": 24
|
|
},
|
|
"HealthMonitor": {
|
|
"CheckIntervalSeconds": 30,
|
|
"DegradedThresholdMultiplier": 3,
|
|
"OfflineThresholdMultiplier": 5
|
|
},
|
|
"Telemetry": {
|
|
"RetentionDays": 365,
|
|
"CleanupIntervalHours": 24
|
|
}
|
|
}
|
|
```
|
|
|
|
## 8.13 Testing & Validation
|
|
|
|
| Test | Method | Pass Criteria |
|
|
|------|--------|---------------|
|
|
| API starts | `dotnet run` | Swagger UI at /swagger |
|
|
| DB migration | `dotnet ef database update` | Tables created |
|
|
| MQTT connection | Start broker, run console | MQTT connected, subscribed |
|
|
| Device registration | Publish registration via MQTT | Device appears in DB |
|
|
| Telemetry ingest | Publish telemetry via MQTT | Record saved, queryable via API |
|
|
| Heartbeat processing | Publish heartbeat | Device status updated |
|
|
| Health monitor | Stop device heartbeats | Device marked degraded then offline |
|
|
| Send command | POST /api/devices/{id}/command | Command published to MQTT, response received |
|
|
| Firmware upload | POST /api/firmware with binary | File saved, DB record created |
|
|
| Firmware check | GET /api/firmware/check | Correct update response |
|
|
| Firmware download | GET /api/firmware/download/{v} | Binary downloaded |
|
|
| Deploy firmware | POST /api/firmware/deploy | OTA command sent to devices |
|
|
| Auth login | POST /api/auth/login | JWT token returned |
|
|
| Auth protected | GET /api/devices without token | 401 Unauthorized |
|
|
| SignalR | Connect from browser | Real-time events received |
|
|
| Docker | `docker-compose up` | All services start, communicate |
|
|
| Alert processing | Publish alert via MQTT | Alert saved, queryable |
|
|
|
|
## 8.14 Phase 8 Completion Criteria
|
|
|
|
- [ ] .NET 10 Web API project builds and runs
|
|
- [ ] PostgreSQL schema migrated, all tables created
|
|
- [ ] MQTT bridge subscribes to all device topics
|
|
- [ ] Device registration works (MQTT -> DB)
|
|
- [ ] Telemetry ingestion works (MQTT -> DB -> API)
|
|
- [ ] Heartbeat processing updates device status
|
|
- [ ] Health monitor marks devices degraded/offline
|
|
- [ ] Command send/receive flow complete
|
|
- [ ] Firmware upload, check, and download working
|
|
- [ ] Firmware deployment (push OTA via MQTT)
|
|
- [ ] Alert processing and querying
|
|
- [ ] JWT authentication on all endpoints
|
|
- [ ] SignalR real-time updates
|
|
- [ ] Docker Compose deployment working
|
|
- [ ] API documented via Swagger/OpenAPI
|
|
|
|
---
|
|
|
|
**Previous Phase:** [Phase 7 — Heartbeat, Health & Registration](acuvim-spec-07.md)
|
|
**Next Phase:** [Phase 9 — Console Application Frontend](acuvim-spec-09.md)
|