2026-02-10 17:52:55 +08:00
|
|
|
|
1. 4G OTA 下载的时候,为什么使用十六进制下载,读取 URC 事件?
|
2026-01-23 11:28:40 +08:00
|
|
|
|
因为使用二进制下载的时候,经常会出现错误,并且会失败?然后最稳定传输的办法,是每次传输的时候,是分块,而且每次分块都要“删/建”http实例。推测原因是因为我们现在是直接传输文件的源代码,代码中含有了一些字符串可能和 AT指令重复,导致了 AT 模块在解释的时候出错。而使用 16 进制的方式,可以避免这个问题。因为十六进制直接把数据先转成了字符串,然后在设备端再把字符串转成数据,这样就不可能出现 AT的指令,从而减少了麻烦。
|
2026-02-10 17:52:55 +08:00
|
|
|
|
2. 4G OTA 下载的时候,为什么不用 AT 模块里 HTTPDLFILE 的指令?
|
2026-01-23 11:28:40 +08:00
|
|
|
|
因为在测试中发现,使用 HTTPDLFILE,其实是下载到了 4G 模块内部,需要重新从模块内部转到存储卡,而且 4G 模块的存储较小,大概只有 40k,所以还需要分块来下载和转存,比较麻烦,于是最终使用了使用读取串口事件的模式。
|
2026-02-10 17:52:55 +08:00
|
|
|
|
3. 4G OTA 下载的时候,为什么不用 AT 模块里 HTTPREAD 的指令?
|
2026-01-23 11:28:40 +08:00
|
|
|
|
因为之前测试发现,READ模式其实是需要多步:
|
|
|
|
|
|
3.1. AT+MHTTPCREATE
|
|
|
|
|
|
3.2. AT+MHTTPCFG
|
|
|
|
|
|
3.3. AT+MHTTPREQUEST
|
|
|
|
|
|
3.4. AT+MHTTPREAD
|
|
|
|
|
|
它其实也是把数据下载到 4g 模块的缓存里,然后再从缓存里读取出来。所以也是比较繁琐的,还不如 HTTPDLFILE 简单。
|
2026-02-10 17:52:55 +08:00
|
|
|
|
4. WiFi OTA 流程(ota_manager.handle_wifi_and_update())
|
|
|
|
|
|
* 解析 ota_url 得到 host:port
|
|
|
|
|
|
* 调用 network_manager.connect_wifi(ssid, password, verify_host=host, verify_port=port, persist=True)
|
|
|
|
|
|
* 只有“能连上 WiFi 且能访问 OTA host:port”才会把新凭证保留在 /boot
|
|
|
|
|
|
* 连接成功后开始下载 OTA 文件(download_file())
|
|
|
|
|
|
* 下载成功则 apply_ota_and_reboot()
|
|
|
|
|
|
5. TCP 通信
|
|
|
|
|
|
1) 平时 TCP 通信主流程(network_manager.tcp_main())
|
|
|
|
|
|
外层无限循环:一直尝试保持与服务器的 TCP 会话。
|
|
|
|
|
|
每轮开始:
|
|
|
|
|
|
如果 OTA 正在进行:暂停(避免抢占资源/串口)。
|
|
|
|
|
|
connect_server():建立 TCP 连接(自动选 WiFi 或 4G)。
|
|
|
|
|
|
发送“登录包”(msg_type=1),等待服务器返回“登录成功”。
|
|
|
|
|
|
登录成功后进入内层循环:
|
|
|
|
|
|
接收数据:
|
|
|
|
|
|
WiFi:非阻塞 recv();没数据返回 b"";有数据进入缓冲区拼包解析。
|
|
|
|
|
|
4G:从 ATClient 的队列 pop_tcp_payload() 取数据。
|
|
|
|
|
|
处理命令/ACK:
|
|
|
|
|
|
登录响应、心跳 ACK、OTA 命令、关机命令、日志上传命令等。
|
|
|
|
|
|
发送业务队列:
|
|
|
|
|
|
从高优/普通队列取 1 条,发送失败会放回队首,并断线重连(不再丢消息)。
|
|
|
|
|
|
发送心跳:
|
|
|
|
|
|
按 HEARTBEAT_INTERVAL 发心跳包。
|
|
|
|
|
|
心跳失败会计数(当前为连续失败到阈值才重连)。
|
|
|
|
|
|
任何发送/接收致命失败:
|
|
|
|
|
|
关闭 socket/断开连接 → 跳出内层循环 → 外层等待一会儿后重新 connect_server() → 重新登录。
|
|
|
|
|
|
6. “WiFi 连接/验证”
|
|
|
|
|
|
TCP 连接建立与网络选择(connect_server() / select_network())
|
|
|
|
|
|
* select_network():WiFi 优先,但要求:
|
|
|
|
|
|
is_wifi_connected() 为 True(系统层面有 WiFi IP 或 Maix WLAN connected)
|
|
|
|
|
|
且能连到 TCP 服务器 SERVER_IP:SERVER_PORT
|
|
|
|
|
|
否则回退到 4G
|
|
|
|
|
|
* connect_server():
|
|
|
|
|
|
若已有连接:WiFi 会做 _check_wifi_connection() 轻量检查;4G 直接认为 OK(由 AT 层维护)。
|
|
|
|
|
|
否则按网络类型走:
|
|
|
|
|
|
WiFi:创建 socket → connect → setblocking(False)(接收用非阻塞)
|
|
|
|
|
|
4G:AT+MIPOPEN 建链
|
|
|
|
|
|
WiFi 链接(connect_wifi())
|
|
|
|
|
|
当前 connect_wifi() 的关键特点是:必须让 /etc/init.d/S30wifi restart 真正用新 SSID 去连,所以会临时写 /boot/wifi.ssid 和 /boot/wifi.pass,失败自动回滚。
|
|
|
|
|
|
流程是:
|
|
|
|
|
|
(1) 备份旧配置
|
|
|
|
|
|
* /boot/wifi.ssid、/boot/wifi.pass
|
|
|
|
|
|
* /etc/wpa_supplicant.conf(尽量备份)
|
|
|
|
|
|
(2) 写入新凭证
|
|
|
|
|
|
* 把新 ssid/pass 写到 /boot/*
|
|
|
|
|
|
-(同时尽量写 /etc/wpa_supplicant.conf,但不强依赖)
|
|
|
|
|
|
(3) 重启 WiFi 服务:/etc/init.d/S30wifi restart
|
|
|
|
|
|
(4) 等待获取 IP(默认 20 秒,可调)
|
|
|
|
|
|
(5) 验证可用性,连到 verify_host:verify_port
|
|
|
|
|
|
(6) 成功
|
|
|
|
|
|
* persist=True:保留 /boot/*(持久化)
|
|
|
|
|
|
* persist=False:回滚 /boot/* 到旧值(不重启,当前连接仍可继续)
|
|
|
|
|
|
(7) 失败
|
|
|
|
|
|
* 回滚 /boot/* + 回滚 /etc/wpa_supplicant.conf(如果有备份)
|
|
|
|
|
|
* 再 S30wifi restart 恢复旧网络
|
|
|
|
|
|
* 返回错误
|
|
|
|
|
|
|
|
|
|
|
|
7. 日志上传(inner_cmd == 43),当前只支持 wifi 上传日志
|
|
|
|
|
|
命令带 ssid/password/url 时:
|
|
|
|
|
|
* 若 WiFi 未连接:先 connect_wifi(..., verify_host=upload_host, verify_port=upload_port, persist=True)
|
|
|
|
|
|
上传内容:
|
|
|
|
|
|
* sync # 把日志从内存同步到文件
|
|
|
|
|
|
* 快照 app.log* 到 /tmp staging
|
|
|
|
|
|
* 打包成 tar.gz(默认)或 zip
|
|
|
|
|
|
* 以 multipart/form-data 的 file 字段 POST 到 url
|
2026-03-11 18:19:17 +08:00
|
|
|
|
|
|
|
|
|
|
8. 自动关机:
|
|
|
|
|
|
hardware中设定了开停表,然后再增加了获取idle的时间。
|
|
|
|
|
|
自动关机的时机: 超过配置的idle时长,
|
|
|
|
|
|
禁止自动关机的情况:1.校准中,2.OTA中
|
|
|
|
|
|
重启计时的时机:1.校准完成,2.命令触发射箭,3.真实触发射箭,4.初始化完成
|
2026-04-02 18:02:34 +08:00
|
|
|
|
9. Wifi网络监控:
|
|
|
|
|
|
有两次发现wifi网络下,有些消息发送很慢,但具体是什么缘故还不清楚,现在增加了wifi网络下的检测,并一旦发现wifi的网络质量差,就会切换到4G。
|
|
|
|
|
|
WiFi 连接成功
|
|
|
|
|
|
↓
|
|
|
|
|
|
启动后台监测线程
|
|
|
|
|
|
↓
|
|
|
|
|
|
每 5 秒循环:
|
|
|
|
|
|
测量 RTT (1 样本,600ms timeout)
|
|
|
|
|
|
获取 RSSI
|
|
|
|
|
|
更新缓存
|
|
|
|
|
|
判断是否差:
|
|
|
|
|
|
- RTT >= 600ms → 差
|
|
|
|
|
|
- RTT >= 350ms 且 RSSI <= -80dBm → 差
|
|
|
|
|
|
↓
|
|
|
|
|
|
如果质量差:
|
|
|
|
|
|
快速重试2次,如果其中任意一次网络恢复了,继续使用wifi。否则,
|
|
|
|
|
|
调用 _switch_to_4g_due_to_poor_wifi()
|
|
|
|
|
|
关闭 WiFi socket
|
|
|
|
|
|
重置连接状态
|
|
|
|
|
|
尝试切换到 4G
|
|
|
|
|
|
↓
|
|
|
|
|
|
上层检测到连接断开:
|
2026-04-17 18:30:50 +08:00
|
|
|
|
重新 connect_server() → 自动选择 4G
|
|
|
|
|
|
|
|
|
|
|
|
10. 现在使用的相机,其实是支持更大的分辨率的,比如说1920*1280,但是由于我们的图像处理,拍照处理之后很容易触发OOM。
|
|
|
|
|
|
|
|
|
|
|
|
11. 环数计算流程:
|
|
|
|
|
|
现在设备侧的目标是:算出箭点相对靶心的偏移(dx,dy),单位是物理厘米(cm),然后把它作为 x,y 上报给后端;后端再去算环。
|
|
|
|
|
|
设备侧本身不直接算环数,它算的是偏移与距离,并上报。
|
|
|
|
|
|
|
|
|
|
|
|
算法流程(一次射箭从触发到上报)
|
|
|
|
|
|
1) 触发后取一帧图
|
|
|
|
|
|
在 process_shot() 里读取相机帧并调用 analyze_shot(frame)
|
|
|
|
|
|
2) 确定激光点(laser_point)
|
|
|
|
|
|
|
|
|
|
|
|
analyze_shot() 第一步先确定激光点 (x,y)(像素坐标):
|
|
|
|
|
|
|
|
|
|
|
|
硬编码:config.HARDCODE_LASER_POINT=True → 用 laser_manager.laser_point
|
|
|
|
|
|
已校准:laser_manager.has_calibrated_point() → 用校准值
|
|
|
|
|
|
动态模式:先 detect_circle_v3(frame, None) 粗估距离,再根据距离反推激光点
|
|
|
|
|
|
代码在:
|
|
|
|
|
|
|
|
|
|
|
|
if config.HARDCODE_LASER_POINT:
|
|
|
|
|
|
...
|
|
|
|
|
|
elif laser_manager.has_calibrated_point():
|
|
|
|
|
|
...
|
|
|
|
|
|
else:
|
|
|
|
|
|
_, _, _, _, best_radius1_temp, _ = detect_circle_v3(frame, None)
|
|
|
|
|
|
distance_m_first = estimate_distance(best_radius1_temp) ...
|
|
|
|
|
|
laser_point = laser_manager.calculate_laser_point_from_distance(distance_m_first)
|
|
|
|
|
|
3) 优先走三角形路径(成功就直接用于上报 x/y)
|
|
|
|
|
|
如果 config.USE_TRIANGLE_OFFSET=True,先尝试识别靶面四角三角形标记:
|
|
|
|
|
|
|
|
|
|
|
|
if getattr(config, "USE_TRIANGLE_OFFSET", False):
|
|
|
|
|
|
K, dist_coef, pos = _get_triangle_calib()
|
|
|
|
|
|
img_rgb = image.image2cv(frame, False, False)
|
|
|
|
|
|
tri = try_triangle_scoring(img_rgb, (x, y), pos, K, dist_coef, ...)
|
|
|
|
|
|
if tri.get("ok"):
|
|
|
|
|
|
return {... "dx": tri["dx_cm"], "dy": tri["dy_cm"], "distance_m": tri.get("distance_m"), ...}
|
|
|
|
|
|
这一步里 try_triangle_scoring() 做了两件事(都在 triangle_target.py):
|
|
|
|
|
|
|
|
|
|
|
|
单应性(homography):把激光点从图像坐标映射到靶面坐标系,得到(dx,dy)(cm)
|
|
|
|
|
|
PnP:用识别到的角点与相机标定,估算 相机到靶的距离 distance_m
|
|
|
|
|
|
关键代码:
|
|
|
|
|
|
|
|
|
|
|
|
ok_h, tx, ty, _H = homography_calibration(...)
|
|
|
|
|
|
out["dx_cm"] = tx
|
|
|
|
|
|
out["dy_cm"] = -ty
|
|
|
|
|
|
out["distance_m"] = dist_m
|
|
|
|
|
|
out["distance_method"] = "pnp_triangle"
|
|
|
|
|
|
注意:这里 dy_cm 取了负号,是为了和现网约定一致(laser_manager.compute_laser_position 的坐标方向)。
|
|
|
|
|
|
|
|
|
|
|
|
4) 三角形失败 → 回退圆形/椭圆靶心检测(兜底)
|
|
|
|
|
|
如果三角形不可用或识别失败,就走传统靶心检测:
|
|
|
|
|
|
|
|
|
|
|
|
detect_circle_v3(frame, laser_point) 找黄心/红心、半径、椭圆参数
|
|
|
|
|
|
用 laser_manager.compute_laser_position() 把像素偏移换算成厘米偏移(dx,dy)
|
|
|
|
|
|
在 shoot_manager.py:
|
|
|
|
|
|
|
|
|
|
|
|
result_img, center, radius, method, best_radius1, ellipse_params = detect_circle_v3(frame, laser_point)
|
|
|
|
|
|
if center and radius:
|
|
|
|
|
|
dx, dy = laser_manager.compute_laser_position(center, (x, y), radius, method)
|
|
|
|
|
|
distance_m = estimate_distance(best_radius1) ...
|
|
|
|
|
|
在 laser_manager.compute_laser_position()(核心换算逻辑):
|
|
|
|
|
|
|
|
|
|
|
|
r = radius * 5
|
|
|
|
|
|
target_x = (lx-cx)/r*100
|
|
|
|
|
|
target_y = (ly-cy)/r*100
|
|
|
|
|
|
return (target_x, -target_y)
|
|
|
|
|
|
这里 (像素差)/(radius*5)*100 是你们旧约定下的“像素→厘米”比例模型(并且 y 方向同样取负号)。
|
|
|
|
|
|
|
|
|
|
|
|
5) 上报数据:把(dx,dy) 作为 x/y 发给后端
|
|
|
|
|
|
最终上报发生在 process_shot(),直接把 dx,dy 填到 inner_data["x"],["y"]:
|
|
|
|
|
|
|
|
|
|
|
|
srv_x = round(float(dx), 4) if dx is not None else 200.0
|
|
|
|
|
|
srv_y = round(float(dy), 4) if dy is not None else 200.0
|
|
|
|
|
|
inner_data = {
|
|
|
|
|
|
"x": srv_x,
|
|
|
|
|
|
"y": srv_y,
|
|
|
|
|
|
"d": round((distance_m or 0.0) * 100),
|
|
|
|
|
|
"m": method if method else "no_target",
|
|
|
|
|
|
"offset_method": offset_method,
|
|
|
|
|
|
"distance_method": distance_method,
|
|
|
|
|
|
...
|
|
|
|
|
|
}
|
|
|
|
|
|
network_manager.safe_enqueue(...)
|
|
|
|
|
|
x,y:物理厘米(cm)
|
|
|
|
|
|
d:相机到靶距离(m→cm,乘 100;三角形成功时来自 PnP)
|
|
|
|
|
|
m/offset_method/distance_method:标记本次用的算法路径(triangle / yellow / pnp 等)
|
|
|
|
|
|
后端收到 x,y 后,再用你之前给的 Go 公式 CalculateRingNumber(x,y,tenRingRadius) 计算环数。
|
|
|
|
|
|
|
|
|
|
|
|
你现在的“环数计算”实际依赖关系
|
|
|
|
|
|
最好路径(快+稳):三角形 → dx,dy(单应性) + distance_m(PnP)
|
|
|
|
|
|
兜底路径:圆/椭圆靶心 → dx,dy(基于黄心半径比例/透视校正) + distance_m(黄心半径估距)
|