remove rtt from wifi monitoring

This commit is contained in:
gcw_4spBpAfv
2026-04-03 11:24:29 +08:00
parent ec80107128
commit 685dce2519
4 changed files with 186 additions and 167 deletions

View File

@@ -17,6 +17,13 @@ SERVER_IP = "www.shelingxingqiu.com"
SERVER_PORT = 50005 SERVER_PORT = 50005
HEARTBEAT_INTERVAL = 15 # 心跳间隔(秒) HEARTBEAT_INTERVAL = 15 # 心跳间隔(秒)
# WiFi 质量评估(开机先尝试 WiFi质量差且 4G 可用则切到 4G本次上电直至关机锁定 4G
WIFI_QUALITY_RTT_SAMPLES = 3 # 到业务服务器 TCP 建连耗时采样次数,取中位数
WIFI_QUALITY_RTT_BAD_MS = 600.0 # 中位数超过此值认为延迟过高
WIFI_QUALITY_RTT_WARN_MS = 350.0 # 与 RSSI 联合:超过此值且信号弱也判为差
WIFI_QUALITY_RSSI_BAD_DBM = -80.0 # 低于此 dBm更负更差视为信号弱
WIFI_QUALITY_USE_RSSI = True # 是否把 RSSI 纳入综合判定False 则仅看 RTT
# ===== TCP over SSL(TLS) 配置 ===== # ===== TCP over SSL(TLS) 配置 =====
USE_TCP_SSL = False # True=按手册走 MSSLCFG/MIPCFG 绑定 SSL USE_TCP_SSL = False # True=按手册走 MSSLCFG/MIPCFG 绑定 SSL
TCP_LINK_ID = 2 # TCP_LINK_ID = 2 #
@@ -24,11 +31,11 @@ TCP_SSL_PORT = 443 # TLS 端口(不一定必须 443以服务器为
# SSL profile # SSL profile
SSL_ID = 1 # ssl_id=1 SSL_ID = 1 # ssl_id=1
SSL_AUTH_MODE = 1 # 1=单向认证验证服务器2=双向 SSL_AUTH_MODE = 0 # 1=单向认证验证服务器2=双向
SSL_VERIFY_MODE = 1 # 1=写入并使用 CA 证书0=不验(仅测试加密,风险大) SSL_VERIFY_MODE = 1 # 0=不验(仅测试用);1=写入并使用 CA 证书
SSL_CERT_FILENAME = "test.cer" # 模组里证书名MSSLCERTWR / MSSLCFG="cert" 用) SSL_CERT_FILENAME = "www.shelingxingqiu.com.crt" # 模组里证书名MSSLCERTWR / MSSLCFG="cert" 用)
SSL_CERT_PATH = "/root/test.cer" # 设备文件系统里 CA 证书路径(你自己放进去) SSL_CERT_PATH = "/root/www.shelingxingqiu.com.crt" # 设备文件系统里 CA 证书路径(你自己放进去)
# MIPOPEN 末尾的参数在不同固件里含义可能不同;按你手册例子保留 # MIPOPEN 末尾的参数在不同固件里含义可能不同;按你手册例子保留
MIPOPEN_TAIL = ",,0" MIPOPEN_TAIL = ",,0"
@@ -117,7 +124,7 @@ SAVE_IMAGE_ENABLED = True # 是否保存图像True=保存False=不保存
PHOTO_DIR = "/root/phot" # 照片存储目录 PHOTO_DIR = "/root/phot" # 照片存储目录
MAX_IMAGES = 1000 MAX_IMAGES = 1000
SHOW_CAMERA_PHOTO_WHILE_SHOOTING = False # 是否在拍摄时显示摄像头图像True=显示False=不显示建议在连着USB测试过程中打开 SHOW_CAMERA_PHOTO_WHILE_SHOOTING = True # 是否在拍摄时显示摄像头图像True=显示False=不显示建议在连着USB测试过程中打开
# ==================== OTA配置 ==================== # ==================== OTA配置 ====================
MAX_BACKUPS = 5 MAX_BACKUPS = 5

View File

