wenxiangchao
3 天以前 b544d5cccd76735e034c8055918a50927b437474
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
using System;
using System.Collections.Generic;
using System.Linq;
using CanSimulator;
 
namespace CanSimulatorServer
{
    /// <summary>
    /// 服务端命令串执行器:对挂载节点执行 SEND/QUERY 命令,语义与原 CanDriverAdapter
    /// 保持一致(命令串直传协议的核心)。返回值约定:
    /// OK 前缀 = 成功响应;ERR 前缀 = 业务失败;其余 = 裸数据(由会话层补 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(data 内可含空格,不参与分段)
            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}";
        }
    }
}