You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
4.2 KiB
132 lines
4.2 KiB
using Peak.Can.Basic; |
|
using IOModuleTestBlazor.Models; |
|
|
|
namespace IOModuleTestBlazor.Services; |
|
|
|
public class CanService : ICanService |
|
{ |
|
private PcanChannel _channel; |
|
private readonly List<CanFilter> _filters = []; |
|
private readonly List<CanBitmask> _bitmasks = []; |
|
private readonly object _lock = new(); |
|
|
|
public bool IsConnected { get; private set; } |
|
public string ChannelName => _channel.ToString(); |
|
|
|
public event Action<CanMessageDto>? MessageReceived; |
|
|
|
public void PublishMessage(CanMessageDto dto) => MessageReceived?.Invoke(dto); |
|
|
|
// ── Filters ─────────────────────────────────────────────────────────────── |
|
|
|
public IReadOnlyList<CanFilter> 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<CanBitmask> 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<string, double> ExtractSignals(uint messageId, byte[] data) |
|
{ |
|
List<CanBitmask> applicable; |
|
lock (_lock) |
|
applicable = _bitmasks.Where(b => b.MessageId == messageId).ToList(); |
|
|
|
if (applicable.Count == 0) |
|
return new Dictionary<string, double>(); |
|
|
|
Span<byte> 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<string, double>(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(); } |
|
} |
|
}
|
|
|