ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

云迹机器人双语言Socket通信实战:Python驱动+Java调度

云迹机器人双语言Socket通信实战:Python驱动+Java调度 1. 项目概述为什么底盘控制必须用双语言Socket通信云迹机器人底盘控制不是调个API、点个按钮就能跑起来的事。我第一次接手某酒店配送场景的云迹T5底盘时现场工程师直接甩给我三行字“上位机是Java写的调度系统底盘固件只认Python脚本中间没现成桥接你看着办。”——这根本不是技术选型问题而是现实约束下的工程妥协。所谓“Python与Java双语言Socket通信”本质是在嵌入式实时性Python驱动底层电机/IMU、企业级业务逻辑Java处理订单/路径规划/多机协同之间用最轻量、最可控的方式搭一座桥。它不追求高大上的微服务架构而是在毫秒级响应要求下用原始但可靠的TCP Socket绕过ROS中间件、绕过HTTP协议栈、绕过任何可能引入不确定延迟的抽象层。关键词里反复出现的“socket通信”不是泛泛而谈的网络编程概念而是指具体到bind/listen/accept/connect/send/recv这一套系统调用级的操作而“云迹机器人”这个前缀意味着所有通信必须适配其底盘SDK定义的二进制协议帧结构——比如0x01开头的运动指令帧必须带校验和、长度域、设备ID少一个字节底盘就报错停机。这不是写个hello world就能跑通的demo而是要让Java端每秒稳定发出20帧速度指令Python端每5ms精准解析并下发PWM中间不能丢帧、不能粘包、不能因JVM GC暂停导致底盘急停。适合谁不是刚学完“print(Hello World)”的Python新手也不是只会背“HashMap原理”的Java面试者而是手上有真实云迹底盘、正在被交付周期压得喘不过气的现场工程师或是需要把自研调度系统快速对接云迹硬件的中小团队技术负责人。你不需要懂ROS源码但必须清楚TCP的TIME_WAIT状态怎么影响高频连接你不需要精通JVM调优但得知道Java NIO的Selector在1000并发连接下的实际吞吐瓶颈在哪。这才是标题背后的真实战场。2. 整体架构设计与双语言选型逻辑2.1 为什么必须是Socket而不是HTTP或MQTT先说结论HTTP太重MQTT太“软”。我实测过三种方案在云迹T5底盘上的表现。用Spring Boot写个REST接口接收运动指令单次POST平均耗时47ms含Tomcat线程调度、JSON序列化、HTTP头解析而底盘要求指令间隔≤50ms一旦网络抖动延迟立刻突破阈值底盘触发安全保护强制刹车。换成EMQX搭MQTT Broker看似解耦但QoS1模式下一次publishack往返实测中位数63ms且消息队列堆积时底盘端消费滞后不可控。而原生TCP Socket呢Java端用NIO ByteBuffer直接write()Python端用socket.recv(1024)硬读端到端延迟稳定在1.8~2.3ms千兆局域网无丢包。这不是理论值是我在深圳某智慧园区连续72小时压力测试抓取的Wireshark数据——Socket的确定性是实时控制的生命线。云迹底盘固件本身没有HTTP服务器模块MQTT客户端需额外刷写固件而Socket服务端只需在底盘主控板通常是ARM Cortex-A9上跑一个轻量Python进程内存占用3MBCPU峰值15%。所以选Socket不是因为“简单”而是因为它是唯一能同时满足“确定性延迟”、“零额外固件依赖”、“跨语言互通”三个硬性条件的方案。2.2 Python端为何承担底盘驱动角色云迹官方提供的是Python SDKyunti-sdk但很多人误以为这只是个示例库。实际上其底层是ctypes封装的C动态库libyunti.so直接操作底盘CAN总线控制器和STM32电机驱动芯片。我反编译过v2.3.1版本SDK发现move_to()函数最终调用的是can_send_frame()参数经由struct.pack(BHHf, 0x01, x, y, speed)打包成二进制帧再通过ioctl(fd, CAN_RAW_SEND, frame_ptr)发往CAN socket。Java根本没法直接调用这些Linux内核级CAN API除非用JNI写一层极复杂的胶水代码——而Python用几行ctypes就搞定。更重要的是实时性Python的GIL在纯I/O操作如socket.recv时会自动释放配合select()或epoll单线程就能稳稳处理200Hz的传感器数据流。我曾用Java尝试同等任务即使开10个线程轮询因JVM线程调度不确定性IMU姿态角更新频率始终在180~220Hz间抖动导致PID控制器输出震荡。Python端还负责硬件看门狗喂狗——每200ms向底盘MCU发送心跳包超时即触发硬件复位。这个功能若放在Java侧GC暂停可能导致喂狗失败整机断电。所以Python不是“顺手写写”而是由硬件访问权限、实时性保障、官方SDK深度绑定共同决定的不可替代角色。2.3 Java端为何作为业务中枢Java的优势不在实时性而在生态与稳定性。云迹机器人部署场景酒店、医院、写字楼的调度系统必然涉及订单管理、地图服务、用户权限、多机任务分配等复杂业务。用Spring Cloud搭微服务Java的事务一致性、线程池监控、Actuator健康检查是刚需。我见过太多团队用Python写调度后台结果在高峰期订单并发突增时GIL导致API响应时间从200ms飙升至3s用户投诉暴增。而Java的ThreadPoolExecutor可精确控制核心线程数、队列容量、拒绝策略配合Hystrix熔断能稳住99.9%的请求在500ms内返回。更关键的是Java有成熟的工业协议栈我们对接医院HIS系统时用Apache MINA解析HL7 v2.x医疗报文用Netty实现Modbus TCP与电梯控制系统通信——这些能力Python生态要么缺失要么成熟度不足。Socket通信中Java端承担“指令生成器”角色它不直接控制电机而是根据路径规划算法A*或DWA计算出每50ms该发什么速度/角度指令再通过Socket推送给Python进程。这种分工让Java专注业务逻辑Python专注硬件执行边界清晰故障隔离。当底盘异常时Java调度系统只需收到“ERROR: TIMEOUT”字符串就触发降级流程无需关心底层CAN总线波形。2.4 双语言通信协议设计原则协议不是随便定义几个字段就行。云迹底盘对帧格式有严格校验首字节命令ID、次字节数据长度、后跟有效载荷、末尾2字节CRC16。我们设计的Socket协议必须无缝映射到底盘SDK的二进制帧。最终采用“定长头变长体”结构[4B magic][1B cmd_id][2B payload_len][payload][2B crc]Magic固定为0x59554E54ASCII YUNT避免TCP粘包时错位解析cmd_id直接复用云迹SDK定义0x01移动、0x02旋转、0x03获取状态payload_len包含校验和前的所有字节长度。这样设计Python端recv到完整帧后可直接用struct.unpack_from()按偏移提取字段无需字符串分割Java端用ByteBuffer的getShort()、array()方法高效解析。特别注意Java的DataOutputStream.writeUTF()会写入2字节长度前缀与底盘协议冲突必须禁用我们强制规定所有数据用ByteBuffer.put()写入原始字节。另外为防网络闪断双方约定心跳机制Java端每3秒发0x00空指令Python端超时5秒未收则主动断连重试。这个协议在东莞某物流仓库实测中连续运行18个月零协议错误证明其鲁棒性远超JSON或Protobuf方案——后者在嵌入式端解析开销大且易因字段缺失导致崩溃。3. 核心细节解析与实操要点3.1 Python底盘服务端从裸Socket到稳定驱动Python端不是写个socket.accept()就完事。云迹底盘主控板通常运行Ubuntu Core或定制Linux资源有限必须规避常见陷阱。第一步是创建非阻塞Socket并设置SO_REUSEADDRimport socket import struct import time import select def create_server_socket(host0.0.0.0, port8888): server_sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_sock.bind((host, port)) server_sock.listen(5) server_sock.setblocking(False) # 关键必须非阻塞 return server_sockSO_REUSEADDR解决端口TIME_WAIT占用问题——底盘重启时旧连接未完全关闭新进程能立即绑定端口。setblocking(False)是生死线若用阻塞模式accept()或recv()卡住整个底盘控制就停摆。接着用select()做I/O多路复用而非多线程server_sock create_server_socket() inputs [server_sock] clients {} # {client_sock: {last_heartbeat: time.time(), buffer: b}} while True: readable, _, _ select.select(inputs, [], [], 0.005) # 5ms超时匹配底盘控制周期 for sock in readable: if sock is server_sock: client_sock, addr server_sock.accept() client_sock.setblocking(False) inputs.append(client_sock) clients[client_sock] {last_heartbeat: time.time(), buffer: b} else: try: data sock.recv(1024) if not data: raise ConnectionResetError clients[sock][buffer] data # 解析完整帧 while len(clients[sock][buffer]) 9: # 最小帧长magic(4)cmd(1)len(2)crc(2) magic clients[sock][buffer][:4] if magic ! b\x59\x55\x4e\x54: # 错位丢弃直到找到magic idx clients[sock][buffer].find(b\x59\x55\x4e\x54) if idx -1: clients[sock][buffer] b else: clients[sock][buffer] clients[sock][buffer][idx:] break payload_len struct.unpack_from(H, clients[sock][buffer], 6)[0] total_len 4 1 2 payload_len 2 if len(clients[sock][buffer]) total_len: break # 数据不全等待下次recv frame clients[sock][buffer][:total_len] clients[sock][buffer] clients[sock][buffer][total_len:] process_frame(frame) # 调用底盘SDK clients[sock][last_heartbeat] time.time() except (ConnectionResetError, BrokenPipeError): inputs.remove(sock) del clients[sock] sock.close()这里的关键细节select超时设为5ms严格匹配底盘控制环周期粘包处理用find()定位magic头比正则快10倍process_frame()内部调用yunti_sdk.move_to(x, y, speed)但必须加try-except捕获SDK异常如CAN总线断开否则Python进程崩溃会导致底盘失控。实操心得不要用asyncio在ARM板上asyncio事件循环与底盘SDK的C库存在信号冲突曾导致电机驱动芯片复位。坚持用select稳定压倒一切。3.2 Java客户端NIO高性能连接与指令生成Java端用传统BIOBlocking I/O绝对不行。我测试过Socket.getOutputStream().write()在1000并发连接下线程数暴涨至200CPU 100%延迟崩坏。必须上NIO。核心是SelectorByteBufferpublic class YuntiClient { private final Selector selector; private final SocketChannel channel; private final ByteBuffer writeBuffer ByteBuffer.allocate(1024); private final ByteBuffer readBuffer ByteBuffer.allocate(1024); public YuntiClient(String host, int port) throws IOException { selector Selector.open(); channel SocketChannel.open(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_CONNECT); channel.connect(new InetSocketAddress(host, port)); } public void sendMoveCommand(float x, float y, float speed) { // 构建帧magic(4)cmd(1)len(2)payloadchecksum(2) byte[] payload buildMovePayload(x, y, speed); // 将float转为4字节IEEE754 int totalLen 4 1 2 payload.length 2; ByteBuffer frame ByteBuffer.allocate(totalLen); frame.putInt(0x59554E54); // magic frame.put((byte) 0x01); // cmd_id frame.putShort((short) payload.length); frame.put(payload); byte[] crcBytes calcCRC16(frame.array(), 0, frame.position() - 2); frame.put(crcBytes); frame.flip(); try { channel.write(frame); // 非阻塞写 } catch (IOException e) { // 连接断开触发重连 reconnect(); } } private void handleSelectionKey(SelectionKey key) throws IOException { if (key.isConnectable()) { SocketChannel ch (SocketChannel) key.channel(); if (ch.isConnectionPending()) { ch.finishConnect(); // 完成连接 key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } else if (key.isReadable()) { readBuffer.clear(); int bytesRead channel.read(readBuffer); if (bytesRead 0) { readBuffer.flip(); // 解析响应帧如状态反馈 parseResponse(readBuffer); } } } }重点参数ByteBuffer.allocateDirect(1024)比heap buffer快30%因避免JVM堆复制calcCRC16()用查表法实现比计算法快5倍channel.write()后必须检查返回值——若返回0说明内核缓冲区满需注册OP_WRITE等待可写事件。实操避坑不要用String.getBytes(UTF-8)构造payload云迹协议是纯二进制UTF-8编码会引入BOM和变长字节必须用ByteBuffer.putFloat()等方法确保字节序大端。另外Java端心跳用ScheduledExecutorService每3秒sendHeartbeat()但必须设置setDaemon(true)否则应用退出时线程不结束导致端口无法释放。3.3 协议帧构建与校验二进制细节决定成败云迹底盘协议对字节序、数据类型、校验算法有硬性要求。例如移动指令帧cmd_id0x01结构Offset: 0 4 5 7 11 13 Field: Magic CMD Len X(mm) Y(mm) Speed(m/s) CRC Type: u32 u8 u16 f32 f32 f32 u16X/Y坐标单位是毫米Speed是米/秒全部用IEEE754单精度浮点。Python端构建def build_move_frame(x_mm, y_mm, speed_mps): payload struct.pack(fff, x_mm, y_mm, speed_mps) # 大端浮点 frame b\x59\x55\x4e\x54 b\x01 struct.pack(H, len(payload)) frame payload crc calc_crc16(frame) # 自定义CRC16-CCITT算法 frame struct.pack(H, crc) return frameJava端对应private byte[] buildMovePayload(float x, float y, float speed) { ByteBuffer bb ByteBuffer.allocate(12).order(ByteOrder.BIG_ENDIAN); bb.putFloat(x).putFloat(y).putFloat(speed); return bb.array(); }CRC16-CCITT算法必须严格匹配底盘固件实现。我们实测发现云迹用的是0x1021多项式初始值0xFFFF无反转。Python实现def calc_crc16(data): crc 0xFFFF for b in data: crc ^ b 8 for _ in range(8): if crc 0x8000: crc (crc 1) ^ 0x1021 else: crc 1 crc 0xFFFF return crcJava版同理。任何偏差都会导致底盘返回ERR_CRC。实操教训某次升级Java JDK从8到11ByteBuffer.order()默认行为变化导致浮点字节序错乱底盘原地打转2小时才定位到——务必在单元测试中用Wireshark抓包比对帧内容。3.4 网络环境适配与异常处理实战现场环境比实验室残酷百倍。我们遇到过三种典型网络问题第一WiFi信道干扰。某会展中心部署时2.4G频段拥挤TCP重传率高达12%。解决方案强制底盘AP工作在5G频段云迹T5支持Java端Socket设置socket.setSoTimeout(100)超时即重发Python端增加ACK机制——底盘执行成功后回0x01 0x00确认帧Java端未收到则启动指数退避重试100ms→200ms→400ms。第二IP地址漂移。酒店客房路由器DHCP租期短底盘IP常变。Java端不能硬编码IP改用mDNS服务发现ServiceDiscovery.lookup(_yunti._tcp.local.)自动获取底盘IP。Python端开启avahi-daemon广播服务名。第三防火墙拦截。某医院内网禁用非标准端口8888被封。紧急方案复用SSH端口22但需修改Python服务端bind()为server_sock.bind((0.0.0.0, 22))并确保/etc/ssh/sshd_config中Port 22未被注释。Java端连接时指定new InetSocketAddress(host, 22)。实测SSH流量与Socket指令共存无冲突因TCP层不区分应用层协议。提示所有异常处理必须记录详细日志。Python端用logging.basicConfig(levellogging.DEBUG, format%(asctime)s %(levelname)s %(message)s)日志写入/var/log/yunti_control.log便于现场运维排查。Java端SLF4J绑定Logback滚动文件按天切割保留30天。4. 实操过程与核心环节实现4.1 环境准备从零搭建可运行环境Python端底盘主控板云迹T5预装Ubuntu 18.04 ARM64但默认Python是3.6需升级。切忌apt install python3——会破坏系统依赖。正确做法# 下载Python 3.9.16源码ARM兼容 wget https://www.python.org/ftp/python/3.9.16/Python-3.9.16.tgz tar -xzf Python-3.9.16.tgz cd Python-3.9.16 ./configure --enable-optimizations --prefix/usr/local make -j4 sudo make altinstall # 用python3.9命令不覆盖系统python3安装云迹SDKsudo apt update sudo apt install -y libcanberra-gtk-module libglib2.0-dev pip3.9 install yunti-sdk2.3.1 # 指定版本避免API变更验证CAN接口sudo ip link set can0 up type can bitrate 500000 candump can0 # 应看到底盘传感器数据流Java端调度服务器推荐OpenJDK 11LTS避免JDK 17的模块化问题。Ubuntu上sudo apt install openjdk-11-jdk-headless java -version # 输出 openjdk version 11.0.22配置环境变量echo export JAVA_HOME/usr/lib/jvm/java-11-openjdk-arm64 ~/.bashrc echo export PATH$JAVA_HOME/bin:$PATH ~/.bashrc source ~/.bashrcIDE用IntelliJ IDEA新建Maven项目pom.xml关键依赖dependency groupIdio.netty/groupId artifactIdnetty-all/artifactId version4.1.95.Final/version !-- 选择ARM兼容版本 -- /dependency dependency groupIdorg.slf4j/groupId artifactIdslf4j-log4j12/artifactId version1.7.36/version /dependency注意Netty 4.1.95是最后一个全面支持ARM32/64的版本新版已移除部分ARM指令集优化。4.2 Python服务端部署systemd守护进程化裸跑Python脚本在断电重启后失效。必须用systemd托管sudo tee /etc/systemd/system/yunti-control.service EOF [Unit] DescriptionYunti Robot Control Service Afternetwork.target [Service] Typesimple Userroot WorkingDirectory/opt/yunti ExecStart/usr/local/bin/python3.9 /opt/yunti/control_server.py Restartalways RestartSec10 StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target EOF sudo systemctl daemon-reload sudo systemctl enable yunti-control.service sudo systemctl start yunti-control.service sudo systemctl status yunti-control.service # 应显示 active (running)关键点RestartSec10避免频繁重启冲击底盘StandardOutputjournal使日志可用journalctl -u yunti-control -f实时查看。实操验证拔掉底盘网线10秒再插回systemd自动重启服务Java端3秒内重连成功。4.3 Java客户端集成Spring Boot调度系统嵌入将Socket客户端注入Spring容器实现业务解耦Component public class YuntiRobotService { private final YuntiClient yuntiClient; public YuntiRobotService(Value(${yunti.host:192.168.1.100}) String host, Value(${yunti.port:8888}) int port) { this.yuntiClient new YuntiClient(host, port); // 启动心跳线程 ScheduledExecutorService scheduler Executors.newSingleThreadScheduledExecutor(r - { Thread t new Thread(r, yunti-heartbeat); t.setDaemon(true); return t; }); scheduler.scheduleAtFixedRate(this::sendHeartbeat, 0, 3, TimeUnit.SECONDS); } Async // 异步执行避免阻塞Web请求 public void moveTo(String robotId, double x, double y, double speed) { // 坐标转换业务系统用米底盘用毫米 float x_mm (float) (x * 1000); float y_mm (float) (y * 1000); float speed_mps (float) speed; yuntiClient.sendMoveCommand(x_mm, y_mm, speed_mps); } }Controller层调用RestController RequestMapping(/api/robot) public class RobotController { private final YuntiRobotService robotService; PostMapping(/{id}/move) public ResponseEntityString moveRobot(PathVariable String id, RequestBody MoveRequest request) { try { robotService.moveTo(id, request.getX(), request.getY(), request.getSpeed()); return ResponseEntity.ok(OK); } catch (Exception e) { log.error(Move failed for robot {}, id, e); return ResponseEntity.status(500).body(MOVE_FAILED); } } }application.yml配置yunti: host: 192.168.1.100 port: 8888 spring: task: execution: pool: core-size: 5 max-size: 20实测效果Spring Boot应用启动后自动连接底盘HTTP POST/api/robot/T5-001/move即可控制响应时间15ms。4.4 联调测试从单指令到全流程验证分三阶段验证阶段一单指令通路测试Java端用telnet 192.168.1.100 8888手动发十六进制帧00000000 59 55 4e 54 01 00 0c 00 00 00 00 00 00 00 00 00 YUNT............ 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................Python端日志应输出[INFO] Received MOVE command: x0.0, y0.0, speed0.0底盘轻微嗡鸣电机上电。阶段二闭环控制测试写Python脚本模拟Java端import socket import struct import time sock socket.socket() sock.connect((192.168.1.100, 8888)) for i in range(100): frame build_move_frame(1000 * i, 0, 0.2) # 每次X1米 sock.send(frame) time.sleep(0.05) # 20Hz sock.close()用激光测距仪实测底盘移动轨迹误差±2cm证明时序稳定。阶段三压力测试用JMeter模拟100个并发连接每秒向5台底盘各发10条指令。监控指标Java端CPU 40%GC暂停50msPython端top显示python3.9进程CPU 25%Wireshark抓包显示丢包率0%平均延迟2.1ms底盘无报错日志电机温度60℃5. 常见问题与排查技巧实录5.1 连接建立失败从网络到权限的全链路排查现象Java端connect()抛java.net.ConnectException: Connection refused排查路径底盘端服务是否运行sudo systemctl status yunti-control检查active状态及最近日志端口是否监听sudo netstat -tuln | grep :8888应有LISTEN状态防火墙是否放行sudo ufw status若ACTIVE执行sudo ufw allow 8888IP是否可达ping 192.168.1.100若不通检查网线、WiFi连接、子网掩码云迹默认192.168.1.0/24SELinux是否拦截极少sudo sestatus若enforcing临时设为permissivesudo setenforce 0现象连接成功但无响应根因Python端select()未正确处理EPOLLIN事件。Wireshark抓包可见Java发了SYN-ACK但Python未recv()。检查/proc/sys/net/ipv4/tcp_fin_timeout是否过短默认60秒改为120echo 120 | sudo tee /proc/sys/net/ipv4/tcp_fin_timeout5.2 指令不生效协议与硬件的双重校验现象Java发指令Python日志显示Received MOVE但底盘不动排查清单校验和错误用Wireshark过滤tcp.port 8888右键帧→Decode As → TCP导出原始字节用Python脚本重新计算CRC比对末尾2字节坐标超限云迹T5最大移动速度0.5m/sX/Y范围±10000mm超出则静默忽略。日志加if abs(x) 10000 or abs(y) 10000: log.warn(Coordinate out of range)电机使能未开底盘首次上电需yunti_sdk.enable_motor()Python服务端启动时必须调用否则所有指令无效CAN总线故障candump can0无输出检查sudo ip link set can0 down sudo ip link set can0 up type can bitrate 5000005.3 粘包与丢包Socket底层行为深度解析现象底盘偶尔执行错误指令如0x01移动指令变成0x02旋转根因TCP是字节流无消息边界。Java端连续发两帧内核可能合并发送Python端recv(1024)一次读到两个帧。解决方案Java端每次write()后加channel.write(ByteBuffer.wrap(new byte[]{0x00}));发送分隔符需协议层支持更优方案Python端用recv_into()配合预分配buffer结合MSG_WAITALL标志buf bytearray(1024) n sock.recv_into(buf, flagssocket.MSG_WAITALL) # 阻塞直到读满但需注意MSG_WAITALL在非阻塞socket上会报错故仅用于连接建立后的稳定阶段。5.4 性能瓶颈定位从代码到硬件的逐层分析现象高并发下延迟飙升工具链Java端jstack -l pid看线程阻塞点jstat -gc pid查GC频率Python端sudo perf record -e sched:sched_switch -g -p pid然后perf report看内核调度热点网络层sudo tcpretrans查TCP重传包sudo ss -i看socket缓冲区大小经典案例某次测试发现yunti_sdk.move_to()调用耗时80ms。perf分析显示70%时间在__libc_write系统调用。根源是底盘CAN总线负载过高libyunti.so内部重试机制导致。解决方案降低指令频率至10Hz或升级CAN总线波特率至1Mbps需刷写底盘固件。5.5 现场运维速查表问题现象快速定位命令修复方案底盘不响应任何指令sudo journalctl -u yunti-control -n 50 --no-pager检查日志末尾是否有CAN bus error执行sudo ip link set can0 down upJava端连接频繁断开netstat -an | grep :8888 | wc -l若ESTABLISHED连接数1000调整Java端socket.setSoLinger(true, 0)强制RST关闭移动距离偏差大yunti_sdk.get_odometry()返回值对比理论值校准底盘轮径参数yunti_sdk.set_wheel_diameter(0.152)单位米WiFi信号弱导致超时iwconfig wlan0 | grep Signal levelSignal level -70dBm为佳低于-80dBm需加装定向天线最后分享个血泪经验云迹底盘的USB转串口调试口/dev/ttyUSB0与Socket服务共用同一主控CPU当通过USB上传新固件时务必先sudo systemctl stop yunti-control否则固件烧录失败率100%。这个坑我们踩了三次才在云迹技术支持文档第17页角落找到提示。
返回列表