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>
97 lines
3.1 KiB
C#
97 lines
3.1 KiB
C#
using System.Text.Json;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Tau.Acuvim.Console.Data;
|
|
using Tau.Acuvim.Console.Models;
|
|
|
|
namespace Tau.Acuvim.Console.Services;
|
|
|
|
public class AlertService
|
|
{
|
|
private readonly AppDbContext _db;
|
|
private readonly ILogger<AlertService> _logger;
|
|
|
|
public AlertService(AppDbContext db, ILogger<AlertService> logger)
|
|
{
|
|
_db = db;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task ProcessAlertAsync(string deviceId, string payload)
|
|
{
|
|
try
|
|
{
|
|
using var doc = JsonDocument.Parse(payload);
|
|
var root = doc.RootElement;
|
|
|
|
var alert = new Alert
|
|
{
|
|
DeviceId = deviceId,
|
|
AlertType = root.GetProperty("alert").GetString() ?? "unknown",
|
|
Severity = root.GetProperty("severity").GetString() ?? "info",
|
|
Message = root.GetProperty("message").GetString() ?? "",
|
|
Value = root.TryGetProperty("value", out var v) ? v.GetDouble() : null,
|
|
Threshold = root.TryGetProperty("threshold", out var t) ? t.GetDouble() : null,
|
|
Metadata = JsonDocument.Parse(payload)
|
|
};
|
|
|
|
_db.Alerts.Add(alert);
|
|
await _db.SaveChangesAsync();
|
|
|
|
_logger.LogWarning("Alert {Type} from {DeviceId}: {Message}",
|
|
alert.AlertType, deviceId, alert.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to process alert from {DeviceId}", deviceId);
|
|
}
|
|
}
|
|
|
|
public async Task<List<Alert>> GetAllAsync(string? deviceId, string? severity,
|
|
bool? acknowledged, int page, int pageSize)
|
|
{
|
|
var query = _db.Alerts.AsQueryable();
|
|
|
|
if (!string.IsNullOrEmpty(deviceId))
|
|
query = query.Where(a => a.DeviceId == deviceId);
|
|
if (!string.IsNullOrEmpty(severity))
|
|
query = query.Where(a => a.Severity == severity);
|
|
if (acknowledged.HasValue)
|
|
query = query.Where(a => a.Acknowledged == acknowledged.Value);
|
|
|
|
return await query
|
|
.OrderByDescending(a => a.CreatedAt)
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<List<Alert>> GetByDeviceAsync(string deviceId, int page, int pageSize)
|
|
{
|
|
return await _db.Alerts
|
|
.Where(a => a.DeviceId == deviceId)
|
|
.OrderByDescending(a => a.CreatedAt)
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<bool> AcknowledgeAsync(long id, string acknowledgedBy)
|
|
{
|
|
var alert = await _db.Alerts.FindAsync(id);
|
|
if (alert == null) return false;
|
|
|
|
alert.Acknowledged = true;
|
|
alert.AcknowledgedBy = acknowledgedBy;
|
|
alert.AcknowledgedAt = DateTime.UtcNow;
|
|
await _db.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
public async Task<int> GetActiveCountAsync()
|
|
{
|
|
return await _db.Alerts
|
|
.Where(a => !a.Acknowledged && a.ResolvedAt == null)
|
|
.CountAsync();
|
|
}
|
|
}
|