using System.Collections.Concurrent; using RobotNet10.CANOpen.Enums; using RobotNet10.CANOpen.Interfaces; using RobotNet10.CANOpen.Models; namespace RobotNet10.CANOpen.Services; /// /// Emergency Monitor - theo dõi emergency messages từ các nodes /// public class EmergencyMonitor : IDisposable { private readonly ICanBus _canBus; private readonly ConcurrentDictionary _lastEmergencies; private bool _disposed; public event EventHandler? EmergencyReceived; public EmergencyMonitor(ICanBus canBus) { _canBus = canBus ?? throw new ArgumentNullException(nameof(canBus)); _lastEmergencies = new ConcurrentDictionary(); _canBus.FrameReceived += OnFrameReceived; } /// /// Lấy emergency message cuối cùng từ một node /// public EmergencyMessage? GetLastEmergency(byte nodeId) { return _lastEmergencies.TryGetValue(nodeId, out var emergency) ? emergency : null; } /// /// Xóa emergency history của một node /// public void ClearEmergency(byte nodeId) { _lastEmergencies.TryRemove(nodeId, out _); } /// /// Xóa tất cả emergency history /// public void ClearAll() { _lastEmergencies.Clear(); } private void OnFrameReceived(object? sender, CanFrameReceivedEventArgs e) { // EMERGENCY COB-ID: 0x80 + NodeID uint baseEmergencyCobId = (uint)CanMessageType.Emergency; if (e.CanId >= baseEmergencyCobId && e.CanId < baseEmergencyCobId + 0x7F) { var emergency = EmergencyMessage.FromCanFrame(e.CanId, e.Data, e.Timestamp); // Thread-safe update using ConcurrentDictionary _lastEmergencies[emergency.NodeId] = emergency; EmergencyReceived?.Invoke(this, new EmergencyReceivedEventArgs(emergency)); } } public void Dispose() { if (_disposed) return; _canBus.FrameReceived -= OnFrameReceived; _lastEmergencies.Clear(); _disposed = true; GC.SuppressFinalize(this); } } /// /// Event args cho Emergency received /// public class EmergencyReceivedEventArgs(EmergencyMessage Emergency) : EventArgs { /// /// Helper để format emergency message cho logging /// public string GetDescription() { var errorClass = ((ushort)Emergency.ErrorCode) >> 8; var errorType = GetErrorType(errorClass); // Error Register bits (DS301): // Bit 0: Generic Error // Bit 1: Current // Bit 2: Voltage // Bit 3: Temperature // Bit 4: Communication Error // Bit 5: Device Profile Specific // Bit 6: Reserved // Bit 7: Manufacturer Specific bool isGeneric = (Emergency.ErrorRegister & 0x01) != 0; bool isCurrent = (Emergency.ErrorRegister & 0x02) != 0; bool isVoltage = (Emergency.ErrorRegister & 0x04) != 0; bool isTemp = (Emergency.ErrorRegister & 0x08) != 0; return $"Node {Emergency.NodeId}: {errorType} - Error 0x{(ushort)Emergency.ErrorCode:X4}" + $" (Generic: {isGeneric}, Current: {isCurrent}, " + $"Voltage: {isVoltage}, Temp: {isTemp})"; } private static string GetErrorType(int errorClass) { return errorClass switch { 0x00 => "Error Reset or No Error", 0x10 => "Generic Error", 0x20 => "Current Error", 0x21 => "Current Device Input Side", 0x22 => "Current Inside Device", 0x23 => "Current Device Output Side", 0x30 => "Voltage Error", 0x31 => "Mains Voltage", 0x32 => "Voltage Inside Device", 0x33 => "Output Voltage", 0x40 => "Temperature Error", 0x41 => "Ambient Temperature", 0x42 => "Device Temperature", 0x50 => "Device Hardware Error", 0x60 => "Device Software Error", 0x61 => "Internal Software Error", 0x62 => "User Software Error", 0x63 => "Data Set Error", 0x70 => "Additional Modules Error", 0x80 => "Monitoring Error", 0x81 => "Communication Error", 0x82 => "Protocol Error", 0x90 => "External Error", 0xF0 => "Additional Functions Error", 0xFF => "Device Specific Error", _ => "Unknown Error" }; } }