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 object _lock = new(); public bool IsConnected { get; private set; } public string ChannelName => _channel.ToString(); public event Action? MessageReceived; public void PublishMessage(CanMessageDto dto) => MessageReceived?.Invoke(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 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)}"); IsConnected = true; } 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) => Api.Write(_channel, msg); private static string GetErrorText(PcanStatus status) { try { Api.GetErrorText(status, out var text); return text; } catch (PcanBasicException) { return status.ToString(); } } }