using Peak.Can.Basic; using IOModuleTestBlazor.Models; namespace IOModuleTestBlazor.Services; public class CanService : ICanService { private PcanChannel _channel; private readonly List _filters = []; private readonly List _bitmasks = []; private readonly Dictionary _latestMessages = []; private readonly object _lock = new(); public bool IsConnected { get; private set; } public string ChannelName => IsConnected ? _channel.ToString() : ""; public Bitrate CurrentBitrate { get; private set; } private (PcanChannel Channel, Bitrate Bitrate)? _pendingReinit; public IReadOnlyDictionary GetLatestMessages() { lock (_lock) return new Dictionary(_latestMessages); } public void UpdateLatestMessage(CanMessageDto dto) { lock (_lock) _latestMessages[dto.Id] = dto; } // ── Filters ─────────────────────────────────────────────────────────────── public IReadOnlyList Filters { get { lock (_lock) return _filters.ToList(); } } public CanFilter AddFilter(CanFilter filter) { lock (_lock) _filters.Add(filter); return filter; } public bool RemoveFilter(Guid id) { lock (_lock) return _filters.RemoveAll(f => f.Id == id) > 0; } public void ClearFilters() { lock (_lock) _filters.Clear(); } // ── Bitmasks ────────────────────────────────────────────────────────────── public IReadOnlyList Bitmasks { get { lock (_lock) return _bitmasks.ToList(); } } public CanBitmask AddBitmask(CanBitmask bitmask) { lock (_lock) _bitmasks.Add(bitmask); return bitmask; } public bool RemoveBitmask(Guid id) { lock (_lock) return _bitmasks.RemoveAll(b => b.Id == id) > 0; } public void ClearBitmasks() { lock (_lock) _bitmasks.Clear(); } // ── Helpers ─────────────────────────────────────────────────────────────── public bool PassesFilter(uint messageId) { lock (_lock) { if (_filters.Count == 0) return true; return _filters.Any(f => (messageId & f.Mask) == (f.MessageId & f.Mask)); } } public IReadOnlyDictionary ExtractSignals(uint messageId, byte[] data) { List applicable; lock (_lock) applicable = _bitmasks.Where(b => b.MessageId == messageId).ToList(); if (applicable.Count == 0) return new Dictionary(); Span padded = stackalloc byte[8]; var len = Math.Min(data.Length, 8); for (int i = 0; i < len; i++) padded[i] = data[i]; ulong raw = 0; for (int i = 0; i < 8; i++) raw = (raw << 8) | padded[i]; var signals = new Dictionary(applicable.Count); foreach (var bm in applicable) { ulong masked = (raw & bm.DataMask) >> bm.RightShift; signals[bm.SignalName] = masked * bm.Scale + bm.Offset; } return signals; } // ── PCAN passthrough ────────────────────────────────────────────────────── public IReadOnlyList GetAvailableChannels() { var available = new List(); foreach (var ch in Enum.GetValues() .Where(c => c.ToString().StartsWith("Usb", StringComparison.OrdinalIgnoreCase)) .OrderBy(c => c.ToString())) { var result = Api.GetValue(ch, PcanParameter.ChannelCondition, out uint condition); if (result == PcanStatus.OK && condition != 0) available.Add(ch); } return available; } public void RequestReinitialize(PcanChannel channel, Bitrate bitrate) { lock (_lock) _pendingReinit = (channel, bitrate); } public bool TryConsumePendingReinit(out PcanChannel channel, out Bitrate bitrate) { lock (_lock) { if (_pendingReinit.HasValue) { channel = _pendingReinit.Value.Channel; bitrate = _pendingReinit.Value.Bitrate; _pendingReinit = null; return true; } channel = default; bitrate = default; return false; } } public void Initialize(PcanChannel channel, Bitrate bitrate) { _channel = channel; var result = Api.Initialize(channel, bitrate); if (result != PcanStatus.OK) throw new InvalidOperationException($"CAN init failed: {GetErrorText(result)}"); // Optimize PCAN settings for low latency try { // Try to set receive event to 0 to disable buffering delays Api.SetValue(channel, PcanParameter.ReceiveEvent, 0u); } catch (Exception) { // Buffer optimization failed but connection still works } IsConnected = true; CurrentBitrate = bitrate; } public void Uninitialize() { Api.Uninitialize(_channel); IsConnected = false; } public PcanStatus Read(out PcanMessage msg, out ulong timestamp) => Api.Read(_channel, out msg, out timestamp); public PcanStatus Write(PcanMessage msg) { var status = Api.Write(_channel, msg); if (status == PcanStatus.OK) { var timestampUs = (ulong)(DateTime.UtcNow.Ticks / 10); var data = new byte[msg.DLC]; Array.Copy(msg.Data, data, msg.DLC); UpdateLatestMessage(new CanMessageDto( msg.ID, data, msg.DLC, timestampUs, msg.MsgType == MessageType.Extended, null)); } return status; } private static string GetErrorText(PcanStatus status) { try { Api.GetErrorText(status, out var text); return text; } catch (PcanBasicException) { return status.ToString(); } } }