-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathhardware_interfaces.py
More file actions
273 lines (229 loc) · 8.74 KB
/
Copy pathhardware_interfaces.py
File metadata and controls
273 lines (229 loc) · 8.74 KB
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
"""Hardware communication adapters for the radar EVM and DCA1000."""
import socket
import time
from app_config import NetworkConfig, SerialPortConfig
class HardwareConnectionError(ConnectionError):
"""Raised when a radar capture interface cannot be initialized."""
class RadarCliCommandError(HardwareConnectionError):
"""Raised when the radar rejects a CLI configuration command."""
CLI_ERROR_MARKERS = (
"not recognized",
"error",
"failed",
"failure",
"invalid",
"exception",
)
def classify_cli_response(line):
"""Return the UI color for one radar CLI response line."""
normalized = line.strip().lower()
if any(marker in normalized for marker in CLI_ERROR_MARKERS):
return "red"
if "ignored" in normalized or "warning" in normalized:
return "red"
if normalized == "done" or normalized.endswith(" done"):
return "green"
if "skipped" in normalized or normalized.startswith("mmwdemo:/>"):
return "gray"
return "gray"
def is_radar_config_comment(line):
"""Identify comments and decorative separators that must not be sent."""
stripped = line.strip()
if not stripped:
return True
if stripped.startswith(("%", "#", "//")):
return True
return all(character in "*-=_" for character in stripped)
def select_preferred_cli_port(ports):
"""Choose the radar configuration UART from discovered serial ports.
TI XDS110 boards expose an Application/User UART, while CP2105-based
boards expose Enhanced and Standard ports. The Application/User or
Enhanced port is the CLI/configuration port.
"""
def score(port):
fields = (
getattr(port, "description", ""),
getattr(port, "manufacturer", ""),
getattr(port, "product", ""),
getattr(port, "interface", ""),
getattr(port, "hwid", ""),
)
text = " ".join(str(field or "") for field in fields).lower()
value = 0
if "application/user" in text or "application uart" in text:
value += 120
if "enhanced" in text or "增强" in text:
value += 110
if "xds110" in text:
value += 40
if "cp2105" in text or "10c4:ea70" in text:
value += 30
if "standard" in text or "data port" in text or "auxiliary" in text:
value -= 100
if "bluetooth" in text or "bthenum" in text or "蓝牙" in text:
value -= 200
return value
candidates = list(ports)
if not candidates:
return None
_, preferred = max(
enumerate(candidates), key=lambda item: (score(item[1]), -item[0])
)
return preferred if score(preferred) > 0 else None
class RadarCliClient:
"""Send CLI configuration commands to a TI radar evaluation module."""
def __init__(
self,
name,
cli_port,
baud_rate=None,
settings=None,
log_callback=None,
serial_factory=None,
):
if serial_factory is None:
import serial
serial_factory = serial.Serial
self.name = name
self.settings = settings or SerialPortConfig()
self.log_callback = log_callback
self.cli_port = serial_factory(
cli_port,
baudrate=baud_rate or self.settings.cli_baud_rate,
)
@property
def is_open(self):
return self.cli_port.is_open
def send_config(self, config_file_name):
with open(config_file_name, encoding="utf-8") as config_file:
for line in config_file:
stripped = line.strip()
if not stripped:
continue
if is_radar_config_comment(stripped):
self._log("注释: {}".format(stripped), "gray")
continue
self._send_line(stripped)
def _send_line(self, line):
command = line.strip()
reset_input = getattr(self.cli_port, "reset_input_buffer", None)
if reset_input is not None:
reset_input()
self.cli_port.write((command + "\n").encode())
self._log("发送: {}".format(command), "blue")
start_time = time.time()
response = b""
while time.time() - start_time < self.settings.response_timeout_seconds:
if self.cli_port.in_waiting > 0:
response += self.cli_port.read(self.cli_port.in_waiting)
if b"mmwDemo:/>" in response:
break
time.sleep(self.settings.line_delay_seconds)
response_text = response.decode(errors="ignore").strip()
if not response_text:
self._log("接收: 未收到雷达响应", "red")
raise RadarCliCommandError(
"命令 {!r} 未收到雷达响应".format(command)
)
has_command_error = False
for response_line in response_text.splitlines():
response_line = response_line.strip()
if not response_line:
continue
color = classify_cli_response(response_line)
self._log("接收: {}".format(response_line), color)
normalized = response_line.lower()
if any(marker in normalized for marker in CLI_ERROR_MARKERS):
has_command_error = True
time.sleep(self.settings.line_delay_seconds)
if has_command_error:
raise RadarCliCommandError(
"雷达拒绝命令 {!r}: {}".format(command, response_text)
)
return response_text
def _log(self, message, color):
if self.log_callback is not None:
self.log_callback(message, color)
else:
print(message)
def start_radar(self):
return self._send_line("sensorStart")
def stop_radar(self):
return self._send_line("sensorStop")
def close(self):
self.cli_port.close()
def disconnect(self):
try:
self.stop_radar()
finally:
self.close()
class Dca1000Controller:
"""Configure and control the DCA1000 FPGA over UDP."""
INITIAL_COMMANDS = ("9", "E", "3", "B", "5")
COMMAND_CODES = {
"3": 0x03,
"5": 0x05,
"6": 0x06,
"9": 0x09,
"B": 0x0B,
"E": 0x0E,
}
COMMAND_PAYLOADS = {
"3": (0x01020102031E).to_bytes(6, byteorder="big", signed=False),
"B": (0xC005350C0000).to_bytes(6, byteorder="big", signed=False),
}
def __init__(
self,
name,
config_address=None,
fpga_address=None,
settings=None,
):
settings = settings or NetworkConfig()
self.name = name
self.config_address = config_address or settings.host_address
self.fpga_address = fpga_address or settings.fpga_address
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.settimeout(settings.response_timeout_seconds)
try:
self.socket.bind(self.config_address)
for command in self.INITIAL_COMMANDS:
self._send_and_receive(command)
except OSError as error:
self.socket.close()
if getattr(error, "winerror", None) == 10049:
raise HardwareConnectionError(
"无法绑定DCA1000本机地址 {}:{};请先将采集网卡IPv4配置为该地址,"
"或修改 app_config.py 中的 NetworkConfig.host_address。".format(
*self.config_address
)
) from error
raise HardwareConnectionError(
"连接DCA1000失败(本机 {}:{},设备 {}:{}):{}".format(
*self.config_address, *self.fpga_address, error
)
) from error
except Exception:
self.socket.close()
raise
def _send_and_receive(self, command):
self.socket.sendto(self.build_command(command), self.fpga_address)
time.sleep(0.1)
self.socket.recvfrom(2048)
def close(self):
try:
self.socket.sendto(self.build_command("6"), self.fpga_address)
finally:
self.socket.close()
@classmethod
def build_command(cls, command):
try:
command_code = cls.COMMAND_CODES[command]
except KeyError:
raise ValueError("Unsupported DCA1000 command: {}".format(command))
header = (0xA55A).to_bytes(2, byteorder="little", signed=False)
footer = (0xEEAA).to_bytes(2, byteorder="little", signed=False)
code = command_code.to_bytes(2, byteorder="little", signed=False)
payload = cls.COMMAND_PAYLOADS.get(command, b"")
data_size = len(payload).to_bytes(2, byteorder="little", signed=False)
return header + code + data_size + payload + footer