@@ -323,6 +323,7 @@ def cmd_str():
hardware_manager.start_idle_timer() # 重新计时 hardware_manager.start_idle_timer() # 重新计时
diff_ms = current_time - last_adc_trigger diff_ms = current_time - last_adc_trigger
if diff_ms < 3000: if diff_ms < 3000:
logger.info(f"[MAIN] 扳机触发过于频繁, {diff_ms}ms")
continue continue
last_adc_trigger = current_time last_adc_trigger = current_time
# 触发前先把缓存刷出来,避免波形被长耗时处理截断 # 触发前先把缓存刷出来,避免波形被长耗时处理截断

View File

@@ -440,20 +440,19 @@ class NetworkManager:
# 1) 开机先尝试 WiFi并评估质量 # 1) 开机先尝试 WiFi并评估质量
if prefer_wifi and self.is_wifi_connected(): if prefer_wifi and self.is_wifi_connected():
wifi_rssi_dbm = self._get_wifi_rssi_dbm() wifi_rssi_dbm = self._get_wifi_rssi_dbm()
wifi_rtt_ms, wifi_reachable = self._measure_wifi_tcp_rtt_ms( wifi_rtt_ms = 0
host, port, wifi_reachable = True
samples=getattr(config, "WIFI_QUALITY_RTT_SAMPLES", 3), # wifi_rtt_ms, wifi_reachable = self._measure_wifi_tcp_rtt_ms(
per_sample_timeout_ms=900, # host, port,
) # samples=getattr(config, "WIFI_QUALITY_RTT_SAMPLES", 3),
# per_sample_timeout_ms=900,
# )
wifi_bad = self._is_wifi_quality_bad(wifi_rtt_ms, wifi_rssi_dbm) wifi_bad = self._is_wifi_quality_bad(wifi_rtt_ms, wifi_rssi_dbm)
try: self.logger.info(
self.logger.info( f"[NET] WiFi质量评估rtt_ms(median)={wifi_rtt_ms:.1f}, rssi_dbm={wifi_rssi_dbm}, "
f"[NET] WiFi质量评估rtt_ms(median)={wifi_rtt_ms:.1f}, rssi_dbm={wifi_rssi_dbm}, " f"reachable={wifi_reachable}, bad={wifi_bad}"
f"reachable={wifi_reachable}, bad={wifi_bad}" )
)
except Exception:
pass
# 如果质量差且 4G 可用 -> 切换 4G 并锁定本次会话 # 如果质量差且 4G 可用 -> 切换 4G 并锁定本次会话
if wifi_bad and self.is_4g_available(): if wifi_bad and self.is_4g_available():
@@ -623,7 +622,7 @@ class NetworkManager:
self._tcp_connected = True self._tcp_connected = True
self.logger.info("[WIFI-TCP] TCP 连接已建立") self.logger.info("[WIFI-TCP] TCP 连接已建立")
# 启动 WiFi 质量后台 # 启动 WiFi 质量后台
self._start_wifi_quality_monitor() self._start_wifi_quality_monitor()
return True return True
@@ -755,7 +754,7 @@ class NetworkManager:
def _disconnect_tcp_via_wifi(self): def _disconnect_tcp_via_wifi(self):
"""断开 WiFi TCP 连接并停止监测""" """断开 WiFi TCP 连接并停止监测"""
# 先停止监测线程 # 关闭wifi检测
self._stop_wifi_quality_monitor() self._stop_wifi_quality_monitor()
# 再关闭 socket # 再关闭 socket
@@ -1298,6 +1297,7 @@ class NetworkManager:
data = self.receive_tcp_data_via_wifi(timeout_ms=50) data = self.receive_tcp_data_via_wifi(timeout_ms=50)
if data: if data:
# 将数据添加到缓冲区 # 将数据添加到缓冲区
self.logger.info(f"[NET] 接收WiFi数据, {time.time()}")
wifi_manager.recv_buffer += data wifi_manager.recv_buffer += data
# 尝试从缓冲区解析完整的数据包 # 尝试从缓冲区解析完整的数据包
@@ -1315,10 +1315,12 @@ class NetworkManager:
break break
else: else:
# 数据包不完整,等待更多数据 # 数据包不完整,等待更多数据
self.logger.info(f"[NET] 接收WiFi数据不完整, {time.time()}")
break break
except: except:
# 解析失败,清空缓冲区 # 解析失败,清空缓冲区
wifi_manager.recv_buffer = b"" wifi_manager.recv_buffer = b""
self.logger.info(f"[NET] 接收WiFi数据解析失败, {time.time()}")
break break
elif self._network_type == "4g": elif self._network_type == "4g":
# 4G接收数据 # 4G接收数据
@@ -1399,152 +1401,155 @@ class NetworkManager:
data_obj = body.get("data") data_obj = body.get("data")
if isinstance(data_obj, dict): if isinstance(data_obj, dict):
inner_cmd = data_obj.get("cmd") inner_cmd = data_obj.get("cmd")
if inner_cmd == 2: # 开启激光并校准
if inner_cmd == 2: # 开启激光并校准 from laser_manager import laser_manager
from laser_manager import laser_manager if not laser_manager.calibration_active:
if not laser_manager.calibration_active: laser_manager.turn_on_laser()
laser_manager.turn_on_laser() time.sleep_ms(100)
time.sleep_ms(100) hardware_manager.stop_idle_timer() # 停表
hardware_manager.stop_idle_timer() # 停表 if not config.HARDCODE_LASER_POINT:
if not config.HARDCODE_LASER_POINT: laser_manager.start_calibration()
laser_manager.start_calibration() self.safe_enqueue({"result": "calibrating"}, 2)
self.safe_enqueue({"result": "calibrating"}, 2)
else:
# 写死的逻辑,不需要校准激光点
self.safe_enqueue({"result": "laser pos set by hard code"}, 2)
elif inner_cmd == 3: # 关闭激光
from laser_manager import laser_manager
laser_manager.turn_off_laser()
laser_manager.stop_calibration()
hardware_manager.start_idle_timer() # 开表
self.safe_enqueue({"result": "laser_off"}, 2)
elif inner_cmd == 4: # 上报电量
voltage = get_bus_voltage()
battery_percent = voltage_to_percent(voltage)
battery_data = {"battery": battery_percent, "voltage": round(voltage, 3)}
self.safe_enqueue(battery_data, 2)
self.logger.info(f"电量上报: {battery_percent}%")
elif inner_cmd == 5: # OTA 升级
inner_data = data_obj.get("data", {}) if isinstance(data_obj, dict) else {}
ssid = inner_data.get("ssid")
password = inner_data.get("password")
ota_url = inner_data.get("url")
mode = (inner_data.get("mode") or "").strip().lower()
if not ota_url:
self.logger.error("ota missing_url")
self.safe_enqueue({"result": "missing_url"}, 2)
continue
from ota_manager import ota_manager
if ota_manager.update_thread_started:
self.safe_enqueue({"result": "update_already_started"}, 2)
continue
# 自动判断模式如果没有明确指定根据WiFi连接状态和凭证决定
if mode not in ("4g", "wifi"):
self.logger.info("ota missing mode, auto-detecting...")
# 若本次会话已锁定 4G则 OTA 自动也走 4G避免后续回切导致体验不一致
if self._session_force_4g:
mode = "4g"
self.logger.info("ota auto-selected: 4g (session locked on 4g)")
else:
# 只有同时满足WiFi已连接 且 提供了WiFi凭证才使用WiFi
if self.is_wifi_connected() and ssid and password:
mode = "wifi"
self.logger.info("ota auto-selected: wifi (WiFi connected and credentials provided)")
else: else:
# 写死的逻辑,不需要校准激光点
self.safe_enqueue({"result": "laser pos set by hard code"}, 2)
elif inner_cmd == 3: # 关闭激光
from laser_manager import laser_manager
laser_manager.turn_off_laser()
laser_manager.stop_calibration()
hardware_manager.start_idle_timer() # 开表
self.safe_enqueue({"result": "laser_off"}, 2)
elif inner_cmd == 4: # 上报电量
voltage = get_bus_voltage()
battery_percent = voltage_to_percent(voltage)
battery_data = {"battery": battery_percent, "voltage": round(voltage, 3)}
self.safe_enqueue(battery_data, 2)
self.logger.info(f"电量上报: {battery_percent}%")
elif inner_cmd == 5: # OTA 升级
inner_data = data_obj.get("data", {}) if isinstance(data_obj, dict) else {}
ssid = inner_data.get("ssid")
password = inner_data.get("password")
ota_url = inner_data.get("url")
mode = (inner_data.get("mode") or "").strip().lower()
if not ota_url:
self.logger.error("ota missing_url")
self.safe_enqueue({"result": "missing_url"}, 2)
continue
from ota_manager import ota_manager
if ota_manager.update_thread_started:
self.safe_enqueue({"result": "update_already_started"}, 2)
continue
# 自动判断模式如果没有明确指定根据WiFi连接状态和凭证决定
if mode not in ("4g", "wifi"):
self.logger.info("ota missing mode, auto-detecting...")
# 若本次会话已锁定 4G则 OTA 自动也走 4G避免后续回切导致体验不一致
if self._session_force_4g:
mode = "4g" mode = "4g"
self.logger.info("ota auto-selected: 4g (WiFi not available or no credentials)") self.logger.info("ota auto-selected: 4g (session locked on 4g)")
else:
# 只有同时满足WiFi已连接 且 提供了WiFi凭证才使用WiFi
if self.is_wifi_connected() and ssid and password:
mode = "wifi"
self.logger.info("ota auto-selected: wifi (WiFi connected and credentials provided)")
else:
mode = "4g"
self.logger.info("ota auto-selected: 4g (WiFi not available or no credentials)")
hardware_manager.stop_idle_timer() # 停表注意OTA停表之后就没有再开表因为OTA后面会重启会重新开表 hardware_manager.stop_idle_timer() # 停表注意OTA停表之后就没有再开表因为OTA后面会重启会重新开表
if mode == "4g": if mode == "4g":
ota_manager._set_ota_url(ota_url) # 记录 OTA URL供命令7使用 ota_manager._set_ota_url(ota_url) # 记录 OTA URL供命令7使用
ota_manager._start_update_thread()
_thread.start_new_thread(ota_manager.direct_ota_download_via_4g, (ota_url,))
else: # mode == "wifi"
if not ssid or not password:
self.logger.error("ota wifi mode requires ssid and password")
self.safe_enqueue({"result": "missing_ssid_or_password"}, 2)
else:
ota_manager._start_update_thread() ota_manager._start_update_thread()
_thread.start_new_thread(ota_manager.handle_wifi_and_update, (ssid, password, ota_url)) _thread.start_new_thread(ota_manager.direct_ota_download_via_4g, (ota_url,))
elif inner_cmd == 6: else: # mode == "wifi"
try: if not ssid or not password:
ip = os.popen("ifconfig wlan0 2>/dev/null | grep 'inet ' | awk '{print $2}'").read().strip() self.logger.error("ota wifi mode requires ssid and password")
ip = ip if ip else "no_ip" self.safe_enqueue({"result": "missing_ssid_or_password"}, 2)
except: else:
ip = "error_getting_ip" ota_manager._start_update_thread()
self.safe_enqueue({"result": "current_ip", "ip": ip}, 2) _thread.start_new_thread(ota_manager.handle_wifi_and_update, (ssid, password, ota_url))
# elif inner_cmd == 7: elif inner_cmd == 6:
# from ota_manager import ota_manager try:
# if ota_manager.update_thread_started: ip = os.popen("ifconfig wlan0 2>/dev/null | grep 'inet ' | awk '{print $2}'").read().strip()
# self.safe_enqueue({"result": "update_already_started"}, 2) ip = ip if ip else "no_ip"
# continue except:
ip = "error_getting_ip"
self.safe_enqueue({"result": "current_ip", "ip": ip}, 2)
# elif inner_cmd == 7:
# from ota_manager import ota_manager
# if ota_manager.update_thread_started:
# self.safe_enqueue({"result": "update_already_started"}, 2)
# continue
# try: # try:
# ip = os.popen("ifconfig wlan0 2>/dev/null | grep 'inet ' | awk '{print $2}'").read().strip() # ip = os.popen("ifconfig wlan0 2>/dev/null | grep 'inet ' | awk '{print $2}'").read().strip()
# except: # except:
# ip = None # ip = None
# if not ip: # if not ip:
# self.safe_enqueue({"result": "ota_rejected", "reason": "no_wifi_ip"}, 2) # self.safe_enqueue({"result": "ota_rejected", "reason": "no_wifi_ip"}, 2)
# else: # else:
# # 注意direct_ota_download 需要 ota_url 参数 # # 注意direct_ota_download 需要 ota_url 参数
# # 如果 ota_manager.ota_url 为 None需要从其他地方获取 # # 如果 ota_manager.ota_url 为 None需要从其他地方获取
# ota_url_to_use = ota_manager.ota_url # ota_url_to_use = ota_manager.ota_url
# if not ota_url_to_use: # if not ota_url_to_use:
# self.logger.error("[OTA] cmd=7 但 OTA_URL 未设置") # self.logger.error("[OTA] cmd=7 但 OTA_URL 未设置")
# self.safe_enqueue({"result": "ota_failed", "reason": "ota_url_not_set"}, 2) # self.safe_enqueue({"result": "ota_failed", "reason": "ota_url_not_set"}, 2)
# else: # else:
# ota_manager._start_update_thread() # ota_manager._start_update_thread()
# _thread.start_new_thread(ota_manager.direct_ota_download, (ota_url_to_use,)) # _thread.start_new_thread(ota_manager.direct_ota_download, (ota_url_to_use,))
elif inner_cmd == 41: elif inner_cmd == 41:
self.logger.info("[TEST] 收到TCP射箭触发命令") self.logger.info(f"[TEST] 收到TCP射箭触发命令, {time.time()}")
self._manual_trigger_flag = True self._manual_trigger_flag = True
self.safe_enqueue({"result": "trigger_ack"}, 2) self.safe_enqueue({"result": "trigger_ack"}, 2)
hardware_manager.start_idle_timer() # 重新计时 hardware_manager.start_idle_timer() # 重新计时
elif inner_cmd == 42: # 关机命令 elif inner_cmd == 42: # 关机命令
self.logger.info("[SHUTDOWN] 收到TCP关机命令准备关机...") self.logger.info("[SHUTDOWN] 收到TCP关机命令准备关机...")
self.safe_enqueue({"result": "shutdown_ack"}, 2) self.safe_enqueue({"result": "shutdown_ack"}, 2)
time.sleep_ms(1000) time.sleep_ms(1000)
self.disconnect_server() self.disconnect_server()
# 尝试关闭4G模块 # 尝试关闭4G模块
try: try:
with self.get_uart_lock(): with self.get_uart_lock():
hardware_manager.at_client.send("AT+CFUN=0", "OK", 5000) hardware_manager.at_client.send("AT+CFUN=0", "OK", 5000)
except: except:
pass pass
time.sleep_ms(2000) time.sleep_ms(2000)
os.system("sync") # 刷新文件系统缓存到磁盘,防止数据丢失 os.system("sync") # 刷新文件系统缓存到磁盘,防止数据丢失
time.sleep_ms(500) time.sleep_ms(500)
# os.system("poweroff") # os.system("poweroff")
hardware_manager.power_off() hardware_manager.power_off()
return return
elif inner_cmd == 43: # 上传日志命令 elif inner_cmd == 43: # 上传日志命令
# 格式: {"cmd":43, "data":{"ssid":"xxx","password":"xxx","url":"xxx", ...}} # 格式: {"cmd":43, "data":{"ssid":"xxx","password":"xxx","url":"xxx", ...}}
inner_data = data_obj.get("data", {}) inner_data = data_obj.get("data", {})
upload_url = inner_data.get("url") upload_url = inner_data.get("url")
wifi_ssid = inner_data.get("ssid") wifi_ssid = inner_data.get("ssid")
wifi_password = inner_data.get("password") wifi_password = inner_data.get("password")
include_rotated = inner_data.get("include_rotated", True) include_rotated = inner_data.get("include_rotated", True)
max_files = inner_data.get("max_files") max_files = inner_data.get("max_files")
archive_format = inner_data.get("archive", "tgz") # tgz 或 zip archive_format = inner_data.get("archive", "tgz") # tgz 或 zip
hardware_manager.start_idle_timer() # 重新计时 hardware_manager.start_idle_timer() # 重新计时
if not upload_url: if not upload_url:
self.logger.error("[LOG_UPLOAD] 缺少 url 参数") self.logger.error("[LOG_UPLOAD] 缺少 url 参数")
self.safe_enqueue({"result": "log_upload_failed", "reason": "missing_url"}, 2) self.safe_enqueue({"result": "log_upload_failed", "reason": "missing_url"}, 2)
else: else:
self.logger.info(f"[LOG_UPLOAD] 收到日志上传命令目标URL: {upload_url}") self.logger.info(f"[LOG_UPLOAD] 收到日志上传命令目标URL: {upload_url}")
# 在新线程中执行上传,避免阻塞主循环 # 在新线程中执行上传,避免阻塞主循环
import _thread import _thread
_thread.start_new_thread( _thread.start_new_thread(
self._upload_log_file, self._upload_log_file,
(upload_url, wifi_ssid, wifi_password, include_rotated, max_files, archive_format) (upload_url, wifi_ssid, wifi_password, include_rotated, max_files, archive_format)
) )
else: # data的结构不是 dict
self.logger.info(f"[NET] body={body}, {time.time()}")
else:
self.logger.info(f"[NET] 未知数据 {body}, {time.time()}")
else: else:
time.sleep_ms(5) time.sleep_ms(5)

