Blazor app with can interface
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

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(); }
}
}