CAN卡添加
This commit is contained in:
12
ADP.sln
12
ADP.sln
@@ -35,10 +35,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateInfoMoudle", "UpdateI
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonitorModule", "MonitorModule\MonitorModule.csproj", "{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CAN驱动", "CAN驱动\CAN驱动.csproj", "{D1868672-0132-4B1A-A393-C2CF49A25E74}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceEditModule", "DeviceEditModule\DeviceEditModule.csproj", "{170AD4C1-189D-4FBE-B10D-2A4304527834}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "USBCANFD", "USBCANFD\USBCANFD.csproj", "{C011DC26-E2F8-4F40-AD62-C5D50BA56CE8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -105,14 +105,14 @@ Global
|
||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D1868672-0132-4B1A-A393-C2CF49A25E74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D1868672-0132-4B1A-A393-C2CF49A25E74}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D1868672-0132-4B1A-A393-C2CF49A25E74}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D1868672-0132-4B1A-A393-C2CF49A25E74}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C011DC26-E2F8-4F40-AD62-C5D50BA56CE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C011DC26-E2F8-4F40-AD62-C5D50BA56CE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C011DC26-E2F8-4F40-AD62-C5D50BA56CE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C011DC26-E2F8-4F40-AD62-C5D50BA56CE8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CAN驱动\CAN驱动.csproj" />
|
||||
<ProjectReference Include="..\Command\Command.csproj" />
|
||||
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
||||
<ProjectReference Include="..\DeviceEditModule\DeviceEditModule.csproj" />
|
||||
@@ -31,6 +30,7 @@
|
||||
<ProjectReference Include="..\TestingModule\TestingModule.csproj" />
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
|
||||
<ProjectReference Include="..\USBCANFD\USBCANFD.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
417
CAN驱动/CAN.cs
417
CAN驱动/CAN.cs
@@ -1,417 +0,0 @@
|
||||
using Common.Attributes;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using TSMaster;
|
||||
namespace CAN驱动
|
||||
{
|
||||
[ADPCommand]
|
||||
public class CAN
|
||||
{
|
||||
|
||||
public static event Action ConnectEvent;
|
||||
public static event Action DisConnectEvent;
|
||||
public static bool ConnectFlag { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化CAN驱动
|
||||
/// </summary>
|
||||
/// <param name="ProjectName"></param>
|
||||
/// <param name="filePath"></param>
|
||||
/// <returns></returns>
|
||||
[Browsable(false)]
|
||||
public static int Init(string ProjectName, string? filePath = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
return TsMasterApi.initialize_lib_tsmaster(ProjectName);
|
||||
}
|
||||
else
|
||||
{
|
||||
return TsMasterApi.initialize_lib_tsmaster_with_project(ProjectName, Path.GetFullPath(filePath));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放CAN驱动
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public static void Release()
|
||||
{
|
||||
TsMasterApi.finalize_lib_tsmaster();
|
||||
}
|
||||
|
||||
public static int obj = 0;
|
||||
public static TCANFDQueueEvent_Win32 listener;
|
||||
[Browsable(false)]
|
||||
public static int RegisterListener(TCANFDQueueEvent_Win32 listenEvent)
|
||||
{
|
||||
listener = listenEvent;
|
||||
var re = TsMasterApi.tsapp_register_event_canfd(ref obj, listener);
|
||||
Debug.Assert(re == 0);
|
||||
return re;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public static int UnRegisterListener(TCANFDQueueEvent_Win32 listenEvent = null)
|
||||
{
|
||||
if (listenEvent is not null)
|
||||
{
|
||||
listener = listenEvent;
|
||||
}
|
||||
var re = TsMasterApi.tsapp_unregister_event_canfd(ref obj, listener);
|
||||
Debug.Assert(re == 0);
|
||||
return re;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int Connect()
|
||||
{
|
||||
|
||||
var re = TsMasterApi.tsapp_connect();
|
||||
if (re == 0)
|
||||
{
|
||||
re = TsMasterApi.tscom_can_rbs_start();
|
||||
if (re == 0)
|
||||
{
|
||||
ConnectFlag = true;
|
||||
Task.Run(() => ConnectEvent?.Invoke());
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"CAN_RBS启动失败,错误代码:{re}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"CAN连接失败,错误代码:{re}");
|
||||
return re;
|
||||
}
|
||||
return re;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int DisConnect()
|
||||
{
|
||||
var re = TsMasterApi.tsapp_disconnect();
|
||||
//Debug.Assert(re == 0);
|
||||
if (re == 0)
|
||||
{
|
||||
ConnectFlag = false;
|
||||
Task.Run(() => DisConnectEvent?.Invoke());
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"断开CAN连接失败,错误代码:{re}");
|
||||
}
|
||||
|
||||
|
||||
return re;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载DBC
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="databaseID"></param>
|
||||
/// <returns></returns>
|
||||
[Browsable(false)]
|
||||
public static int LoadDBC(string filePath, int[] channel, out uint databaseID)
|
||||
{
|
||||
databaseID = 0;
|
||||
|
||||
var re = TsMasterApi.tsdb_load_can_db(Path.GetFullPath(filePath), string.Join(",", channel), ref databaseID);
|
||||
foreach (var item in channel)
|
||||
{
|
||||
DBCParse.parse(databaseID, item);
|
||||
DBCParse.rbs_parse(databaseID, item);
|
||||
}
|
||||
|
||||
return re;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卸载DBC
|
||||
/// </summary>
|
||||
/// <param name="databaseID"></param>
|
||||
/// <returns></returns>
|
||||
[Browsable(false)]
|
||||
public static int UnLoadDBC(uint? databaseID = null)
|
||||
{
|
||||
if (databaseID == null)
|
||||
{
|
||||
return TsMasterApi.tsdb_unload_can_dbs();
|
||||
}
|
||||
else
|
||||
{
|
||||
return TsMasterApi.tsdb_unload_can_db(databaseID.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始记录日志
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <returns></returns>
|
||||
public static int StartLogging(string filePath)
|
||||
{
|
||||
return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束记录日志
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int StopLogging()
|
||||
{
|
||||
return TsMasterApi.tsapp_stop_logging();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹出通道映射窗口
|
||||
/// </summary>
|
||||
/// <param name="isWait"></param>
|
||||
/// <returns></returns>
|
||||
[Browsable(false)]
|
||||
public static int ShowChannelMappingWindow(bool isWait = false)
|
||||
{
|
||||
return TsMasterApi.tsapp_show_tsmaster_window("Hardware", isWait);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取信号值
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="AMsgName"></param>
|
||||
/// <param name="ASgnName"></param>
|
||||
/// <returns></returns>
|
||||
public static double GetSignalValue(byte channel, string AMsgName, string ASgnName)
|
||||
{
|
||||
double value = double.NaN;
|
||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
||||
var re = TsMasterApi.tsdb_get_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, ref value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取信号值
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="AMsgName"></param>
|
||||
/// <param name="ASgnName"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
[Browsable(false)]
|
||||
public static int GetSignalValue(byte channel, string AMsgName, string ASgnName, ref double value)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
||||
return TsMasterApi.tsdb_get_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取信号值
|
||||
/// </summary>
|
||||
/// <param name="ACANFD"></param>
|
||||
/// <param name="AMsgName"></param>
|
||||
/// <param name="ASgnName"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
[Browsable(false)]
|
||||
public static int GetSignalValue(ref TLIBCANFD ACANFD, string AMsgName, string ASgnName, ref double value)
|
||||
{
|
||||
return TsMasterApi.tsdb_get_signal_value_canfd(ref ACANFD, AMsgName, ASgnName, ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置信号值
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="AMsgName"></param>
|
||||
/// <param name="ASgnName"></param>
|
||||
/// <param name="AValue"></param>
|
||||
/// <param name="isSend"></param>
|
||||
/// <param name="sendPeriod"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static int SetSignalValue(byte channel, string AMsgName, string ASgnName, double AValue, bool isSend = false, float sendPeriod = 0)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
||||
find.ACANFD.FIdxChn = channel;
|
||||
var re = TsMasterApi.tsdb_set_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, AValue);
|
||||
Debug.Assert(re == 0);
|
||||
if (re != 0) throw new Exception($"设置报文失败!返回代码{re}");
|
||||
if (re != 0) return re;
|
||||
if (isSend)
|
||||
{
|
||||
if (sendPeriod == 0)
|
||||
{
|
||||
return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD);
|
||||
}
|
||||
else
|
||||
{
|
||||
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 设置信号值
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="AMsgName"></param>
|
||||
/// <param name="sendPeriod"></param>
|
||||
/// <returns></returns>
|
||||
public static int SetSignalValue(APP_CHANNEL channel, string AMsgName, float sendPeriod = 0)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[(byte)channel].First(s => s.msg_name == AMsgName);
|
||||
find.ACANFD.FIdxChn = (byte)channel;
|
||||
if (sendPeriod == 0)
|
||||
{
|
||||
return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD);
|
||||
}
|
||||
else
|
||||
{
|
||||
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送自定义报文
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Browsable(false)]
|
||||
public static int SetMsg(TLIBCANFD msg)
|
||||
{
|
||||
return TsMasterApi.tsapp_transmit_canfd_async(ref msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置报文
|
||||
/// </summary>
|
||||
/// <param name="channel">通道</param>
|
||||
/// <param name="ID">DBC数据库ID</param>
|
||||
/// <param name="bytes">报文数组</param>
|
||||
public static void SetMsg(APP_CHANNEL channel, int ID, byte[] bytes)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[(byte)channel].FirstOrDefault(s => s.ACANFD.FIdentifier == ID);
|
||||
if (find != null)
|
||||
{
|
||||
Array.Copy(bytes, 0, find.ACANFD.FData, 0, Math.Min(bytes.Length, find.ACANFD.FData.Length));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送自定义报文
|
||||
/// </summary>
|
||||
/// <param name="AIdxChn"></param>
|
||||
/// <param name="AID"></param>
|
||||
/// <param name="AIsTx"></param>
|
||||
/// <param name="AIsExt"></param>
|
||||
/// <param name="AIsRemote"></param>
|
||||
/// <param name="ADLC"></param>
|
||||
/// <param name="ADataArray"></param>
|
||||
/// <param name="AIsFD"></param>
|
||||
/// <param name="AIsBRS"></param>
|
||||
/// <param name="isUpdateDBCDatabase"></param>
|
||||
/// <param name="sendPeriod"></param>
|
||||
/// <returns></returns>
|
||||
public static int SendMsg(APP_CHANNEL AIdxChn, int AID, bool AIsTx, bool AIsExt,
|
||||
bool AIsRemote, byte ADLC, byte[] ADataArray, bool AIsFD = true, bool AIsBRS = false,
|
||||
bool isUpdateDBCDatabase = false, float sendPeriod = 0)
|
||||
{
|
||||
var send = new TLIBCANFD
|
||||
{
|
||||
FIdxChn = (byte)AIdxChn,
|
||||
FProperties = 0,
|
||||
FIdentifier = AID,
|
||||
FDLC = ADLC,
|
||||
FTimeUS = 0uL,
|
||||
FData = new byte[64],
|
||||
FFDProperties = 0,
|
||||
FIsTx = AIsTx,
|
||||
FIsError = false,
|
||||
FIsExt = AIsExt,
|
||||
FIsRemote = AIsRemote,
|
||||
FIsFD = AIsFD,
|
||||
FIsBRS = AIsBRS
|
||||
};
|
||||
int length = Math.Min(ADataArray.Length, 64);
|
||||
Array.Copy(ADataArray, 0, send.FData, 0, length);
|
||||
int re;
|
||||
if (sendPeriod == 0)
|
||||
{
|
||||
re = TsMasterApi.tsapp_transmit_canfd_async(ref send);
|
||||
}
|
||||
else
|
||||
{
|
||||
re = TsMasterApi.tsapp_add_cyclic_msg_canfd(ref send, sendPeriod);
|
||||
}
|
||||
|
||||
if (isUpdateDBCDatabase)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[(byte)AIdxChn].FirstOrDefault(s => s.ACANFD.FIdentifier == send.FIdentifier);
|
||||
if (find != null)
|
||||
{
|
||||
Array.Copy(send.FData, 0, find.ACANFD.FData, 0, Math.Min(ADataArray.Length, find.ACANFD.FData.Length));
|
||||
}
|
||||
}
|
||||
return re;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加循环发送报文
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="AMsgName"></param>
|
||||
/// <param name="sendPeriod"></param>
|
||||
/// <returns></returns>
|
||||
public static int AddCyclicMsg(byte channel, string AMsgName, float sendPeriod)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
||||
find.ACANFD.FIdxChn = channel;
|
||||
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除循环发送的报文
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int DeleteCyclicMsgs()
|
||||
{
|
||||
return TsMasterApi.tsapp_delete_cyclic_msgs();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取错误提示
|
||||
/// </summary>
|
||||
/// <param name="errorCode">错误代码</param>
|
||||
/// <returns></returns>
|
||||
public static string GetErrorDescription(int errorCode)
|
||||
{
|
||||
IntPtr ADesc = IntPtr.Zero;
|
||||
TsMasterApi.tsapp_get_error_description(errorCode, ref ADesc);
|
||||
|
||||
if (ADesc == IntPtr.Zero) return $"未知错误代码: {errorCode}";
|
||||
|
||||
// 假设返回的是 ANSI 字符串(如 C 的 char*)
|
||||
string? description = Marshal.PtrToStringAnsi(ADesc);
|
||||
|
||||
return description ?? $"未知错误代码: {errorCode}";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using TSMaster;
|
||||
|
||||
namespace CAN驱动
|
||||
{
|
||||
public struct can_network
|
||||
{
|
||||
[JsonInclude]
|
||||
public string network_name;
|
||||
[JsonInclude]
|
||||
public can_node[] can_nodes;
|
||||
|
||||
}
|
||||
public struct can_node
|
||||
{
|
||||
[JsonInclude]
|
||||
public string node_name;
|
||||
[JsonInclude]
|
||||
public can_message[] tx_can_Messages;
|
||||
[JsonInclude]
|
||||
public can_message[] rx_can_Messages;
|
||||
}
|
||||
public struct can_message
|
||||
{
|
||||
[JsonInclude]
|
||||
public string message_name;
|
||||
[JsonInclude]
|
||||
public string[] signals;
|
||||
}
|
||||
|
||||
public class _Msg_
|
||||
{
|
||||
public int msg_type { get; set; }
|
||||
public int msg_id { get; set; }
|
||||
public int msg_cyclic { get; set; }
|
||||
public byte msg_dlc { get; set; }
|
||||
public string msg_name { get; set; }
|
||||
public string[] signal_Name { get; set; }
|
||||
public int msg_brs { get; set; }
|
||||
public TLIBCANFD ACANFD;
|
||||
}
|
||||
public enum Msg_Type
|
||||
{
|
||||
CAN = 0,
|
||||
CANFD = 1,
|
||||
}
|
||||
/// <summary>
|
||||
/// DBC 文件信息类型枚举,用于指定 tsdb_get_can_db_info 查询的内容类型。
|
||||
/// </summary>
|
||||
public enum DBCINFO
|
||||
{
|
||||
/// <summary>网络名称</summary>
|
||||
NETWORK_Name = 0,
|
||||
|
||||
/// <summary>CAN 报文总数</summary>
|
||||
CAN_MSG_COUNT = 11,
|
||||
/// <summary>CAN FD 报文总数</summary>
|
||||
CANFD_MSG_COUNT = 12,
|
||||
/// <summary>节点总数</summary>
|
||||
NODE_COUNT = 14,
|
||||
|
||||
/// <summary>信号名称</summary>
|
||||
SIGNAL_NAME = 33,
|
||||
|
||||
/// <summary>CAN 报文类型</summary>
|
||||
CAN_MSG_TYPE = 40,
|
||||
/// <summary>CAN 报文 DLC(数据长度码)</summary>
|
||||
CAN_MSG_DLC = 41,
|
||||
/// <summary>CAN 报文 ID</summary>
|
||||
CAN_MSG_ID = 42,
|
||||
/// <summary>CAN 报文周期</summary>
|
||||
CAN_MSG_CYCLIC = 43,
|
||||
/// <summary>CAN 报文名称</summary>
|
||||
CAN_MSG_NAME = 44,
|
||||
/// <summary>CAN 报文信号数量</summary>
|
||||
CAN_MSG_SIGNAL_COUNT = 47,
|
||||
/// <summary>CAN 报文信号名称索引</summary>
|
||||
CAN_MSG_SIGNAL_NAME_INDEX = 48,
|
||||
|
||||
/// <summary>CAN FD 报文类型</summary>
|
||||
CANFD_MSG_TYPE = 60,
|
||||
/// <summary>CAN FD 报文 DLC(数据长度码)</summary>
|
||||
CANFD_MSG_DLC = 61,
|
||||
/// <summary>CAN FD 报文 ID</summary>
|
||||
CANFD_MSG_ID = 62,
|
||||
/// <summary>CAN FD 报文周期</summary>
|
||||
CANFD_MSG_CYCLIC = 63,
|
||||
/// <summary>CAN FD 报文名称</summary>
|
||||
CANFD_MSG_NAME = 64,
|
||||
/// <summary>CAN FD 报文信号数量</summary>
|
||||
CANFD_MSG_SIGNAL_COUNT = 67,
|
||||
/// <summary>CAN FD 报文信号名称索引</summary>
|
||||
CANFD_MSG_SIGNAL_NAME_INDEX = 68,
|
||||
|
||||
/// <summary>节点名称</summary>
|
||||
NODE_Name = 101,
|
||||
/// <summary>节点发送 CAN 报文数量</summary>
|
||||
NODE_TX_CAN_COUNT = 103,
|
||||
/// <summary>节点发送 CAN 报文索引</summary>
|
||||
NODE_TX_CAN_MESSAGE_INDEX = 104,
|
||||
/// <summary>节点接收 CAN 报文数量</summary>
|
||||
NODE_RX_CAN_COUNT = 105,
|
||||
/// <summary>节点接收 CAN 报文索引</summary>
|
||||
NODE_RX_CAN_MESSAGE_INDEX = 106,
|
||||
/// <summary>节点发送 CAN FD 报文数量</summary>
|
||||
NODE_TX_CANFD_COUNT = 107,
|
||||
/// <summary>节点发送 CAN FD 报文索引</summary>
|
||||
NODE_TX_CANFD_MESSAGE_INDEX = 108,
|
||||
/// <summary>节点接收 CAN FD 报文数量</summary>
|
||||
NODE_RX_CANFD_COUNT = 109,
|
||||
/// <summary>节点接收 CAN FD 报文索引</summary>
|
||||
NODE_RX_CANFD_MESSAGE_INDEX = 110,
|
||||
}
|
||||
|
||||
public class DBCParse
|
||||
{
|
||||
public static List<List<_Msg_>> MsgDatabase { get; set; } =
|
||||
[.. Enumerable.Range(0, 12).Select(_ => new List<_Msg_>())];
|
||||
public static can_network can_Network;
|
||||
public static string tsdb_get_can_db_info(uint ADatabaseId, int AType, int AIndex, int ASubIndex)
|
||||
{
|
||||
nint tmpInt = new nint();
|
||||
int ret = TsMasterApi.tsdb_get_can_db_info(ADatabaseId, AType, AIndex, ASubIndex, ref tmpInt);
|
||||
if (ret == 0)
|
||||
{
|
||||
return Marshal.PtrToStringAnsi(tmpInt) ?? "";
|
||||
}
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void parse(uint Aid, int channel)
|
||||
{
|
||||
int CANcount = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_COUNT, 0, 0));
|
||||
for (int i = 0; i < CANcount; i++)
|
||||
{
|
||||
_Msg_ temp = new _Msg_();
|
||||
temp.msg_type = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_TYPE, i, 0));
|
||||
temp.msg_dlc = byte.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_DLC, i, 0));
|
||||
temp.msg_id = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_ID, i, 0));
|
||||
temp.msg_name = tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_NAME, i, 0);
|
||||
temp.msg_cyclic = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_CYCLIC, i, 0));
|
||||
temp.signal_Name = new string[int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_SIGNAL_COUNT, i, 0))];
|
||||
for (int signalcount = 0; signalcount < temp.signal_Name.Length; signalcount++)
|
||||
{
|
||||
temp.signal_Name[signalcount] = tsdb_get_can_db_info(Aid, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_SIGNAL_NAME_INDEX, i, signalcount)), 0);
|
||||
}
|
||||
temp.msg_brs = 0;
|
||||
temp.ACANFD = new TLIBCANFD(APP_CHANNEL.CHN1,
|
||||
temp.msg_id,
|
||||
true,
|
||||
temp.msg_type == 1 || temp.msg_type == 4 ? true : false,
|
||||
false, temp.msg_dlc,
|
||||
temp.msg_type < 2 ? false : true,
|
||||
false);
|
||||
MsgDatabase[channel].Add(temp);
|
||||
|
||||
}
|
||||
|
||||
CANcount = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_COUNT, 0, 0));
|
||||
for (int i = 0; i < CANcount; i++)
|
||||
{
|
||||
_Msg_ temp = new _Msg_();
|
||||
temp.msg_type = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_TYPE, i, 0));
|
||||
temp.msg_dlc = byte.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_DLC, i, 0));
|
||||
temp.msg_id = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_ID, i, 0));
|
||||
temp.msg_name = tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_NAME, i, 0);
|
||||
temp.msg_cyclic = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_CYCLIC, i, 0));
|
||||
temp.signal_Name = new string[int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_SIGNAL_COUNT, i, 0))];
|
||||
for (int signalcount = 0; signalcount < temp.signal_Name.Length; signalcount++)
|
||||
{
|
||||
temp.signal_Name[signalcount] = tsdb_get_can_db_info(Aid, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_SIGNAL_NAME_INDEX, i, signalcount)), 0);
|
||||
}
|
||||
temp.msg_brs = int.Parse(tsdb_get_can_db_info(Aid, 69, i, 0));
|
||||
temp.ACANFD = new TLIBCANFD(APP_CHANNEL.CHN1, temp.msg_id, true, temp.msg_type == 1 || temp.msg_type == 4 ? true : false, false, temp.msg_dlc, temp.msg_type < 2 ? false : true, temp.msg_brs == 1 ? true : false);
|
||||
MsgDatabase[channel].Add(temp);
|
||||
}
|
||||
MsgDatabase[channel].Sort((a, b) => a.msg_id.CompareTo(b.msg_id));
|
||||
}
|
||||
|
||||
public static void rbs_parse(uint AID, int channel)
|
||||
{
|
||||
can_Network = new can_network();
|
||||
can_Network.network_name = tsdb_get_can_db_info(AID, (int)DBCINFO.NETWORK_Name, 0, 0);
|
||||
int Node_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_COUNT, 0, 0));
|
||||
can_Network.can_nodes = new can_node[Node_count];
|
||||
for (int i = 0; i < Node_count; i++)
|
||||
{
|
||||
can_Network.can_nodes[i].node_name = tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_Name, i, 0);
|
||||
int tx_can_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CAN_COUNT, i, 0));
|
||||
int tx_canfd_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CANFD_COUNT, i, 0));
|
||||
|
||||
int rx_can_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CAN_COUNT, i, 0));
|
||||
int rx_canfd_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CANFD_COUNT, i, 0));
|
||||
|
||||
//获取tx_can结构体
|
||||
can_Network.can_nodes[i].tx_can_Messages = new can_message[tx_can_count + tx_canfd_count];
|
||||
|
||||
can_Network.can_nodes[i].rx_can_Messages = new can_message[rx_can_count + rx_canfd_count];
|
||||
//获取报文名以及信号名
|
||||
//1 get can_message idnex
|
||||
//2 get can message name
|
||||
//3 get signal count
|
||||
//4 get signal name
|
||||
int tx_index = -1;
|
||||
for (int tx_can = 0; tx_can < tx_can_count; tx_can++)
|
||||
{
|
||||
int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CAN_MESSAGE_INDEX, i, tx_can));
|
||||
can_Network.can_nodes[i].tx_can_Messages[tx_can].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_NAME, can_message_idnex, 0);
|
||||
|
||||
can_Network.can_nodes[i].tx_can_Messages[tx_can].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
|
||||
for (int signalcount = 0; signalcount < can_Network.can_nodes[i].tx_can_Messages[tx_can].signals.Length; signalcount++)
|
||||
{
|
||||
can_Network.can_nodes[i].tx_can_Messages[tx_can].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
|
||||
}
|
||||
|
||||
tx_index = tx_can;
|
||||
}
|
||||
for (int tx_can = 0; tx_can < tx_canfd_count; tx_can++, tx_index++)
|
||||
{
|
||||
int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CANFD_MESSAGE_INDEX, i, tx_can));
|
||||
can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_NAME, can_message_idnex, 0);
|
||||
|
||||
can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
|
||||
for (int signalcount = 0; signalcount < can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].signals.Length; signalcount++)
|
||||
{
|
||||
can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
|
||||
}
|
||||
}
|
||||
|
||||
int rx_index = -1;
|
||||
for (int tx_can = 0; tx_can < rx_can_count; tx_can++)
|
||||
{
|
||||
int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CAN_MESSAGE_INDEX, i, tx_can));
|
||||
can_Network.can_nodes[i].rx_can_Messages[tx_can].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_NAME, can_message_idnex, 0);
|
||||
|
||||
can_Network.can_nodes[i].rx_can_Messages[tx_can].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
|
||||
for (int signalcount = 0; signalcount < can_Network.can_nodes[i].rx_can_Messages[tx_can].signals.Length; signalcount++)
|
||||
{
|
||||
can_Network.can_nodes[i].rx_can_Messages[tx_can].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
|
||||
}
|
||||
|
||||
rx_index = tx_can;
|
||||
}
|
||||
for (int tx_can = 0; tx_can < rx_canfd_count; tx_can++, rx_index++)
|
||||
{
|
||||
int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CANFD_MESSAGE_INDEX, i, tx_can));
|
||||
can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_NAME, can_message_idnex, 0);
|
||||
|
||||
can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
|
||||
for (int signalcount = 0; signalcount < can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].signals.Length; signalcount++)
|
||||
{
|
||||
can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -172,7 +172,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 快捷设置电压电流(double 电压, double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":APPLY {0:F3},{1:F3}{2}", 电压, 电流, SCPIDelimiter);
|
||||
// 修正:去除前缀冒号,规范应用 APPL 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "APPL {0:F3},{1:F3}{2}", 电压, 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
@@ -181,7 +182,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询快捷电压电流设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":APPLY?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"APPL?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -194,7 +196,8 @@ namespace DeviceCommand.Device
|
||||
[Monitorable("低压直流电源电流")]
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CURRent?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号,改用标准 MEAS:CURR?
|
||||
return await WriteReadAsync($"MEAS:CURR?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -203,16 +206,18 @@ namespace DeviceCommand.Device
|
||||
[Monitorable("低压直流电源功率")]
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号,改用标准 MEAS:POW?
|
||||
return await WriteReadAsync($"MEAS:POW?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.3 查询通道输出端子上测得的直流电压值 (V)
|
||||
/// </summary
|
||||
/// </summary>
|
||||
[Monitorable("低压直流电源电压")]
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VOLTage?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号,改用标准 MEAS:VOLT?
|
||||
return await WriteReadAsync($"MEAS:VOLT?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -220,7 +225,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VAP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"MEAS:VAP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -228,7 +234,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询测得电池容量(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CAPAcity?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"MEAS:CAP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -236,7 +243,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒流输出时间长度(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:TIMEr:CC?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"MEAS:TIM:CC?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -244,7 +252,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询总输出时间长度(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:TIME:OUTPut?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"MEAS:TIME:OUTP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -256,8 +265,9 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用 OUTP 短格式
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":OUTPut {状态}{SCPIDelimiter}", ct);
|
||||
await SendAsync($"OUTP {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -265,7 +275,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":OUTPut?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"OUTP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -273,8 +284,9 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时器状态(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":OUTPut:TIMEr {状态}{SCPIDelimiter}", ct);
|
||||
await SendAsync($"OUTP:TIM {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -282,7 +294,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时器时间(double 秒数, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":OUTPut:TIMEr:DATA {0:F1}{1}", 秒数, SCPIDelimiter);
|
||||
// 修正:去除前缀冒号
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "OUTP:TIM:DATA {0:F1}{1}", 秒数, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
@@ -291,8 +304,9 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置输出模式优先(bool 是CV优先, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 模式 = 是CV优先 ? "CV" : "CC";
|
||||
await SendAsync($":OUTPut:MODE {模式}{SCPIDelimiter}", ct);
|
||||
await SendAsync($"OUTP:MODE {模式}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -304,7 +318,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 清除电流保护状态(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":CURRent:PROTection:CLEar{SCPIDelimiter}", ct);
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"CURR:PROT:CLE{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -312,8 +327,9 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流保护使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,将 STATE 改为更常用的 STAT 简写
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($":CURRent:PROTection:STATE {状态}{SCPIDelimiter}", ct);
|
||||
await SendAsync($"CURR:PROT:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -321,7 +337,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护值(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent:PROTection {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
// 修正:去除前缀冒号。按 NGI 通用指令树,加上层级限制或者直接作用于 CURR:PROT
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CURR:PROT {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
@@ -330,7 +347,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
// 修正:去除前缀冒号,改用标准 CURR 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CURR {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
@@ -339,7 +357,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 清除电压保护状态(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":VOLTage:PROTection:CLEar{SCPIDelimiter}", ct);
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"VOLT:PROT:CLE{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -347,8 +366,9 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压保护使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($":VOLTage:PROTection:STATE {状态}{SCPIDelimiter}", ct);
|
||||
await SendAsync($"VOLT:PROT:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -356,7 +376,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护值(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage:PROTection {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
// 修正:去除前缀冒号
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT:PROT {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
@@ -365,7 +386,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
// 修正:去除前缀冒号,改用标准 VOLT 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
@@ -374,7 +396,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压上限限制(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage:LIMIT {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
// 修正:去除前缀冒号
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT:LIM {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
@@ -383,7 +406,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置功率(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":POWer {0:F3}{1}", 功率, SCPIDelimiter);
|
||||
// 修正:去除前缀冒号,改用标准 POW 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "POW {0:F3}{1}", 功率, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
@@ -392,8 +416,9 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率输出使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($":POWer:STATE {状态}{SCPIDelimiter}", ct);
|
||||
await SendAsync($"POW:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -405,8 +430,9 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 设置蜂鸣器状态(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":SYSTem:BEEPer:STATE {状态}{SCPIDelimiter}", ct);
|
||||
await SendAsync($"SYST:BEEP:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -414,7 +440,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 蜂鸣器鸣叫(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:BEEPer{SCPIDelimiter}", ct);
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:BEEP{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -422,7 +449,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误记录数量(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor:COUNT?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"SYST:ERR:COUN?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -430,7 +458,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"SYST:ERR?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -438,7 +467,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:LOCal{SCPIDelimiter}", ct);
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:LOC{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -446,7 +476,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:REMote{SCPIDelimiter}", ct);
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:REM{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -454,7 +485,8 @@ namespace DeviceCommand.Device
|
||||
/// </summary>
|
||||
public virtual async Task 远程控制模式锁定(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:RWLock{SCPIDelimiter}", ct);
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:RWL{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -182,12 +182,14 @@ namespace MonitorModule.ViewModels
|
||||
channel.Record(time, args.Value);
|
||||
|
||||
// 入队数据库批量写入
|
||||
// CreateTime 使用 args.Time(采样触发时刻),而不是 DateTime.Now(设备响应到达时刻),
|
||||
// 这样即使设备响应有延迟,数据库里的时间戳仍然按设定采样间隔分布。
|
||||
var entity = new MonitorValueEntity
|
||||
{
|
||||
MonitorName = channel.DisplayName,
|
||||
MonitorValue = args.Value,
|
||||
Scope = args.Scope,
|
||||
CreateTime = DateTime.Now,
|
||||
CreateTime = args.Time,
|
||||
IsDel = 0
|
||||
};
|
||||
_insertQueue.Enqueue(entity);
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
using Logger;
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Logger;
|
||||
using Model.Entity;
|
||||
using ORM;
|
||||
using SqlSugar;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
@@ -12,9 +17,9 @@ using UIShare.ViewModelBase;
|
||||
namespace MonitorModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录界面:用于查看运行目录下 SQL/ADP.db 中的数据。
|
||||
/// 功能:表选择 / 关键字 WHERE 查询 / 分页 / 导出 CSV。
|
||||
/// 仍延续 ScopedContext 隔离 + 双击展开 的范式,每个工位独立一份查询状态。
|
||||
/// 记录界面:查询 MonitorValueEntity 表中的历史监测数据。
|
||||
/// 功能:监测项筛选 / 台架筛选 / 日期筛选 / 分页 / 导出 CSV。
|
||||
/// 使用 SqlSugarContext.DbContext 全局单例,与写入端共用同一数据库连接。
|
||||
/// </summary>
|
||||
public class RecordViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||
{
|
||||
@@ -30,30 +35,39 @@ namespace MonitorModule.ViewModels
|
||||
set => SetProperty(ref _testStatus, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<string> TableNames
|
||||
/// <summary>监测项名称下拉列表</summary>
|
||||
public ObservableCollection<string> MonitorNames
|
||||
{
|
||||
get => _tableNames;
|
||||
set => SetProperty(ref _tableNames, value);
|
||||
get => _monitorNames;
|
||||
set => SetProperty(ref _monitorNames, value);
|
||||
}
|
||||
|
||||
public string? SelectedTable
|
||||
/// <summary>选中的监测项(null = 全部)</summary>
|
||||
public string? SelectedMonitorName
|
||||
{
|
||||
get => _selectedTable;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedTable, value))
|
||||
{
|
||||
PageIndex = 1;
|
||||
Query();
|
||||
}
|
||||
}
|
||||
get => _selectedMonitorName;
|
||||
set => SetProperty(ref _selectedMonitorName, value);
|
||||
}
|
||||
|
||||
// 用户填写的 WHERE 子句(不带 WHERE 关键字),例如:Status='OK' AND Id>10
|
||||
public string WhereClause
|
||||
/// <summary>台架(作用域)下拉列表</summary>
|
||||
public ObservableCollection<string> ScopeNames
|
||||
{
|
||||
get => _whereClause;
|
||||
set => SetProperty(ref _whereClause, value);
|
||||
get => _scopeNames;
|
||||
set => SetProperty(ref _scopeNames, value);
|
||||
}
|
||||
|
||||
/// <summary>选中的台架(null = 全部)</summary>
|
||||
public string? SelectedScope
|
||||
{
|
||||
get => _selectedScope;
|
||||
set => SetProperty(ref _selectedScope, value);
|
||||
}
|
||||
|
||||
/// <summary>查询日期(null = 全部日期)</summary>
|
||||
public DateTime? SelectedDate
|
||||
{
|
||||
get => _selectedDate;
|
||||
set => SetProperty(ref _selectedDate, value);
|
||||
}
|
||||
|
||||
public DataTable ResultTable
|
||||
@@ -111,40 +125,38 @@ namespace MonitorModule.ViewModels
|
||||
|
||||
#region 命令
|
||||
public ICommand LoadedCommand { get; }
|
||||
public ICommand RefreshTablesCommand { get; }
|
||||
public ICommand QueryCommand { get; }
|
||||
public ICommand ClearFilterCommand { get; }
|
||||
public ICommand FirstPageCommand { get; }
|
||||
public ICommand PrevPageCommand { get; }
|
||||
public ICommand NextPageCommand { get; }
|
||||
public ICommand LastPageCommand { get; }
|
||||
public ICommand ExportCsvCommand { get; }
|
||||
// 双击展开/折叠:与 MonitorView/AutomatedTestingView 共用
|
||||
public ICommand RefreshCommand { get; }
|
||||
#endregion
|
||||
|
||||
#region 私有字段
|
||||
// 数据库相对路径:运行目录\SQL\ADP.db(数据库未就绪时容错处理,不抛异常)
|
||||
private static readonly string DbFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SQL");
|
||||
private static readonly string DbPath = Path.Combine(DbFolder, "ADP.db");
|
||||
private static readonly string ConnStr = $"Data Source={DbPath};Version=3;";
|
||||
private bool IsInitiated = false;
|
||||
private IScopedProvider _scope;
|
||||
private string _testStatus = string.Empty;
|
||||
private ObservableCollection<string> _tableNames = new();
|
||||
private string? _selectedTable;
|
||||
private string _whereClause = string.Empty;
|
||||
private ObservableCollection<string> _monitorNames = new();
|
||||
private string? _selectedMonitorName;
|
||||
private ObservableCollection<string> _scopeNames = new(new[] { "全部", "TestCell1", "TestCell2", "TestCell3", "TestCell4", "TestCell5", "TestCell6", "TestCell7", "TestCell8" });
|
||||
private string? _selectedScope = "全部";
|
||||
private DateTime? _selectedDate;
|
||||
private DataTable _resultTable = new();
|
||||
private string _statusMessage = "未连接";
|
||||
private int _pageIndex = 1;
|
||||
private int _pageSize = 50;
|
||||
private long _totalCount;
|
||||
private bool IsInitiated = false;
|
||||
#endregion
|
||||
|
||||
public RecordViewModel(IContainerExtension container) : base(container)
|
||||
{
|
||||
_globalInfo = container.Resolve<GlobalInfo>();
|
||||
LoadedCommand = new DelegateCommand(LoadTables);
|
||||
RefreshTablesCommand = new DelegateCommand(LoadTables);
|
||||
LoadedCommand = new DelegateCommand(LoadFilterOptions);
|
||||
QueryCommand = new DelegateCommand(() => { PageIndex = 1; Query(); });
|
||||
ClearFilterCommand = new DelegateCommand(ClearFilter);
|
||||
FirstPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex = 1; Query(); } });
|
||||
PrevPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex--; Query(); } });
|
||||
NextPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex++; Query(); } });
|
||||
@@ -158,105 +170,136 @@ namespace MonitorModule.ViewModels
|
||||
_scope?.Dispose();
|
||||
}
|
||||
|
||||
#region 数据库操作
|
||||
private SqlSugarClient CreateClient()
|
||||
#region 筛选条件加载
|
||||
/// <summary>
|
||||
/// 加载筛选条件:监测项通过反射发现(与 MonitorViewModel 一致),台架从数据库查询。
|
||||
/// </summary>
|
||||
private void LoadFilterOptions()
|
||||
{
|
||||
return new SqlSugarClient(new ConnectionConfig
|
||||
{
|
||||
DbType = SqlSugar.DbType.Sqlite,
|
||||
ConnectionString = ConnStr,
|
||||
IsAutoCloseConnection = true,
|
||||
InitKeyType = InitKeyType.Attribute
|
||||
});
|
||||
// 监测项:反射发现当前作用域所有带 [Monitorable] 的设备方法
|
||||
DiscoverMonitorNames();
|
||||
|
||||
Query();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取数据库中的所有用户表。数据库不存在时不报错,仅在状态栏提示。
|
||||
/// 反射扫描当前作用域 DeviceManager 中所有带 [Monitorable] 特性的设备方法,
|
||||
/// 生成与 MonitorViewModel 完全一致的 DisplayName 列表,填充到下拉框。
|
||||
/// 这样即使数据库还没数据,用户也能看到所有可筛选的监测项。
|
||||
/// </summary>
|
||||
private void LoadTables()
|
||||
private void DiscoverMonitorNames()
|
||||
{
|
||||
MonitorNames = new ObservableCollection<string>();
|
||||
|
||||
if (_scope == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(DbFolder)) Directory.CreateDirectory(DbFolder);
|
||||
if (!File.Exists(DbPath))
|
||||
var deviceManager = _scope.Resolve<DeviceManager>();
|
||||
if (deviceManager?.DeviceMap == null || deviceManager.DeviceMap.Count == 0)
|
||||
{
|
||||
TableNames = new ObservableCollection<string>();
|
||||
SelectedTable = null;
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
StatusMessage = $"数据库尚未创建:{DbPath}";
|
||||
StatusMessage = "当前工位无可用设备";
|
||||
return;
|
||||
}
|
||||
|
||||
using var db = CreateClient();
|
||||
var tables = db.DbMaintenance.GetTableInfoList(false)
|
||||
.Select(t => t.Name)
|
||||
.OrderBy(n => n)
|
||||
.ToList();
|
||||
// 构建 设备实例 → 硬件指纹 的反向查找表
|
||||
var instanceToFp = new Dictionary<object, string>(ReferenceEqualityComparer.Instance);
|
||||
foreach (var poolEntry in _globalInfo.HardwarePool)
|
||||
{
|
||||
var lazy = poolEntry.Value;
|
||||
if (lazy.IsValueCreated && lazy.Value != null)
|
||||
instanceToFp[lazy.Value] = poolEntry.Key;
|
||||
}
|
||||
|
||||
TableNames = new ObservableCollection<string>(tables);
|
||||
if (tables.Count == 0)
|
||||
var names = new List<string>();
|
||||
|
||||
foreach (var kvp in deviceManager.DeviceMap)
|
||||
{
|
||||
SelectedTable = null;
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
StatusMessage = "数据库已连接,但暂无数据表";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = $"已连接:{DbPath}({tables.Count} 张表)";
|
||||
if (string.IsNullOrEmpty(SelectedTable) || !tables.Contains(SelectedTable))
|
||||
string deviceName = kvp.Key;
|
||||
var device = kvp.Value;
|
||||
var deviceType = device.GetType();
|
||||
|
||||
// 只列出在硬件指纹池中注册的设备
|
||||
if (!instanceToFp.ContainsKey(device))
|
||||
continue;
|
||||
|
||||
var methods = deviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m =>
|
||||
{
|
||||
if (m.GetCustomAttribute<MonitorableAttribute>() == null) return false;
|
||||
if (m.ReturnType != typeof(Task<string>)) return false;
|
||||
var parms = m.GetParameters();
|
||||
return parms.Length == 0 ||
|
||||
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
|
||||
});
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
SelectedTable = tables[0]; // setter 会触发 Query
|
||||
}
|
||||
else
|
||||
{
|
||||
Query();
|
||||
var attr = method.GetCustomAttribute<MonitorableAttribute>();
|
||||
string displayName = !string.IsNullOrEmpty(attr?.Description)
|
||||
? $"{deviceName}.{attr.Description}"
|
||||
: $"{deviceName}.{method.Name}";
|
||||
names.Add(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
names.Sort();
|
||||
MonitorNames = new ObservableCollection<string>(names);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope,$"加载数据表失败:{ex.Message}");
|
||||
StatusMessage = $"加载失败:{ex.Message}";
|
||||
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope, $"反射发现监测项失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>清除所有筛选条件</summary>
|
||||
private void ClearFilter()
|
||||
{
|
||||
SelectedMonitorName = null;
|
||||
SelectedScope = "全部";
|
||||
SelectedDate = null;
|
||||
PageIndex = 1;
|
||||
Query();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 查询
|
||||
/// <summary>
|
||||
/// 执行分页查询。WHERE 为空则查询全部。
|
||||
/// 按当前筛选条件分页查询 MonitorValueEntity。
|
||||
/// </summary>
|
||||
private void Query()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath))
|
||||
{
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var db = CreateClient();
|
||||
string table = $"[{SelectedTable}]";
|
||||
string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause;
|
||||
var db = SqlSugarContext.DbContext;
|
||||
|
||||
// COUNT 总数
|
||||
var countSql = $"SELECT COUNT(*) FROM {table} WHERE {where}";
|
||||
TotalCount = db.Ado.GetLong(countSql);
|
||||
// 预计算日期范围,避免在表达式树中访问可空属性
|
||||
DateTime? dateStart = SelectedDate?.Date;
|
||||
DateTime? dateEnd = dateStart?.AddDays(1);
|
||||
|
||||
var query = db.Queryable<MonitorValueEntity>()
|
||||
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
|
||||
.WhereIF(!string.IsNullOrEmpty(SelectedScope) && SelectedScope != "全部", x => x.Scope == SelectedScope)
|
||||
.WhereIF(dateStart.HasValue, x => x.CreateTime >= dateStart && x.CreateTime < dateEnd!.Value)
|
||||
.OrderBy(x => x.CreateTime, OrderByType.Desc);
|
||||
|
||||
TotalCount = query.Count();
|
||||
|
||||
// 修正越界
|
||||
if (PageIndex > TotalPages) PageIndex = TotalPages;
|
||||
if (PageIndex < 1) PageIndex = 1;
|
||||
|
||||
int offset = (PageIndex - 1) * PageSize;
|
||||
var dataSql = $"SELECT * FROM {table} WHERE {where} LIMIT {PageSize} OFFSET {offset}";
|
||||
ResultTable = db.Ado.GetDataTable(dataSql);
|
||||
int skip = (PageIndex - 1) * PageSize;
|
||||
ResultTable = query
|
||||
.Skip(skip)
|
||||
.Take(PageSize)
|
||||
.ToDataTable();
|
||||
|
||||
StatusMessage = $"表 [{SelectedTable}] 共 {TotalCount} 行 第 {PageIndex}/{TotalPages} 页";
|
||||
StatusMessage = $"共 {TotalCount} 行 第 {PageIndex}/{TotalPages} 页";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify(TestStatus,$"查询失败:{ex.Message}");
|
||||
LoggerHelper.ErrorWithNotify(TestStatus, $"查询失败:{ex.Message}");
|
||||
StatusMessage = $"查询失败:{ex.Message}";
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
@@ -266,36 +309,42 @@ namespace MonitorModule.ViewModels
|
||||
|
||||
#region 导出 CSV
|
||||
/// <summary>
|
||||
/// 按当前 WHERE 条件导出全部结果到 CSV(不分页)。
|
||||
/// 按当前筛选条件导出全部结果到 CSV(不分页)。
|
||||
/// </summary>
|
||||
private void ExportCsv()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath))
|
||||
{
|
||||
StatusMessage = "无可导出数据";
|
||||
return;
|
||||
}
|
||||
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||
FileName = $"{SelectedTable}_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
|
||||
Title = $"导出 [{SelectedTable}] 为 CSV"
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
|
||||
try
|
||||
{
|
||||
using var db = CreateClient();
|
||||
string table = $"[{SelectedTable}]";
|
||||
string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause;
|
||||
var sql = $"SELECT * FROM {table} WHERE {where}";
|
||||
var dt = db.Ado.GetDataTable(sql);
|
||||
var db = SqlSugarContext.DbContext;
|
||||
|
||||
DateTime? dateStart = SelectedDate?.Date;
|
||||
DateTime? dateEnd = dateStart?.AddDays(1);
|
||||
|
||||
var query = db.Queryable<MonitorValueEntity>()
|
||||
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
|
||||
.WhereIF(!string.IsNullOrEmpty(SelectedScope) && SelectedScope != "全部", x => x.Scope == SelectedScope)
|
||||
.WhereIF(dateStart.HasValue, x => x.CreateTime >= dateStart && x.CreateTime < dateEnd!.Value)
|
||||
.OrderBy(x => x.CreateTime, OrderByType.Desc);
|
||||
|
||||
var dt = query.ToDataTable();
|
||||
if (dt.Rows.Count == 0)
|
||||
{
|
||||
StatusMessage = "无可导出数据";
|
||||
return;
|
||||
}
|
||||
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||
FileName = $"MonitorData_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
|
||||
Title = "导出监测数据为 CSV"
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
|
||||
WriteCsv(dlg.FileName, dt);
|
||||
|
||||
StatusMessage = $"导出完成:{dlg.FileName}({dt.Rows.Count} 行)";
|
||||
LoggerHelper.InfoWithNotify(TestStatus, $"工位 [{TestStatus}] 导出 [{SelectedTable}] 至 {dlg.FileName},共 {dt.Rows.Count} 行");
|
||||
LoggerHelper.InfoWithNotify(TestStatus, $"工位 [{TestStatus}] 导出监测数据至 {dlg.FileName},共 {dt.Rows.Count} 行");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -333,7 +382,6 @@ namespace MonitorModule.ViewModels
|
||||
if (string.IsNullOrEmpty(TestStatus)) return;
|
||||
_globalInfo.CurrentScope = TestStatus;
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -341,14 +389,14 @@ namespace MonitorModule.ViewModels
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
base.OnNavigatedTo(navigationContext);
|
||||
if (!IsInitiated&&navigationContext.Parameters.ContainsKey("Name"))
|
||||
if (!IsInitiated && navigationContext.Parameters.ContainsKey("Name"))
|
||||
{
|
||||
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
|
||||
_scope = _globalInfo.ScopeDic[TestStatus];
|
||||
_scopedContext = _scope.Resolve<ScopedContext>();
|
||||
IsInitiated = true;
|
||||
}
|
||||
LoadTables();
|
||||
LoadFilterOptions();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
FontSize="20" FontWeight="Bold"
|
||||
Margin="4,0,0,8"/>
|
||||
|
||||
<!-- 工具栏:表选择 / WHERE 条件 / 查询 / 刷新表 / 导出 -->
|
||||
<!-- 工具栏:监测项筛选 / 台架筛选 / 日期筛选 / 查询 / 清除 / 导出 -->
|
||||
<Border Grid.Row="1"
|
||||
Background="White"
|
||||
BorderBrush="#DDD" BorderThickness="1"
|
||||
@@ -47,57 +47,71 @@
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="180"/>
|
||||
<ColumnDefinition Width="200"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 监测项 -->
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="表:"
|
||||
Text="监测项:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,4,0"/>
|
||||
<ComboBox Grid.Column="1"
|
||||
materialDesign:HintAssist.Hint="数据表"
|
||||
ItemsSource="{Binding TableNames}"
|
||||
SelectedItem="{Binding SelectedTable}"
|
||||
materialDesign:HintAssist.Hint="全部"
|
||||
ItemsSource="{Binding MonitorNames}"
|
||||
SelectedItem="{Binding SelectedMonitorName}"
|
||||
IsEditable="True"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<!-- 台架 -->
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="WHERE:"
|
||||
Text="台架:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,4,0"/>
|
||||
<TextBox Grid.Column="3"
|
||||
Text="{Binding WhereClause, UpdateSourceTrigger=PropertyChanged}"
|
||||
VerticalAlignment="Center"
|
||||
materialDesign:HintAssist.Hint="条件筛选"
|
||||
ToolTip="例如:Status='OK' AND Id>10。留空表示查询全部">
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Enter" Command="{Binding QueryCommand}"/>
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
<ComboBox Grid.Column="3"
|
||||
materialDesign:HintAssist.Hint="全部"
|
||||
ItemsSource="{Binding ScopeNames}"
|
||||
SelectedItem="{Binding SelectedScope}"
|
||||
IsEditable="True"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<Button Grid.Column="4"
|
||||
<!-- 日期 -->
|
||||
<TextBlock Grid.Column="4"
|
||||
Text="日期:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,4,0"/>
|
||||
<DatePicker Grid.Column="5"
|
||||
SelectedDate="{Binding SelectedDate}"
|
||||
materialDesign:HintAssist.Hint="全部"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<!-- 按钮 -->
|
||||
<Button Grid.Column="6"
|
||||
Content="查询"
|
||||
Command="{Binding QueryCommand}"
|
||||
Padding="12,4" Margin="8,0,0,0"/>
|
||||
<Button Grid.Column="5"
|
||||
Content="刷新表"
|
||||
Command="{Binding RefreshTablesCommand}"
|
||||
Padding="12,4" Margin="12,0,0,0"/>
|
||||
<Button Grid.Column="7"
|
||||
Content="清除"
|
||||
Command="{Binding ClearFilterCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"/>
|
||||
<Button Grid.Column="6"
|
||||
<Button Grid.Column="8"
|
||||
Content="导出 CSV"
|
||||
Command="{Binding ExportCsvCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- DataGrid:自动生成列,绑 DataTable.DefaultView 直显示 -->
|
||||
<!-- DataGrid:显式列定义,隐藏 Id / IsDel -->
|
||||
<DataGrid Grid.Row="2"
|
||||
ItemsSource="{Binding ResultTable}"
|
||||
AutoGenerateColumns="True"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
@@ -106,7 +120,14 @@
|
||||
AlternatingRowBackground="#F9FAFB"
|
||||
RowBackground="White"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"/>
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="监测项" Binding="{Binding MonitorName}" Width="*"/>
|
||||
<DataGridTextColumn Header="监测值" Binding="{Binding MonitorValue}" Width="120"/>
|
||||
<DataGridTextColumn Header="台架" Binding="{Binding Scope}" Width="120"/>
|
||||
<DataGridTextColumn Header="记录时间" Binding="{Binding CreateTime, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="180"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<Border Grid.Row="3"
|
||||
|
||||
400
USBCANFD/USBCANFD.cs
Normal file
400
USBCANFD/USBCANFD.cs
Normal file
@@ -0,0 +1,400 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using USBCANFD;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class ZLGCANFD : IDisposable
|
||||
{
|
||||
// 硬件设备与通道句柄
|
||||
private IntPtr _deviceHandle = IntPtr.Zero;
|
||||
private readonly IntPtr[] _channelHandles;
|
||||
|
||||
// 【核心修改】每个通道分配独立的 DBC 引擎句柄与锁,彻底解决台架间DBC冲突与锁竞争
|
||||
private readonly uint[] _dbcHandles;
|
||||
private readonly bool[] _isDbcLoadedArray;
|
||||
private readonly object[] _channelLocks; // 通道级细粒度锁
|
||||
|
||||
// 异步高性能接收线程控制
|
||||
private volatile bool _isRunning = false;
|
||||
private readonly List<Thread> _receiveThreads = new List<Thread>();
|
||||
|
||||
// 动态硬件参数
|
||||
private readonly uint _deviceType; // 43: USBCANFD-400U
|
||||
private readonly uint _deviceIndex; // 设备索引
|
||||
private readonly int _maxChannels; // 动态通道数
|
||||
|
||||
/// <summary>
|
||||
/// 事件:当接收到 CAN/CANFD 报文并且通过 DBC 成功解析后触发
|
||||
/// 参数:uint 通道号 (0,1,2,3), ZDBC.DBCMessage 解析后的DBC消息结构体
|
||||
/// </summary>
|
||||
public event Action<uint, ZDBC.DBCMessage>? OnDbcMessageDecoded;
|
||||
|
||||
public ZLGCANFD(uint deviceType = 43, uint deviceIndex = 0, int maxChannels = 4)
|
||||
{
|
||||
_deviceType = deviceType;
|
||||
_deviceIndex = deviceIndex;
|
||||
_maxChannels = maxChannels;
|
||||
|
||||
// 初始化通道相关状态数组
|
||||
_channelHandles = new IntPtr[_maxChannels];
|
||||
_dbcHandles = new uint[_maxChannels];
|
||||
_isDbcLoadedArray = new bool[_maxChannels];
|
||||
_channelLocks = new object[_maxChannels];
|
||||
|
||||
for (int i = 0; i < _maxChannels; i++)
|
||||
{
|
||||
_channelHandles[i] = IntPtr.Zero;
|
||||
_dbcHandles[i] = 0;
|
||||
_isDbcLoadedArray[i] = false;
|
||||
_channelLocks[i] = new object();
|
||||
}
|
||||
}
|
||||
|
||||
#region 1. 硬件连接与通道初始化
|
||||
|
||||
/// <summary>
|
||||
/// 仅仅打开设备,不做具体的通道波特率配置(留给具体的台架去分别配置)
|
||||
/// </summary>
|
||||
public virtual bool 打开设备()
|
||||
{
|
||||
if (_deviceHandle != IntPtr.Zero) return true;
|
||||
_deviceHandle = ZLGCAN.ZCAN_OpenDevice(_deviceType, _deviceIndex, 0);
|
||||
return _deviceHandle != IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 【核心修改】针对特定通道进行参数初始化并启动
|
||||
/// </summary>
|
||||
public virtual bool 初始化并启动通道(uint 通道号, string abitBaud = "500000", string dbitBaud = "2000000", bool 开启终端电阻 = true)
|
||||
{
|
||||
if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("请先调用 '打开设备()' 才能初始化通道。");
|
||||
if (通道号 >= _maxChannels) return false;
|
||||
|
||||
lock (_channelLocks[通道号])
|
||||
{
|
||||
// 如果已经启动过,先复位
|
||||
if (_channelHandles[通道号] != IntPtr.Zero)
|
||||
{
|
||||
ZLGCAN.ZCAN_ResetCAN(_channelHandles[通道号]);
|
||||
}
|
||||
|
||||
// 1. 设置该通道专属的仲裁域与数据域波特率
|
||||
if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_abit_baud_rate", abitBaud) != 1) return false;
|
||||
if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_dbit_baud_rate", dbitBaud) != 1) return false;
|
||||
|
||||
// 2. 设置该通道专属内部终端电阻状态
|
||||
string resistanceStr = 开启终端电阻 ? "1" : "0";
|
||||
if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/initenal_resistance", resistanceStr) != 1) return false;
|
||||
|
||||
// 3. 规范配置通道结构体
|
||||
ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG config = new ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG();
|
||||
config.can_type = 1; // 1 代表 CANFD 模式
|
||||
config.config.canfd.mode = 0; // 0 代表正常工作模式
|
||||
|
||||
IntPtr pConfig = Marshal.AllocHGlobal(Marshal.SizeOf(config));
|
||||
Marshal.StructureToPtr(config, pConfig, true);
|
||||
_channelHandles[通道号] = ZLGCAN.ZCAN_InitCAN(_deviceHandle, 通道号, pConfig);
|
||||
Marshal.FreeHGlobal(pConfig);
|
||||
|
||||
if (_channelHandles[通道号] == IntPtr.Zero) return false;
|
||||
|
||||
// 4. 启动 CAN 通道
|
||||
if (ZLGCAN.ZCAN_StartCAN(_channelHandles[通道号]) != 1) return false;
|
||||
|
||||
// 5. 为该通道启动专属的独立后台高性能轮询接收线程(如果尚未启动轮询)
|
||||
if (!_isRunning) _isRunning = true;
|
||||
|
||||
int chnIdx = (int)通道号;
|
||||
Thread rxThread = new Thread(() => 接收轮询核心(_channelHandles[chnIdx], (uint)chnIdx))
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = $"ZLGCANFD_Dev{_deviceIndex}_CH{chnIdx}_RxThread"
|
||||
};
|
||||
_receiveThreads.Add(rxThread);
|
||||
rxThread.Start();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void 关闭CAN卡设备()
|
||||
{
|
||||
_isRunning = false;
|
||||
Thread.Sleep(50); // 确保轮询线程安全退出
|
||||
_receiveThreads.Clear();
|
||||
|
||||
// 动态复位所有通道并释放DBC
|
||||
for (uint i = 0; i < _maxChannels; i++)
|
||||
{
|
||||
lock (_channelLocks[i])
|
||||
{
|
||||
if (_channelHandles[i] != IntPtr.Zero)
|
||||
{
|
||||
ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]);
|
||||
_channelHandles[i] = IntPtr.Zero;
|
||||
}
|
||||
释放通道DBC(i);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭设备主句柄
|
||||
if (_deviceHandle != IntPtr.Zero)
|
||||
{
|
||||
ZLGCAN.ZCAN_CloseDevice(_deviceHandle);
|
||||
_deviceHandle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. DBC 矩阵文件操作
|
||||
|
||||
/// <summary>
|
||||
/// 【核心修改】指定通道加载特定的 DBC 文件(支持不同通道加载不同的DBC矩阵)
|
||||
/// </summary>
|
||||
public virtual bool 加载通道DBC文件(uint 通道号, string dbcFilePath)
|
||||
{
|
||||
if (通道号 >= _maxChannels) return false;
|
||||
|
||||
lock (_channelLocks[通道号])
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isDbcLoadedArray[通道号]) return true;
|
||||
|
||||
uint chDbcHandle = ZDBC.ZDBC_Init();
|
||||
if (chDbcHandle == 0) return false;
|
||||
|
||||
IntPtr ptrPath = Marshal.StringToHGlobalAnsi(dbcFilePath);
|
||||
bool success = ZDBC.ZDBC_LoadFile(chDbcHandle, ptrPath);
|
||||
Marshal.FreeHGlobal(ptrPath);
|
||||
|
||||
if (success)
|
||||
{
|
||||
_dbcHandles[通道号] = chDbcHandle;
|
||||
_isDbcLoadedArray[通道号] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ZDBC.ZDBC_Release(chDbcHandle);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void 释放通道DBC(uint 通道号)
|
||||
{
|
||||
if (通道号 >= _maxChannels) return;
|
||||
|
||||
lock (_channelLocks[通道号])
|
||||
{
|
||||
if (_isDbcLoadedArray[通道号] && _dbcHandles[通道号] != 0)
|
||||
{
|
||||
ZDBC.ZDBC_Release(_dbcHandles[通道号]);
|
||||
_dbcHandles[通道号] = 0;
|
||||
_isDbcLoadedArray[通道号] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. 轮询接收与实时自动化 DBC 解码
|
||||
|
||||
private void 接收轮询核心(IntPtr channelHandle, uint channelIndex)
|
||||
{
|
||||
const int bufferSize = 100;
|
||||
int canStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Receive_Data));
|
||||
int canfdStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_ReceiveFD_Data));
|
||||
int dbcMsgSize = Marshal.SizeOf(typeof(ZDBC.DBCMessage));
|
||||
|
||||
IntPtr ptrCanBuffer = Marshal.AllocHGlobal(canStructSize * bufferSize);
|
||||
IntPtr ptrCanFDBuffer = Marshal.AllocHGlobal(canfdStructSize * bufferSize);
|
||||
IntPtr ptrDbcMsg = Marshal.AllocHGlobal(dbcMsgSize);
|
||||
|
||||
try
|
||||
{
|
||||
while (_isRunning)
|
||||
{
|
||||
bool currentTurnHasData = false;
|
||||
|
||||
// 1. 自动提取并解析经典 CAN 帧
|
||||
uint canNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 0);
|
||||
if (canNum > 0)
|
||||
{
|
||||
uint actualRecv = ZLGCAN.ZCAN_Receive(channelHandle, ptrCanBuffer, bufferSize, 5);
|
||||
for (int i = 0; i < actualRecv; i++)
|
||||
{
|
||||
IntPtr framePtr = IntPtr.Add(ptrCanBuffer, i * canStructSize);
|
||||
|
||||
// 【通道级业务隔离隔离】只锁定当前通道的DBC句柄进行解码,高并发完全不卡顿
|
||||
lock (_channelLocks[channelIndex])
|
||||
{
|
||||
if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 0))
|
||||
{
|
||||
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
|
||||
OnDbcMessageDecoded?.Invoke(channelIndex, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
currentTurnHasData = true;
|
||||
}
|
||||
|
||||
// 2. 自动提取并解析高速 CANFD 帧
|
||||
uint canfdNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 1);
|
||||
if (canfdNum > 0)
|
||||
{
|
||||
uint actualRecvFd = ZLGCAN.ZCAN_ReceiveFD(channelHandle, ptrCanFDBuffer, bufferSize, 5);
|
||||
for (int i = 0; i < actualRecvFd; i++)
|
||||
{
|
||||
IntPtr framePtr = IntPtr.Add(ptrCanFDBuffer, i * canfdStructSize);
|
||||
|
||||
lock (_channelLocks[channelIndex])
|
||||
{
|
||||
if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 1))
|
||||
{
|
||||
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
|
||||
OnDbcMessageDecoded?.Invoke(channelIndex, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
currentTurnHasData = true;
|
||||
}
|
||||
|
||||
if (!currentTurnHasData)
|
||||
{
|
||||
Thread.Sleep(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(ptrCanBuffer);
|
||||
Marshal.FreeHGlobal(ptrCanFDBuffer);
|
||||
Marshal.FreeHGlobal(ptrDbcMsg);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. DBC 自动打包与智能报文发送
|
||||
|
||||
public virtual bool 发送DBC定义报文(uint 通道号, uint 帧ID, Action<ZDBC.DBCMessage> 配置信号动作, int 是否使用CANFD = 1)
|
||||
{
|
||||
if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
|
||||
if (!_isDbcLoadedArray[通道号]) throw new InvalidOperationException($"通道 {通道号} 的 DBC 协议未加载,无法执行打包发送指令。");
|
||||
|
||||
IntPtr ptrDbcMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage)));
|
||||
IntPtr ptrCount = Marshal.AllocHGlobal(sizeof(uint));
|
||||
Marshal.WriteInt32(ptrCount, 1);
|
||||
|
||||
try
|
||||
{
|
||||
// 使用通道级锁保护当前通道发送
|
||||
lock (_channelLocks[通道号])
|
||||
{
|
||||
uint chDbcHandle = _dbcHandles[通道号];
|
||||
|
||||
if (!ZDBC.ZDBC_GetMessageById(chDbcHandle, 帧ID, ptrDbcMsg)) return false;
|
||||
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
|
||||
|
||||
配置信号动作(msg);
|
||||
Marshal.StructureToPtr(msg, ptrDbcMsg, true);
|
||||
|
||||
if (是否使用CANFD == 0)
|
||||
{
|
||||
IntPtr ptrCanFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.can_frame)));
|
||||
if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFrame, ptrCount, ptrDbcMsg, 0)) return false;
|
||||
|
||||
ZLGCAN.can_frame canFrame = (ZLGCAN.can_frame)Marshal.PtrToStructure(ptrCanFrame, typeof(ZLGCAN.can_frame));
|
||||
canFrame.__pad |= 0x20;
|
||||
|
||||
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = canFrame, transmit_type = 0 };
|
||||
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
|
||||
Marshal.StructureToPtr(txData, pTx, true);
|
||||
|
||||
uint sendResult = ZLGCAN.ZCAN_Transmit(_channelHandles[通道号], pTx, 1);
|
||||
|
||||
Marshal.FreeHGlobal(ptrCanFrame);
|
||||
Marshal.FreeHGlobal(pTx);
|
||||
return sendResult == 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
IntPtr ptrCanFDFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.canfd_frame)));
|
||||
if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFDFrame, ptrCount, ptrDbcMsg, 1)) return false;
|
||||
|
||||
ZLGCAN.canfd_frame canfdFrame = (ZLGCAN.canfd_frame)Marshal.PtrToStructure(ptrCanFDFrame, typeof(ZLGCAN.canfd_frame));
|
||||
canfdFrame.flags |= 0x20;
|
||||
|
||||
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = canfdFrame, transmit_type = 0 };
|
||||
IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
|
||||
Marshal.StructureToPtr(txFdData, pTxFd, true);
|
||||
|
||||
uint sendResult = ZLGCAN.ZCAN_TransmitFD(_channelHandles[通道号], pTxFd, 1);
|
||||
|
||||
Marshal.FreeHGlobal(ptrCanFDFrame);
|
||||
Marshal.FreeHGlobal(pTxFd);
|
||||
return sendResult == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(ptrDbcMsg);
|
||||
Marshal.FreeHGlobal(ptrCount);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 5. 辅助工具子系统
|
||||
|
||||
/// <summary>
|
||||
/// 【核心修改】计算物理值也需要传入对应通道,使用通道专属的DBC句柄进行计算
|
||||
/// </summary>
|
||||
public ulong 物理值转原始寄存器值(uint 通道号, ZDBC.DBCSignal 信号定义, double 实际物理值)
|
||||
{
|
||||
if (通道号 >= _maxChannels) return 0;
|
||||
|
||||
IntPtr pSignal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal)));
|
||||
IntPtr pValue = Marshal.AllocHGlobal(sizeof(double));
|
||||
|
||||
try
|
||||
{
|
||||
Marshal.StructureToPtr(信号定义, pSignal, true);
|
||||
Marshal.StructureToPtr(实际物理值, pValue, true);
|
||||
lock (_channelLocks[通道号])
|
||||
{
|
||||
return ZDBC.ZDBC_CalcRawValue(pSignal, pValue);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(pSignal);
|
||||
Marshal.FreeHGlobal(pValue);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Browsable(false)]
|
||||
public void Dispose()
|
||||
{
|
||||
关闭CAN卡设备();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>false</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Interop.TSMasterAPI">
|
||||
<HintPath>..\CanDriver\bin\Debug\net8.0\Interop.TSMasterAPI.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
1150
USBCANFD/ZLGAPI.cs
Normal file
1150
USBCANFD/ZLGAPI.cs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user