28
wifi.py
View File

@@ -121,8 +121,8 @@ class WiFiManager:
# 优先用 MaixPy network如果可用 # 优先用 MaixPy network如果可用
try: try:
from maix import network from maix import network
wlan = network.WLAN(network.TYPE_WIFI) wifi = network.wifi.Wifi()
if wlan.isconnected(): if wifi.is_connected():
self._wifi_connected = True self._wifi_connected = True
return True return True
except: except:
@@ -480,16 +480,19 @@ class WiFiManager:
# 只在 WiFi 连接时才测量 # 只在 WiFi 连接时才测量
network_type = self._network_type_callback() network_type = self._network_type_callback()
if network_type == "wifi" and self._wifi_socket: if network_type == "wifi" and self._wifi_socket:
# 测量 RTT1 个样本,快速测量) # # 测量 RTT1 个样本,快速测量)
rtt_ms, reachable = self._measure_wifi_tcp_rtt_ms( # rtt_ms, reachable = self._measure_wifi_tcp_rtt_ms(
self._server_ip, self._server_port, # self._server_ip, self._server_port,
samples=1, per_sample_timeout_ms=600 # samples=1, per_sample_timeout_ms=600
) # )
# 获取 RSSI # 获取 RSSI
rssi_dbm = self._get_wifi_rssi_dbm() rssi_dbm = self._get_wifi_rssi_dbm()
# 更新缓存 # 更新缓存
# 不使用 RTT 测量
rtt_ms = 0
reachable = True
self._last_wifi_rtt_ms = rtt_ms if reachable else None self._last_wifi_rtt_ms = rtt_ms if reachable else None
self._last_wifi_rssi_dbm = rssi_dbm self._last_wifi_rssi_dbm = rssi_dbm
self.logger.debug(f"[WiFi Monitor] - RTT={rtt_ms:.0f}ms, RSSI={rssi_dbm:.0f}dBm") self.logger.debug(f"[WiFi Monitor] - RTT={rtt_ms:.0f}ms, RSSI={rssi_dbm:.0f}dBm")
@@ -506,10 +509,13 @@ class WiFiManager:
for retry_idx in range(2): for retry_idx in range(2):
time.sleep_ms(1000) time.sleep_ms(1000)
rtt2, reachable2 = self._measure_wifi_tcp_rtt_ms( # 不使用 RTT 测量
self._server_ip, self._server_port, rtt2 = 0
samples=1, per_sample_timeout_ms=600 reachable2 = True
) # rtt2, reachable2 = self._measure_wifi_tcp_rtt_ms(
# self._server_ip, self._server_port,
# samples=1, per_sample_timeout_ms=600
# )
rssi2 = self._get_wifi_rssi_dbm() rssi2 = self._get_wifi_rssi_dbm()
# 更新缓存,便于外部查看最新状态 # 更新缓存,便于外部查看最新状态