From 559890c482f5973f06aafdfec9550fee5ba3c7f2 Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期一, 09 十一月 2026 17:02:15 +0800
Subject: [PATCH] feat: CAN 模拟器 TCP 化 + 多设备并发调试 + 后台生命周期管理

---
 CanSimulatorServer/CanSimulatorServer.Core/CanCommandExecutor.cs |  188 +++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 188 insertions(+), 0 deletions(-)

diff --git a/CanSimulatorServer/CanSimulatorServer.Core/CanCommandExecutor.cs b/CanSimulatorServer/CanSimulatorServer.Core/CanCommandExecutor.cs
new file mode 100644
index 0000000..0f2015c
--- /dev/null
+++ b/CanSimulatorServer/CanSimulatorServer.Core/CanCommandExecutor.cs
@@ -0,0 +1,188 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using CanSimulator;
+
+namespace CanSimulatorServer
+{
+    /// <summary>
+    /// 鏈嶅姟绔懡浠や覆鎵ц鍣細瀵规寕杞借妭鐐规墽琛� SEND/QUERY 鍛戒护锛岃涔変笌鍘� CanDriverAdapter
+    /// 淇濇寔涓�鑷达紙鍛戒护涓茬洿浼犲崗璁殑鏍稿績锛夈�傝繑鍥炲�肩害瀹氾細
+    /// OK 鍓嶇紑 = 鎴愬姛鍝嶅簲锛汦RR 鍓嶇紑 = 涓氬姟澶辫触锛涘叾浣� = 瑁告暟鎹紙鐢变細璇濆眰琛� OK: 鍓嶇紑锛夈��
+    /// </summary>
+    public sealed class CanCommandExecutor
+    {
+        private readonly CanNode _node;
+        private readonly CanBusSimulator _bus;
+
+        public CanCommandExecutor(CanNode node, CanBusSimulator bus)
+        {
+            _node = node ?? throw new ArgumentNullException(nameof(node));
+            _bus = bus ?? throw new ArgumentNullException(nameof(bus));
+        }
+
+        /// <summary>鎵ц涓�琛屽懡浠わ紙SEND:鈥� 鎴� QUERY:鈥︼級锛屽紓甯歌浆涓� ERR 鍝嶅簲锛屼笉鎶涘嚭銆�</summary>
+        public string Execute(string line)
+        {
+            try
+            {
+                if (line.StartsWith("SEND:", StringComparison.OrdinalIgnoreCase))
+                    return HandleSend(line);
+
+                if (line.StartsWith("QUERY:", StringComparison.OrdinalIgnoreCase))
+                {
+                    string query = line.Substring("QUERY:".Length);
+                    return HandleQuery(query);
+                }
+
+                return $"ERR:UNKNOWN_CMD:{line}";
+            }
+            catch (ArgumentException ex)
+            {
+                return $"ERR:BAD_FRAME:{ex.Message}";
+            }
+            catch (Exception ex)
+            {
+                return $"ERR:EXEC:{ex.Message}";
+            }
+        }
+
+        private string HandleSend(string line)
+        {
+            // SEND:frameId:data锛坉ata 鍐呭彲鍚┖鏍硷紝涓嶅弬涓庡垎娈碉級
+            var parts = line.Split(':');
+            if (parts.Length < 3)
+                throw new ArgumentException("SEND 鏍煎紡搴斾负 SEND:frameId:data");
+
+            uint frameId = CanFrame.ParseFrameId(parts[1]);
+            byte[] data = CanFrame.ParseData(string.Join(":", parts.Skip(2)));
+            if (data.Length > 8)
+                throw new ArgumentException($"鏁版嵁闀垮害瓒呴檺: {data.Length} 瀛楄妭锛堟渶澶� 8锛�");
+
+            var frame = new CanFrame
+            {
+                FrameId = frameId,
+                Format = frameId > 0x7FF ? CanFrameFormat.Extended : CanFrameFormat.Standard,
+                Type = CanFrameType.Data,
+                Data = data
+            };
+            _node.SendFrame(frame); // 瑙﹀彂鎬荤嚎璺敱锛堝叾瀹冭妭鐐规敹甯� 鈫� 鍚勮嚜鎺ㄩ�侊級涓庤嚜鍔ㄥ搷搴�
+            return $"OK:TX:{frame.FrameIdHex}:{frame.Dlc}";
+        }
+
+        private string HandleQuery(string query)
+        {
+            var parts = query.Split(':');
+            string op = parts[0].ToUpperInvariant();
+
+            switch (op)
+            {
+                case "RECV":
+                {
+                    if (_node.TryDequeueReceived(out var f) && f != null)
+                        return $"OK:RECV:{f.FrameIdHex}:{f.DataHex}:{f.Timestamp:HH:mm:ss.fff}";
+                    return "OK:RECV:EMPTY";
+                }
+                case "RECV_ALL":
+                {
+                    var mailbox = _node.PeekReceiveMailbox();
+                    _node.ClearReceiveMailbox();
+                    if (mailbox.Count == 0) return "OK:RECV_ALL:0";
+                    var items = mailbox.Select(f => $"{f.FrameIdHex}:{f.DataHex}:{f.Timestamp:HH:mm:ss.fff}");
+                    return $"OK:RECV_ALL:{mailbox.Count}:{string.Join("|", items)}";
+                }
+                case "STATUS":
+                    return "OK:STATUS:" + DescribeStatus();
+                case "FILTER":
+                {
+                    if (parts.Length < 3)
+                        throw new ArgumentException("FILTER 鏍煎紡搴斾负 FILTER:id:mask");
+                    uint fid = CanFrame.ParseFrameId(parts[1]);
+                    uint mask = CanFrame.ParseFrameId(parts[2]);
+                    _node.AddFilter(new AcceptanceFilter
+                    {
+                        FilterId = fid,
+                        Mask = mask,
+                        IsExtended = fid > 0x7FF
+                    });
+                    return $"OK:婊ゆ尝鍣ㄥ凡娣诲姞 ID=0x{fid:X3}, Mask=0x{mask:X3}";
+                }
+                case "FILTER_CLEAR":
+                    _node.ClearFilters();
+                    return "OK:婊ゆ尝鍣ㄥ凡娓呴櫎";
+                case "PROMISCUOUS":
+                    _node.SetPromiscuousMode();
+                    return "OK:娣锋潅妯″紡宸插惎鐢�";
+                case "BUS_ECHO":
+                {
+                    if (parts.Length < 2)
+                        return $"OK:BUS_ECHO:{(_bus.EchoResponderToSender ? "ON" : "OFF")}";
+                    string mode = parts[1].Trim().ToUpperInvariant();
+                    switch (mode)
+                    {
+                        case "ON": case "1": case "TRUE": case "ENABLE":
+                            _bus.EchoResponderToSender = true;
+                            return "OK:鎬荤嚎搴旂瓟鍣ㄥ凡寮�鍚紙鍥炴樉缁欏彂閫佽�咃級";
+                        case "OFF": case "0": case "FALSE": case "DISABLE":
+                            _bus.EchoResponderToSender = false;
+                            return "OK:鎬荤嚎搴旂瓟鍣ㄥ凡鍏抽棴";
+                        default:
+                            throw new ArgumentException($"BUS_ECHO 鍙傛暟閿欒: {mode}");
+                    }
+                }
+                case "AUTO_RESPONSE":
+                {
+                    if (parts.Length < 2)
+                        return $"OK:AUTO_RESPONSE:{(_node.AutoResponseEnabled ? "ON" : "OFF")}";
+                    string mode = parts[1].Trim().ToUpperInvariant();
+                    switch (mode)
+                    {
+                        case "ON": case "1": case "TRUE": case "ENABLE":
+                            _node.AutoResponseEnabled = true;
+                            return "OK:鑷姩鍝嶅簲妯″紡宸插惎鐢�";
+                        case "OFF": case "0": case "FALSE": case "DISABLE":
+                            _node.AutoResponseEnabled = false;
+                            return "OK:鑷姩鍝嶅簲妯″紡宸茬鐢�";
+                        default:
+                            throw new ArgumentException($"AUTO_RESPONSE 鍙傛暟閿欒: {mode}");
+                    }
+                }
+                case "RESPONSE_DELAY":
+                {
+                    if (parts.Length < 2)
+                        return $"OK:RESPONSE_DELAY:{_node.AutoResponseDelayMs}ms";
+                    if (!int.TryParse(parts[1].Trim(), out int d) || d < 0)
+                        throw new ArgumentException($"RESPONSE_DELAY 鍙傛暟閿欒: {parts[1]}");
+                    _node.AutoResponseDelayMs = d;
+                    return $"OK:鍝嶅簲寤惰繜宸茶缃负 {d}ms";
+                }
+                case "LOG":
+                {
+                    var log = _bus.GetBusLog(50);
+                    if (log.Count == 0) return "OK:LOG:EMPTY";
+                    var entries = log.Select(f =>
+                        $"[{f.Timestamp:HH:mm:ss.fff}]{f.SourceNodeId}>{f.FrameIdHex}:{f.DataHex}");
+                    return $"OK:LOG:{log.Count}:{string.Join("|", entries)}";
+                }
+                case "BUS_STATUS":
+                    return "OK:" + _bus.GetStatusSummary();
+                default:
+                    return $"ERR:UNKNOWN_CMD:{query}";
+            }
+        }
+
+        private string DescribeStatus()
+        {
+            string nodeStatus = _node.GetStatusSummary();          // Node={nodeId}, Active=鈥�, Rx=鈥�
+            string busStatus = _bus.GetStatusSummary();            // Bus=鈥�, State=鈥�, Nodes=鈥�
+            return $"{nodeStatus}|{busStatus}";
+        }
+
+        /// <summary>鎶婃帴鏀跺抚鏍煎紡鍖栦负 EV:RECV 鎺ㄩ�佽锛堟椂闂存埑涓庢暟鎹笉鍚啋鍙凤級銆�</summary>
+        public static string FormatPush(CanFrame frame)
+        {
+            string src = string.IsNullOrEmpty(frame.SourceNodeId) ? "0" : frame.SourceNodeId;
+            return $"EV:RECV:{frame.FrameIdHex}:{frame.DataHex}:{frame.Timestamp:HH.mm.ss.fff}:{src}";
+        }
+    }
+}

--
Gitblit v1.9.1