Files
CapMachine/CapMachine.Wpf/Services/ZlgCanDriveService.cs
2026-02-06 12:34:34 +08:00

508 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using CapMachine.Model.CANLIN;
using CapMachine.Wpf.CanDrive;
using CapMachine.Wpf.CanDrive.ZlgCan;
using CapMachine.Wpf.Dtos;
using ImTools;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace CapMachine.Wpf.Services
{
/// <summary>
/// ZLG CAN/CANFD 驱动服务(共享设备句柄)。
/// </summary>
public sealed class ZlgCanDriveService : BindableBase
{
private readonly ILogService _log;
/// <summary>
/// 共享的 ZLG 驱动实例CAN/CANFD/LIN
/// </summary>
public ZlgCanFd200uDriver Driver { get; }
/// <summary>
/// 当前选中的配置程序(沿用原有 FreeSql 模型)。
/// </summary>
public CanLinConfigPro? SelectedCanLinConfigPro { get; set; }
/// <summary>
/// Dbc 消息集合(用于 UI 绑定)。
/// </summary>
public ObservableCollection<CanDbcModel> ListCanDbcModel { get; private set; } = new ObservableCollection<CanDbcModel>();
private bool _dbcParserState;
/// <summary>
/// DBC 解析状态。
/// </summary>
public bool DbcParserState
{
get { return _dbcParserState; }
private set { _dbcParserState = value; RaisePropertyChanged(); }
}
private bool _openState;
/// <summary>
/// 设备打开状态。
/// </summary>
public bool OpenState
{
get { return _openState; }
private set { _openState = value; RaisePropertyChanged(); }
}
private ZlgCanMode _mode;
/// <summary>
/// CAN 模式单选CAN 或 CANFD
/// </summary>
public ZlgCanMode Mode
{
get { return _mode; }
set { _mode = value; RaisePropertyChanged(); }
}
/// <summary>
/// 发送使能(与 UI 的调度表使能语义对齐)。
/// </summary>
public bool SchEnable
{
get { return Driver.SchEnable; }
set { Driver.SchEnable = value; RaisePropertyChanged(); }
}
/// <summary>
/// 是否启用事件驱动发送。
/// </summary>
public bool IsCycleSend
{
get { return Driver.IsCycleSend; }
set { Driver.IsCycleSend = value; RaisePropertyChanged(); }
}
/// <summary>
/// 是否正在循环接收(对齐 ToomossIsCycleRevice
/// </summary>
public bool IsCycleRevice
{
get { return Driver.IsReceiving; }
}
/// <summary>
/// 最近是否发送成功(用于 UI 指示)。
/// </summary>
public bool IsSendOk
{
get { return Driver.IsSendOk; }
}
/// <summary>
/// 最近是否接收成功(用于 UI 指示)。
/// </summary>
public bool IsReviceOk
{
get { return Driver.IsReviceOk; }
}
/// <summary>
/// 要发送的 CAN 指令数据。
/// </summary>
public List<CanCmdData> CmdData { get; } = new List<CanCmdData>();
private readonly object _scheduleLock = new object();
private List<(string MsgName, int CycleMs, int OrderSend, int SchTabIndex)> _scheduleItems = new List<(string, int, int, int)>();
private CancellationTokenSource? _scheduleCts;
private Task? _scheduleTask;
private bool _scheduleUseConfigItems;
private CanCmdData? SpeedCanCmdData { get; set; }
private uint _deviceIndex = 0;
private ZlgCanFdChannelOptions _channel0 = new ZlgCanFdChannelOptions();
/// <summary>
/// 构造。
/// </summary>
/// <param name="logService">日志服务。</param>
public ZlgCanDriveService(ILogService logService)
{
_log = logService;
Driver = new ZlgCanFd200uDriver(logService);
Driver.PropertyChanged += (_, __) =>
{
OpenState = Driver.OpenState;
RaisePropertyChanged(nameof(IsCycleRevice));
RaisePropertyChanged(nameof(IsSendOk));
RaisePropertyChanged(nameof(IsReviceOk));
};
OpenState = Driver.OpenState;
Mode = ZlgCanMode.Can;
}
/// <summary>
/// 初始化 CAN 配置信息,并将配置中的名称映射到 DBC 信号集合(用于 UI 显示)。
/// </summary>
/// <param name="selectedCanLinConfigPro">选中的配置。</param>
public void InitCanConfig(CanLinConfigPro selectedCanLinConfigPro)
{
SelectedCanLinConfigPro = selectedCanLinConfigPro;
if (SelectedCanLinConfigPro?.CanLinConfigContents == null) return;
foreach (var item in SelectedCanLinConfigPro.CanLinConfigContents)
{
var find = ListCanDbcModel.FindFirst(a => a.SignalName == item.SignalName);
if (find != null)
{
find.Name = item.Name;
}
}
}
/// <summary>
/// 更新配置(从 DTO/DB 同步到驱动)。
/// </summary>
/// <param name="deviceIndex">设备索引。</param>
/// <param name="arbBaudRate">仲裁波特率bps。</param>
/// <param name="dataBaudRate">数据波特率bps。</param>
/// <param name="resEnable">终端电阻使能。</param>
/// <param name="mergeReceive">是否合并接收。</param>
/// <param name="mergeReceiveBufferFrames">合并接收缓冲帧数。</param>
public void UpdateConfig(uint deviceIndex, uint arbBaudRate, uint dataBaudRate, bool resEnable, bool mergeReceive = false, int mergeReceiveBufferFrames = 100)
{
_deviceIndex = deviceIndex;
_channel0.ArbitrationBaudRate = arbBaudRate;
_channel0.DataBaudRate = dataBaudRate;
_channel0.EnableInternalResistance = resEnable;
_channel0.EnableMergeReceive = mergeReceive;
_channel0.MergeReceiveBufferFrames = mergeReceiveBufferFrames;
}
/// <summary>
/// 打开 CAN/CANFD按 Mode
/// </summary>
public void StartCanDrive()
{
if (OpenState)
{
return;
}
Driver.OpenAndInitCan(_deviceIndex, _channel0);
Driver.StartReceiveLoop(_channel0.EnableMergeReceive, _channel0.MergeReceiveBufferFrames);
OpenState = Driver.OpenState;
}
/// <summary>
/// 使能/停止循环接收。
/// </summary>
/// <param name="enable">true=启动接收线程false=停止接收线程。</param>
public void SetReceiveEnabled(bool enable)
{
if (!OpenState)
{
throw new InvalidOperationException("设备未连接,无法切换循环接收。");
}
if (enable)
{
if (!Driver.IsReceiving)
{
Driver.StartReceiveLoop(_channel0.EnableMergeReceive, _channel0.MergeReceiveBufferFrames);
}
}
else
{
if (Driver.IsReceiving)
{
Driver.StopReceiveLoop();
}
}
RaisePropertyChanged(nameof(IsCycleRevice));
}
/// <summary>
/// 关闭设备(会同时关闭共享的 LIN 通道)。
/// </summary>
public void CloseDevice()
{
try
{
// Close 语义:关闭时必须停止循环发送与循环接收。
// - 循环发送:停止软件调度,并关闭事件驱动发送标志。
// - 循环接收Driver.Close 内部会 StopReceiveLoop。
StopSchedule();
IsCycleSend = false;
Driver.Close();
}
finally
{
OpenState = Driver.OpenState;
DbcParserState = false;
}
}
public void SetScheduleConfigs(IEnumerable<CANScheduleConfigDto> configs)
{
var list = configs?.Where(a => !string.IsNullOrWhiteSpace(a.MsgName)).ToList() ?? new List<CANScheduleConfigDto>();
lock (_scheduleLock)
{
_scheduleItems = list
.Select(a => (a.MsgName!.Trim(), Math.Max(1, a.Cycle), a.OrderSend, a.SchTabIndex))
.ToList();
}
}
public void SetScheduleConfigs(IEnumerable<CANFdScheduleConfigDto> configs)
{
var list = configs?.Where(a => !string.IsNullOrWhiteSpace(a.MsgName)).ToList() ?? new List<CANFdScheduleConfigDto>();
lock (_scheduleLock)
{
_scheduleItems = list
.Select(a => (a.MsgName!.Trim(), Math.Max(1, a.Cycle), a.OrderSend, a.SchTabIndex))
.ToList();
}
}
public void StartSchedule()
{
if (!OpenState)
{
throw new InvalidOperationException("设备未连接,无法启动调度表。");
}
List<(string MsgName, int CycleMs, int OrderSend, int SchTabIndex)> items;
lock (_scheduleLock)
{
items = _scheduleItems.ToList();
}
if (items.Count == 0)
{
throw new InvalidOperationException("调度表为空,无法启动调度表。");
}
_scheduleUseConfigItems = true;
StartSoftwareScheduler(items);
}
public void StartPrecisionCycleSend(int cycleMs)
{
if (!OpenState)
{
throw new InvalidOperationException("设备未连接,无法启动循环发送。");
}
var ms = Math.Max(1, cycleMs);
var msgNames = CmdData.Where(a => !string.IsNullOrWhiteSpace(a.MsgName)).Select(a => a.MsgName!).Distinct(StringComparer.Ordinal).ToList();
if (msgNames.Count == 0)
{
throw new InvalidOperationException("CmdData 为空,无法启动循环发送。");
}
var items = msgNames.Select(n => (n, ms, 1, 0)).ToList();
_scheduleUseConfigItems = false;
StartSoftwareScheduler(items);
}
public void StopSchedule()
{
CancellationTokenSource? cts;
Task? task;
lock (_scheduleLock)
{
cts = _scheduleCts;
task = _scheduleTask;
_scheduleCts = null;
_scheduleTask = null;
}
try
{
cts?.Cancel();
if (task != null)
{
task.Wait(TimeSpan.FromSeconds(2));
}
}
catch
{
}
finally
{
cts?.Dispose();
}
}
private void StartSoftwareScheduler(List<(string MsgName, int CycleMs, int OrderSend, int SchTabIndex)> items)
{
StopSchedule();
var cts = new CancellationTokenSource();
lock (_scheduleLock)
{
_scheduleCts = cts;
}
// 统一:软件调度开启后,等同“循环发送开启”
IsCycleSend = true;
_scheduleTask = Task.Run(async () =>
{
var token = cts.Token;
// next due time for each msg
var now = DateTime.UtcNow;
var due = new Dictionary<string, DateTime>(StringComparer.Ordinal);
var cycle = new Dictionary<string, int>(StringComparer.Ordinal);
var order = new Dictionary<string, int>(StringComparer.Ordinal);
foreach (var it in items)
{
if (!due.ContainsKey(it.MsgName))
{
due[it.MsgName] = now;
cycle[it.MsgName] = Math.Max(1, it.CycleMs);
order[it.MsgName] = it.OrderSend;
}
}
while (!token.IsCancellationRequested)
{
if (!OpenState)
{
await Task.Delay(50, token).ConfigureAwait(false);
continue;
}
var utcNow = DateTime.UtcNow;
var minDue = due.Values.Min();
var delay = minDue - utcNow;
if (delay > TimeSpan.Zero)
{
var ms = (int)Math.Min(delay.TotalMilliseconds, 200);
await Task.Delay(ms, token).ConfigureAwait(false);
continue;
}
// due messages
var ready = due.Where(kv => kv.Value <= utcNow).Select(kv => kv.Key).ToList();
if (ready.Count == 0)
{
await Task.Delay(1, token).ConfigureAwait(false);
continue;
}
// 顺序/并行:这里只决定同一 tick 内的发送顺序(并行模式仍按字典序依次发)
ready.Sort(StringComparer.Ordinal);
foreach (var msg in ready)
{
if (token.IsCancellationRequested) break;
try
{
Driver.SendOneMsgByCmdData(msg, 0, Mode == ZlgCanMode.Can ? (byte)ZDBC.FT_CAN : (byte)ZDBC.FT_CANFD);
}
catch (Exception ex)
{
_log.Warn($"调度表发送失败:{msg}{ex.Message}");
}
finally
{
due[msg] = DateTime.UtcNow.AddMilliseconds(cycle[msg]);
}
}
}
}, cts.Token);
}
/// <summary>
/// 加载 DBC。
/// </summary>
/// <param name="path">DBC 路径。</param>
public ObservableCollection<CanDbcModel> StartDbc(string path)
{
if (!OpenState)
{
throw new InvalidOperationException("请先打开设备后再加载 DBC。");
}
ListCanDbcModel = Driver.StartDbc(path);
DbcParserState = ListCanDbcModel != null && ListCanDbcModel.Count > 0;
return ListCanDbcModel;
}
/// <summary>
/// 加载要发送的数据(订阅数据变化事件)。
/// </summary>
/// <param name="cmdData">指令数据集合。</param>
public void LoadCmdDataToDrive(List<CanCmdData> cmdData)
{
var list = cmdData ?? new List<CanCmdData>();
CmdData.Clear();
CmdData.AddRange(list);
foreach (var item in CmdData)
{
if (string.Equals(item.ConfigName, "转速", StringComparison.Ordinal))
{
SpeedCanCmdData = item;
}
}
Driver.LoadCmdDataToDrive(CmdData, channelIndex: 0, frameType: Mode == ZlgCanMode.Can ? (byte)ZDBC.FT_CAN : (byte)ZDBC.FT_CANFD);
}
/// <summary>
/// 手动发送(目前对齐原服务:仅发送转速)。
/// </summary>
/// <param name="speedData">转速。</param>
public void SendMsgToCanDrive(double speedData)
{
if (!OpenState)
{
return;
}
if (SpeedCanCmdData != null)
{
SpeedCanCmdData.SignalCmdValue = speedData;
}
else
{
_log.Warn("未配置转速指令项ConfigName=转速),忽略手动发送。");
}
// 若未启用事件驱动发送,则这里主动发送一次(与旧行为兼容)
if (!IsCycleSend || !SchEnable)
{
var firstMsg = CmdData.FirstOrDefault()?.MsgName;
if (!string.IsNullOrWhiteSpace(firstMsg))
{
Driver.SendOneMsgByCmdData(firstMsg, 0, Mode == ZlgCanMode.Can ? (byte)ZDBC.FT_CAN : (byte)ZDBC.FT_CANFD);
}
}
}
}
/// <summary>
/// ZLG CAN 工作模式。
/// </summary>
public enum ZlgCanMode
{
/// <summary>
/// CAN 经典帧。
/// </summary>
Can = 0,
/// <summary>
/// CAN FD。
/// </summary>
CanFd = 1
}
}