No bitmasks configured. Add one to extract named signal values from a message's data bytes. Formula: physicalValue = ((rawUint64 & DataMask) >> RightShift) × Scale + Offset
}
else
{
Msg ID
Signal
DataMask (64-bit)
Shift
Scale
Offset
Unit
@foreach (var b in bitmasks)
{
@($"0x{b.MessageId:X3}")
@b.SignalName
@($"0x{b.DataMask:X16}")
@b.RightShift
@b.Scale
@b.Offset
@b.Unit
}
}
@if (bitmaskError != null)
{
@bitmaskError
}
@if (bitmasks.Count > 0)
{
}
@code {
// ── Connection form ───────────────────────────────────────────────────────
private List availableChannels = [];
private string selectedChannel = "";
private string selectedBitrate = Bitrate.Pcan500.ToString();
private bool showConnectionForm;
private bool isConnecting;
private string? connectError;
private static readonly (Bitrate Value, string Label)[] BitrateOptions =
[
(Bitrate.Pcan1000, "1000 kbps"),
(Bitrate.Pcan800, "800 kbps"),
(Bitrate.Pcan500, "500 kbps"),
(Bitrate.Pcan250, "250 kbps"),
(Bitrate.Pcan125, "125 kbps"),
(Bitrate.Pcan100, "100 kbps"),
(Bitrate.Pcan50, "50 kbps"),
(Bitrate.Pcan20, "20 kbps"),
(Bitrate.Pcan10, "10 kbps"),
(Bitrate.Pcan5, "5 kbps"),
];
// ── Data ──────────────────────────────────────────────────────────────────
private readonly List messages = [];
private readonly Dictionary latestMessages = new();
private readonly Dictionary updateCounts = new();
private string msgTab = "stream";
private const int MaxMessages = 200;
private List filters = [];
private List bitmasks = [];
// ── Filter form ───────────────────────────────────────────────────────────
private string newFilterId = "";
private string newFilterMask = "7FF";
private string newFilterDesc = "";
private string? filterError;
// ── Bitmask form ──────────────────────────────────────────────────────────
private string newBmMsgId = "";
private string newBmName = "";
private string newBmMask = "";
private int newBmShift = 0;
private double newBmScale = 1.0;
private double newBmOffset = 0.0;
private string newBmUnit = "";
private string? bitmaskError;
// ── Send form ─────────────────────────────────────────────────────────────
private string sendIdHex = "";
private string sendDataHex = "";
private bool sendExtended;
private string? sendFeedback;
private bool sendOk;
// ── Lifecycle ─────────────────────────────────────────────────────────────
protected override void OnInitialized()
{
filters = CanService.Filters.ToList();
bitmasks = CanService.Bitmasks.ToList();
CanService.MessageReceived += OnMessageReceived;
ScanChannels();
if (CanService.IsConnected)
{
selectedChannel = CanService.ChannelName;
selectedBitrate = CanService.CurrentBitrate.ToString();
}
else
{
showConnectionForm = true;
}
}
private void OnMessageReceived(CanMessageDto msg)
{
lock (messages)
{
messages.Insert(0, msg);
if (messages.Count > MaxMessages)
messages.RemoveAt(messages.Count - 1);
}
lock (latestMessages)
{
if (!latestMessages.TryGetValue(msg.Id, out var existing) ||
!msg.Data.SequenceEqual(existing.Data))
{
latestMessages[msg.Id] = msg;
updateCounts[msg.Id] = updateCounts.GetValueOrDefault(msg.Id) + 1;
}
}
InvokeAsync(StateHasChanged);
}
public void Dispose() => CanService.MessageReceived -= OnMessageReceived;
// ── Connection ────────────────────────────────────────────────────────────
private void ScanChannels()
{
availableChannels = CanService.GetAvailableChannels().ToList();
if (availableChannels.Count > 0 &&
!availableChannels.Any(c => c.ToString() == selectedChannel))
selectedChannel = availableChannels[0].ToString();
}
private async Task Connect()
{
connectError = null;
if (!Enum.TryParse(selectedChannel, out var channel))
{ connectError = "Please select a channel."; return; }
if (!Enum.TryParse(selectedBitrate, out var bitrate))
{ connectError = "Please select a bitrate."; return; }
isConnecting = true;
CanService.RequestReinitialize(channel, bitrate);
// Poll until the worker completes the reinit (typically < 50 ms)
for (int i = 0; i < 20; i++)
{
await Task.Delay(100);
if (CanService.IsConnected) break;
}
isConnecting = false;
if (CanService.IsConnected)
showConnectionForm = false;
else
connectError = "Connection failed. Check the adapter is plugged in and try again.";
StateHasChanged();
}
private static string BitrateLabel(Bitrate b) => b switch
{
Bitrate.Pcan1000 => "1000 kbps",
Bitrate.Pcan800 => "800 kbps",
Bitrate.Pcan500 => "500 kbps",
Bitrate.Pcan250 => "250 kbps",
Bitrate.Pcan125 => "125 kbps",
Bitrate.Pcan100 => "100 kbps",
Bitrate.Pcan50 => "50 kbps",
Bitrate.Pcan20 => "20 kbps",
Bitrate.Pcan10 => "10 kbps",
Bitrate.Pcan5 => "5 kbps",
_ => b.ToString()
};
// ── Messages ──────────────────────────────────────────────────────────────
private void ClearMessages()
{
lock (messages) messages.Clear();
lock (latestMessages) { latestMessages.Clear(); updateCounts.Clear(); }
}
// ── Filters ───────────────────────────────────────────────────────────────
private void AddFilter()
{
filterError = null;
if (!TryParseHex32(newFilterId, out uint msgId))
{ filterError = "Invalid ID — enter a hex value e.g. 100"; return; }
if (!TryParseHex32(newFilterMask, out uint mask))
{ filterError = "Invalid mask — enter a hex value e.g. 7FF"; return; }
var filter = new CanFilter { MessageId = msgId, Mask = mask, Description = newFilterDesc };
CanService.AddFilter(filter);
filters = CanService.Filters.ToList();
newFilterId = ""; newFilterMask = "7FF"; newFilterDesc = "";
}
private void RemoveFilter(Guid id)
{
CanService.RemoveFilter(id);
filters = CanService.Filters.ToList();
}
private void ClearFilters()
{
CanService.ClearFilters();
filters = [];
}
// ── Bitmasks ──────────────────────────────────────────────────────────────
private void AddBitmask()
{
bitmaskError = null;
if (!TryParseHex32(newBmMsgId, out uint msgId))
{ bitmaskError = "Invalid Msg ID"; return; }
if (string.IsNullOrWhiteSpace(newBmName))
{ bitmaskError = "Signal name is required"; return; }
if (!TryParseHex64(newBmMask, out ulong mask))
{ bitmaskError = "Invalid 64-bit mask — enter hex e.g. 00FF000000000000"; return; }
var bitmask = new CanBitmask
{
MessageId = msgId,
SignalName = newBmName.Trim(),
DataMask = mask,
RightShift = newBmShift,
Scale = newBmScale,
Offset = newBmOffset,
Unit = string.IsNullOrWhiteSpace(newBmUnit) ? null : newBmUnit
};
CanService.AddBitmask(bitmask);
bitmasks = CanService.Bitmasks.ToList();
newBmMsgId = ""; newBmName = ""; newBmMask = "";
newBmShift = 0; newBmScale = 1.0; newBmOffset = 0.0; newBmUnit = "";
}
private void RemoveBitmask(Guid id)
{
CanService.RemoveBitmask(id);
bitmasks = CanService.Bitmasks.ToList();
}
private void ClearBitmasks()
{
CanService.ClearBitmasks();
bitmasks = [];
}
// ── Send ──────────────────────────────────────────────────────────────────
private void SendMessage()
{
sendFeedback = null;
if (!TryParseHex32(sendIdHex, out uint id))
{ sendFeedback = "Invalid ID"; sendOk = false; return; }
byte[] data;
try
{
var hex = (sendDataHex ?? "").Replace(" ", "");
data = hex.Length > 0 ? Convert.FromHexString(hex) : [];
}
catch { sendFeedback = "Invalid hex data"; sendOk = false; return; }
if (data.Length > 8)
{ sendFeedback = "Max 8 bytes"; sendOk = false; return; }
var msg = new PcanMessage
{
ID = id,
MsgType = sendExtended ? MessageType.Extended : MessageType.Standard,
DLC = (byte)data.Length,
Data = new byte[8]
};
data.CopyTo(msg.Data, 0);
var result = CanService.Write(msg);
sendOk = result == PcanStatus.OK;
sendFeedback = sendOk ? "Sent!" : $"Error: {result}";
}
// ── Helpers ───────────────────────────────────────────────────────────────
private List GetLatestRows()
{
lock (latestMessages)
return latestMessages.Values.OrderBy(m => m.Id).ToList();
}
private static string SignalsText(CanMessageDto m)
{
if (m.Signals is null || m.Signals.Count == 0) return "–";
return string.Join(", ", m.Signals.Select(kv => $"{kv.Key}={kv.Value:G4}"));
}
private static bool TryParseHex32(string? s, out uint value)
{
value = 0;
if (string.IsNullOrWhiteSpace(s)) return false;
var hex = s.Trim();
if (hex.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) hex = hex[2..];
return uint.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out value);
}
private static bool TryParseHex64(string? s, out ulong value)
{
value = 0;
if (string.IsNullOrWhiteSpace(s)) return false;
var hex = s.Trim();
if (hex.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) hex = hex[2..];
return ulong.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out value);
}
}