增加通讯协议
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,350 @@
|
||||
#!/usr/bin/env python3
|
||||
"""车端 CAN 报文模拟器 / 上行抓包器(联调用,非工程代码)。
|
||||
|
||||
依据《洗消机器人遥控器通信协议20260810.xlsx》sheet RC(分类一/二/三,全部扩展帧、Intel 小端位序)。
|
||||
线格式:32 位帧头 0x5AA5FF00 + 32 位 canId + DLC(8) + 8 字节数据 + 校验和,共 18 字节,
|
||||
多字节字段按小端排列,与 comm/udpcommunication.cpp 的 UdpCanFrame 一致。
|
||||
|
||||
用法示例:
|
||||
# 向 UI 的 9001 端口周期发 VCU 反馈,看底盘页电量/电压/车速刷新
|
||||
python can_sender.py --soc 85 --volt 360 --speed 4.5
|
||||
|
||||
# 制造故障:MCU 码 61(电机缺相故障)+ 整系统通信异常(底盘BMS)
|
||||
python can_sender.py --mcu-fault 61 --comm-fault 2
|
||||
|
||||
# 洗消 + 气体检测仪反馈
|
||||
python can_sender.py --wash disinfect --level-a 88 --press 0.3 --gas-conc 250 --gas-type 2 --gas-level 2
|
||||
|
||||
# 反向:绑 9000 抓 UI 下发的控制指令并解出来
|
||||
python can_sender.py --listen
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
|
||||
HDR = 0x5AA5FF00
|
||||
|
||||
# 分类一 遥控器控制指令(上行,UI → 车端)
|
||||
ID_CHASSIS_CMD = 0x18208081
|
||||
ID_ARM_CMD1 = 0x18218081
|
||||
ID_ARM_CMD2 = 0x18228081
|
||||
ID_DECONTAM_CMD = 0x18238081
|
||||
ID_DECONTAM_DBG = 0x18248081
|
||||
ID_DETECTOR_CMD = 0x18258081
|
||||
ID_EMERGENCY = 0x18268081
|
||||
|
||||
# 分类二/三 VCU 状态反馈(下行,车端 → UI)
|
||||
ID_VEHICLE_FB1 = 0x18308180
|
||||
ID_VEHICLE_FB2 = 0x18318180
|
||||
ID_ARM_FB1 = 0x18328180
|
||||
ID_ARM_FB2 = 0x18338180
|
||||
ID_ARM_FB3 = 0x18348180
|
||||
ID_ARM_FB4 = 0x18358180
|
||||
ID_DECONTAM_FB1 = 0x18368180
|
||||
ID_DECONTAM_FB2 = 0x18378180
|
||||
ID_DETECTOR_FB1 = 0x18388180
|
||||
ID_DETECTOR_FB2 = 0x18398180
|
||||
ID_VEHICLE_FB3 = 0x183A8180
|
||||
ID_VEHICLE_FB4 = 0x183B8180
|
||||
|
||||
NAMES = {
|
||||
ID_CHASSIS_CMD: "Chassis_ControlCmd", ID_ARM_CMD1: "Arm_ControlCmd1",
|
||||
ID_ARM_CMD2: "Arm_ControlCmd2", ID_DECONTAM_CMD: "Decontam_ControlCmd",
|
||||
ID_DECONTAM_DBG: "Decontam_DebugCmd", ID_DETECTOR_CMD: "Detector_ControlCmd",
|
||||
ID_EMERGENCY: "Emergency_Cmd", ID_VEHICLE_FB1: "Vehicle_StatusFeedBack1",
|
||||
ID_VEHICLE_FB2: "Vehicle_StatusFeedBack2", ID_ARM_FB1: "Arm_StatusFeedBack1",
|
||||
ID_ARM_FB2: "Arm_StatusFeedBack2", ID_ARM_FB3: "Arm_StatusFeedBack3",
|
||||
ID_ARM_FB4: "Arm_StatusFeedBack4", ID_DECONTAM_FB1: "Decontam_StatusFeedBack1",
|
||||
ID_DECONTAM_FB2: "Decontam_StatusFeedBack2", ID_DETECTOR_FB1: "Detector_StatusFeedBack1",
|
||||
ID_DETECTOR_FB2: "Detector_StatusFeedBack2", ID_VEHICLE_FB3: "Vehicle_StatusFeedBack3",
|
||||
ID_VEHICLE_FB4: "Vehicle_StatusFeedBack4",
|
||||
}
|
||||
|
||||
|
||||
def set_bits(d, start, ln, v):
|
||||
"""Intel(小端):起始位即最低位序号,位序由低到高,可跨字节。"""
|
||||
v = int(v)
|
||||
for i in range(ln):
|
||||
b = start + i
|
||||
m = 1 << (b & 7)
|
||||
if v & (1 << i):
|
||||
d[b >> 3] |= m
|
||||
else:
|
||||
d[b >> 3] &= ~m & 0xFF
|
||||
|
||||
|
||||
def get_bits(d, start, ln):
|
||||
v = 0
|
||||
for i in range(ln):
|
||||
b = start + i
|
||||
if d[b >> 3] & (1 << (b & 7)):
|
||||
v |= 1 << i
|
||||
return v
|
||||
|
||||
|
||||
def get_signed(d, start, ln):
|
||||
raw = get_bits(d, start, ln)
|
||||
sign = 1 << (ln - 1)
|
||||
return (raw ^ sign) - sign
|
||||
|
||||
|
||||
def checksum(can_id, dlc, d):
|
||||
return (sum((can_id >> (8 * i)) & 0xFF for i in range(4)) + dlc + sum(d)) & 0xFF
|
||||
|
||||
|
||||
def pack(can_id, d):
|
||||
return struct.pack("<IIB8BB", HDR, can_id, 8, *d, checksum(can_id, 8, d))
|
||||
|
||||
|
||||
def clamp(v, lo, hi):
|
||||
return max(lo, min(hi, int(v)))
|
||||
|
||||
|
||||
# ===================== 下行:构造分类二/三 反馈帧 =====================
|
||||
def fb_vehicle1(a):
|
||||
d = bytearray(8)
|
||||
set_bits(d, 0, 16, clamp(round(a.volt * 10), 0, 0xFFFF)) # Total_voltage 精度 0.1
|
||||
set_bits(d, 16, 16, clamp(round(a.soc * 10), 0, 0xFFFF)) # SOC 精度 0.1
|
||||
set_bits(d, 32, 8, a.mcu_fault) # MCU_FaultCode
|
||||
set_bits(d, 40, 2, a.mcu_level) # MCU_FaultLevel
|
||||
set_bits(d, 45, 2, a.celllow_level) # UnderOver_FaultLevel
|
||||
set_bits(d, 47, 2, a.cellhigh_level) # CellOver_FaultLevel(跨字节)
|
||||
return d
|
||||
|
||||
|
||||
def fb_vehicle2(a):
|
||||
d = bytearray(8)
|
||||
set_bits(d, 22, 2, 1 if a.handbrake else 0) # Handbrake
|
||||
# Vehi_spd 16 位 0~500 × 0.01;其后字段整体后移 8 位
|
||||
set_bits(d, 24, 16, clamp(round(a.speed * 100), 0, 500)) # Vehi_spd
|
||||
set_bits(d, 48, 8, a.comm_fault) # All_Comm_FaultCode
|
||||
return d
|
||||
|
||||
|
||||
def fb_arm1(a):
|
||||
d = bytearray(8)
|
||||
set_bits(d, 0, 16, a.posx & 0xFFFF) # End_positionX (Signed mm)
|
||||
set_bits(d, 16, 16, a.posy & 0xFFFF)
|
||||
set_bits(d, 32, 16, a.posz & 0xFFFF)
|
||||
return d
|
||||
|
||||
|
||||
def fb_arm2(a):
|
||||
d = bytearray(8)
|
||||
set_bits(d, 0, 16, a.rx & 0xFFFF)
|
||||
set_bits(d, 16, 16, a.ry & 0xFFFF)
|
||||
set_bits(d, 32, 16, a.rz & 0xFFFF)
|
||||
set_bits(d, 48, 8, clamp(a.endspd, -128, 127) & 0xFF) # End_spd (Signed)
|
||||
return d
|
||||
|
||||
|
||||
def fb_arm3(a):
|
||||
d = bytearray(8)
|
||||
for i in range(4):
|
||||
set_bits(d, i * 16, 16, getattr(a, "j%d" % (i + 1)) & 0xFFFF)
|
||||
return d
|
||||
|
||||
|
||||
def fb_arm4(a):
|
||||
d = bytearray(8)
|
||||
set_bits(d, 0, 16, a.j5 & 0xFFFF)
|
||||
set_bits(d, 16, 16, a.j6 & 0xFFFF)
|
||||
return d
|
||||
|
||||
|
||||
def fb_decontam1(a):
|
||||
d = bytearray(8)
|
||||
set_bits(d, 0, 2, a.wash_mode) # 2=消除 3=消毒
|
||||
set_bits(d, 2, 1, 1 if a.wash_start else 0)
|
||||
set_bits(d, 3, 1, 1 if a.plunger else 0)
|
||||
set_bits(d, 4, 1, 1 if a.air else 0)
|
||||
set_bits(d, 5, 1, 1 if a.leaf else 0)
|
||||
set_bits(d, 8, 1, 1 if a.d1open else 0) # MBV_D1_Sta
|
||||
set_bits(d, 9, 1, 1 if a.d2open else 0)
|
||||
set_bits(d, 10, 1, 1 if a.sys_fault else 0)
|
||||
set_bits(d, 16, 8, clamp(a.level_a, 0, 255))
|
||||
set_bits(d, 24, 8, clamp(a.level_foam, 0, 255))
|
||||
set_bits(d, 32, 8, clamp(a.flow_a, 0, 255))
|
||||
set_bits(d, 40, 8, clamp(a.flow_b, 0, 255))
|
||||
set_bits(d, 48, 8, clamp(a.air_flow_fb, 0, 255))
|
||||
set_bits(d, 56, 8, clamp(a.expansion, 0, 255))
|
||||
return d
|
||||
|
||||
|
||||
def fb_decontam2(a):
|
||||
d = bytearray(8)
|
||||
set_bits(d, 0, 8, clamp(round(a.press * 10), 0, 255)) # Pipe_pressure 精度 0.1
|
||||
set_bits(d, 8, 8, clamp(round(a.air_press * 10), 0, 255)) # AirComp_pressure
|
||||
set_bits(d, 48, 8, a.air_fault) # AirComp_FaultCode
|
||||
set_bits(d, 56, 8, a.plunger_fault) # PlungerPump_FaultCode
|
||||
return d
|
||||
|
||||
|
||||
def fb_detector1(a):
|
||||
d = bytearray(8)
|
||||
set_bits(d, 0, 8, a.detector_sta)
|
||||
set_bits(d, 8, 8, a.gas_type) # Toxic_types 位组合
|
||||
set_bits(d, 16, 8, a.gas_level)
|
||||
set_bits(d, 24, 8, a.warn)
|
||||
return d
|
||||
|
||||
|
||||
def fb_detector2(a):
|
||||
d = bytearray(8)
|
||||
set_bits(d, 0, 40, clamp(a.gas_conc, 0, (1 << 40) - 1)) # Toxic_concentration 40 位
|
||||
return d
|
||||
|
||||
|
||||
FEEDBACK = [
|
||||
(ID_VEHICLE_FB1, fb_vehicle1), (ID_VEHICLE_FB2, fb_vehicle2),
|
||||
(ID_ARM_FB1, fb_arm1), (ID_ARM_FB2, fb_arm2),
|
||||
(ID_ARM_FB3, fb_arm3), (ID_ARM_FB4, fb_arm4),
|
||||
(ID_DECONTAM_FB1, fb_decontam1), (ID_DECONTAM_FB2, fb_decontam2),
|
||||
(ID_DETECTOR_FB1, fb_detector1), (ID_DETECTOR_FB2, fb_detector2),
|
||||
]
|
||||
|
||||
|
||||
# ===================== 上行:解析分类一 控制帧 =====================
|
||||
def describe(can_id, d):
|
||||
if can_id == ID_CHASSIS_CMD:
|
||||
return ("Chassis 模式Cmd=%d 使能=%d gear=%d(0后/2048中/4096前) "
|
||||
"目标速度=%.2fkm/h turning=%d(0左/2048中/4096右)"
|
||||
% (get_bits(d, 0, 1), get_bits(d, 3, 1), get_bits(d, 8, 16),
|
||||
get_bits(d, 24, 16) * 0.01, get_bits(d, 40, 16)))
|
||||
if can_id == ID_ARM_CMD1:
|
||||
return ("Arm1 模式=%d 使能=%d 上下=%d 左右=%d 前后=%d"
|
||||
% (get_bits(d, 0, 1), get_bits(d, 1, 1), get_bits(d, 8, 16),
|
||||
get_bits(d, 24, 16), get_bits(d, 40, 16)))
|
||||
if can_id == ID_ARM_CMD2:
|
||||
return ("Arm2 横滚=%d 俯仰=%d 偏航=%d 回位/暂停=0x%02X(0xAA回位/0x55暂停)"
|
||||
% (get_bits(d, 0, 16), get_bits(d, 16, 16), get_bits(d, 32, 16),
|
||||
get_bits(d, 48, 8)))
|
||||
if can_id == ID_DECONTAM_CMD:
|
||||
return ("Decontam 模式=%d(2消除/3消毒) 启停=%d 发泡=%d 泡沫比例=%d "
|
||||
"空压流量=%d 柱塞流量=%d"
|
||||
% (get_bits(d, 0, 2), get_bits(d, 2, 1), get_bits(d, 8, 8),
|
||||
get_bits(d, 16, 8), get_bits(d, 24, 8), get_bits(d, 32, 8)))
|
||||
if can_id == ID_DECONTAM_DBG:
|
||||
return ("DecontamDbg 调试=%d 柱塞上电=%d 空压上电=%d 叶片上电=%d D1=%d D2=%d "
|
||||
"空压rpm=%d 柱塞rpm=%d 叶片rpm=%d 启动=%d/%d/%d"
|
||||
% (get_bits(d, 0, 1), get_bits(d, 1, 1), get_bits(d, 2, 1), get_bits(d, 3, 1),
|
||||
get_bits(d, 4, 1), get_bits(d, 5, 1), get_bits(d, 8, 16),
|
||||
get_bits(d, 24, 16), get_bits(d, 40, 16),
|
||||
get_bits(d, 56, 1), get_bits(d, 57, 1), get_bits(d, 58, 1)))
|
||||
if can_id == ID_DETECTOR_CMD:
|
||||
return "Detector 指令=0x%02X(0xAA启动/0x55停止)" % get_bits(d, 0, 8)
|
||||
if can_id == ID_EMERGENCY:
|
||||
return "Emergency 急停=0x%02X(0x55急停/0正常)" % get_bits(d, 0, 8)
|
||||
return None
|
||||
|
||||
|
||||
def listen(host, port, once):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind((host, port))
|
||||
print("监听 %s:%d,解 UI 下发的控制帧(Ctrl-C 退出)" % (host, port))
|
||||
last = {}
|
||||
try:
|
||||
while True:
|
||||
data, addr = sock.recvfrom(4096)
|
||||
off = 0
|
||||
while off + 18 <= len(data):
|
||||
hdr, cid, dlc = struct.unpack_from("<IIB", data, off)
|
||||
if hdr == HDR and dlc == 8:
|
||||
body = data[off + 9:off + 17]
|
||||
cks = data[off + 17]
|
||||
if cks == checksum(cid, dlc, body):
|
||||
txt = describe(cid, body)
|
||||
if txt:
|
||||
# 周期 50ms,只在内容变化时打印,避免刷屏
|
||||
if once is None or last.get(cid) != body:
|
||||
last[cid] = bytes(body)
|
||||
print("%-16s %s" % (NAMES.get(cid, hex(cid)), txt))
|
||||
off += 18
|
||||
continue
|
||||
off += 1
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--host", default="127.0.0.1")
|
||||
p.add_argument("--port", type=int, default=9001, help="发送目标(UI 的 net/recv_port)")
|
||||
p.add_argument("--listen", action="store_true", help="改为绑端口抓 UI 上行控制帧")
|
||||
p.add_argument("--hz", type=float, default=10.0, help="反馈帧发送频率,默认 10Hz")
|
||||
p.add_argument("--count", type=int, default=0, help="0=一直发")
|
||||
|
||||
# 电池 / 底盘
|
||||
p.add_argument("--soc", type=float, default=85.0, help="电量 %")
|
||||
p.add_argument("--volt", type=float, default=360.0, help="总电压 V(精度 0.1)")
|
||||
p.add_argument("--speed", type=float, default=3.45, help="km/h,精度 0.01")
|
||||
p.add_argument("--no-handbrake", action="store_true")
|
||||
p.add_argument("--mcu-fault", type=int, default=0)
|
||||
p.add_argument("--mcu-level", type=int, default=0)
|
||||
p.add_argument("--celllow-level", type=int, default=0)
|
||||
p.add_argument("--cellhigh-level", type=int, default=0)
|
||||
p.add_argument("--comm-fault", type=int, default=0, help="整系统通信故障码 0~8")
|
||||
# 机械臂
|
||||
p.add_argument("--posx", type=int, default=1200)
|
||||
p.add_argument("--posy", type=int, default=300)
|
||||
p.add_argument("--posz", type=int, default=800)
|
||||
p.add_argument("--rx", type=int, default=0)
|
||||
p.add_argument("--ry", type=int, default=0)
|
||||
p.add_argument("--rz", type=int, default=0)
|
||||
p.add_argument("--endspd", type=int, default=0)
|
||||
for i in range(1, 7):
|
||||
p.add_argument("--j%d" % i, type=int, default=[30, 20, 0, 90, 45, 0][i - 1])
|
||||
# 洗消
|
||||
p.add_argument("--wash", choices=["clean", "disinfect"], default="clean")
|
||||
p.add_argument("--wash-start", action="store_true")
|
||||
p.add_argument("--plunger", action="store_true")
|
||||
p.add_argument("--air", action="store_true")
|
||||
p.add_argument("--leaf", action="store_true")
|
||||
p.add_argument("--d1open", action="store_true")
|
||||
p.add_argument("--d2open", action="store_true")
|
||||
p.add_argument("--sys-fault", action="store_true")
|
||||
p.add_argument("--level-a", type=float, default=100)
|
||||
p.add_argument("--level-foam", type=float, default=30)
|
||||
p.add_argument("--flow-a", type=float, default=20)
|
||||
p.add_argument("--flow-b", type=float, default=0.5)
|
||||
p.add_argument("--air-flow-fb", type=float, default=100)
|
||||
p.add_argument("--expansion", type=float, default=6)
|
||||
p.add_argument("--press", type=float, default=0.3, help="MPa,精度 0.1")
|
||||
p.add_argument("--air-press", type=float, default=0.35)
|
||||
p.add_argument("--air-fault", type=int, default=0)
|
||||
p.add_argument("--plunger-fault", type=int, default=0)
|
||||
# 气体检测仪
|
||||
p.add_argument("--detector-sta", type=int, default=4, help="4=检测中")
|
||||
p.add_argument("--gas-type", type=int, default=2, help="位组合 1=P 2=S 4=HNO 8=CH 16=AS")
|
||||
p.add_argument("--gas-level", type=int, default=2)
|
||||
p.add_argument("--warn", type=int, default=0, help="1=进气嘴未接 2=氢气不足 4=仪器故障 8=浓度过高")
|
||||
p.add_argument("--gas-conc", type=float, default=250.0, help="ppb")
|
||||
a = p.parse_args()
|
||||
a.wash_mode = 3 if a.wash == "disinfect" else 2
|
||||
a.handbrake = not a.no_handbrake
|
||||
|
||||
if a.listen:
|
||||
listen(a.host, a.port, None)
|
||||
return
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
period = 1.0 / a.hz
|
||||
print("→ %s:%d 反馈帧 %d 条 @ %gHz 电量=%.1f%% 电压=%.1fV 车速=%.2fkm/h 洗消=%s"
|
||||
% (a.host, a.port, len(FEEDBACK), a.hz, a.soc, a.volt, a.speed, a.wash))
|
||||
sent = 0
|
||||
try:
|
||||
while a.count == 0 or sent < a.count:
|
||||
for can_id, build in FEEDBACK:
|
||||
sock.sendto(pack(can_id, build(a)), (a.host, a.port))
|
||||
sent += 1
|
||||
time.sleep(period)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
print("已发送 %d 轮(每轮 %d 帧)" % (sent, len(FEEDBACK)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""遥控器帧模拟发送器(联调用,非工程代码)。
|
||||
|
||||
按 24 字节协议向 UI 的 net/rc_port(默认 9002)周期发送遥控器报文。
|
||||
用法示例:
|
||||
python rc_sender.py --mode drive --gear 4096 # 行车模式,档位推到"前"
|
||||
python rc_sender.py --mode arm --posx 4096 # 机械臂模式(自动带上机械臂使能)
|
||||
python rc_sender.py --estop # bit4=1,触发急停
|
||||
python rc_sender.py --battery 42 # 电量显示 42%
|
||||
python rc_sender.py --roll 4096 --pitch 0 # 右摇杆两轴反向
|
||||
python rc_sender.py --bad-checksum # 校验和故意错,应被丢弃
|
||||
"""
|
||||
import argparse
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
|
||||
MID = 2048
|
||||
|
||||
|
||||
def build_frame(a):
|
||||
flags = 0
|
||||
if a.mode == "drive":
|
||||
flags |= 0x01 # bit0 行车模式
|
||||
if not a.no_chassis_enable:
|
||||
flags |= 0x08 # bit3 底盘使能
|
||||
else:
|
||||
flags |= 0x02 | 0x04 # bit1 机械臂模式须与 bit2 使能同时置 1
|
||||
if a.estop:
|
||||
flags |= 0x10 # bit4 急停
|
||||
|
||||
f = bytearray(24)
|
||||
f[0] = 0x11 # 帧头
|
||||
f[1] = flags
|
||||
axes = (a.gear, a.steer, a.posz, a.posx, a.posy, a.roll, a.pitch, a.yaw)
|
||||
for i, v in enumerate(axes):
|
||||
struct.pack_into("<H", f, 3 + i * 2, v) # BYTE3 起,小端 int16
|
||||
f[19] = max(0, min(100, a.battery))
|
||||
f[22] = 0x22 # 帧尾
|
||||
f[23] = sum(f[0:23]) & 0xFF # 和校验
|
||||
if a.bad_checksum:
|
||||
f[23] ^= 0xFF
|
||||
return bytes(f)
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--host", default="127.0.0.1")
|
||||
p.add_argument("--port", type=int, default=9002)
|
||||
p.add_argument("--mode", choices=["drive", "arm"], default="drive")
|
||||
for name in ("gear", "steer", "posz", "posx", "posy", "roll", "pitch", "yaw"):
|
||||
p.add_argument("--" + name, type=int, default=MID)
|
||||
p.add_argument("--battery", type=int, default=88)
|
||||
p.add_argument("--estop", action="store_true")
|
||||
p.add_argument("--no-chassis-enable", action="store_true")
|
||||
p.add_argument("--bad-checksum", action="store_true")
|
||||
p.add_argument("--hz", type=float, default=10.0, help="默认 10Hz,即协议要求的 100ms")
|
||||
p.add_argument("--count", type=int, default=0, help="0=一直发")
|
||||
a = p.parse_args()
|
||||
|
||||
frame = build_frame(a)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
print("→ %s:%d mode=%s flags=0x%02X checksum=0x%02X bytes=%d rate=%gHz"
|
||||
% (a.host, a.port, a.mode, frame[1], frame[23], len(frame), a.hz))
|
||||
|
||||
period = 1.0 / a.hz
|
||||
sent = 0
|
||||
try:
|
||||
while a.count == 0 or sent < a.count:
|
||||
sock.sendto(frame, (a.host, a.port))
|
||||
sent += 1
|
||||
time.sleep(period)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
print("已发送 %d 帧" % sent)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user