This commit is contained in:
yrx
2026-08-12 14:59:13 +08:00
parent 165eeff64e
commit dc5da0294f
14 changed files with 202 additions and 15 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+37 -1
View File
@@ -69,7 +69,7 @@ class ATClient:
# 同上:避免在 _reader_loop 持锁期间二次 acquire # 同上:避免在 _reader_loop 持锁期间二次 acquire
self._http_events.append(ev) self._http_events.append(ev)
def send(self, cmd: str, expect: str = "OK", timeout_ms: int = 2000): def send(self, cmd: str, expect: str = "OK", timeout_ms: int = 2000, abort_event=None):
""" """
发送 AT 命令并等待 expect(子串匹配)。 发送 AT 命令并等待 expect(子串匹配)。
注意:expect=">" 用于等待 prompt。 注意:expect=">" 用于等待 prompt。
@@ -90,6 +90,9 @@ class ATClient:
t0 = time.ticks_ms() t0 = time.ticks_ms()
while abs(time.ticks_diff(time.ticks_ms(), t0)) < timeout_ms: while abs(time.ticks_diff(time.ticks_ms(), t0)) < timeout_ms:
if abort_event is not None and abort_event.is_set():
self._waiting = False
break
if (not self._waiting) or (self._expect in self._resp): if (not self._waiting) or (self._expect in self._resp):
self._waiting = False self._waiting = False
break break
@@ -102,6 +105,39 @@ class ATClient:
except: except:
return str(self._resp) return str(self._resp)
def send_raw_and_wait(self, data: bytes, expect: str = "OK", timeout_ms: int = 1000,
suffix: bytes = b""):
"""Register the response waiter before writing raw UART data."""
expect_b = expect.encode() if isinstance(expect, str) else expect
with self._cmd_lock:
with self._q_lock:
self._waiting = True
self._expect = expect_b
self._resp = b""
total = 0
while total < len(data):
n = self.uart.write(data[total:])
if not n or n < 0:
time.sleep_ms(1)
continue
total += n
if suffix:
self.uart.write(suffix)
t0 = time.ticks_ms()
while abs(time.ticks_diff(time.ticks_ms(), t0)) < timeout_ms:
if (not self._waiting) or (self._expect in self._resp):
self._waiting = False
break
time.sleep_ms(5)
self._waiting = False
try:
return self._resp.decode(errors="ignore")
except:
return str(self._resp)
def _find_urc_tag(self, tag: bytes): def _find_urc_tag(self, tag: bytes):
""" """
只在"真正的 URC 边界"查找 tag,避免误命中 HTTP payload 内容。 只在"真正的 URC 边界"查找 tag,避免误命中 HTTP payload 内容。
+4 -1
View File
@@ -346,10 +346,13 @@ AUTO_POWER_OFF_IN_SECONDS = 10 * 60 # 自动关机时间(秒),0表示不
# 实机数据:正常放电约为正电流,插入充电线后约为负电流。 # 实机数据:正常放电约为正电流,插入充电线后约为负电流。
CHARGING_SHUTDOWN_ENABLED = True # True=充电时退出应用,False=关闭充电关机功能 CHARGING_SHUTDOWN_ENABLED = True # True=充电时退出应用,False=关闭充电关机功能
CHARGING_DIAGNOSTIC_LOG_ENABLED = False CHARGING_DIAGNOSTIC_LOG_ENABLED = False
CHARGING_CHECK_INTERVAL_MS = 5000 CHARGING_CHECK_INTERVAL_MS = 3000
CHARGING_CURRENT_THRESHOLD_MA = 100.0 CHARGING_CURRENT_THRESHOLD_MA = 100.0
CHARGING_CONFIRM_COUNT = 2 CHARGING_CONFIRM_COUNT = 2
CHARGING_NOTIFY_TIMEOUT_MS = 30000 CHARGING_NOTIFY_TIMEOUT_MS = 30000
CHARGING_4G_UART_LOCK_TIMEOUT_SEC = 2.5
CHARGING_4G_PROMPT_TIMEOUT_MS = 1500
CHARGING_4G_CONFIRM_TIMEOUT_MS = 1000
CHARGING_EXIT_SCRIPT = APP_DIR + "/charging_exit.sh" CHARGING_EXIT_SCRIPT = APP_DIR + "/charging_exit.sh"
BATTERY_SOC_LPF_ALPHA = 0.5 BATTERY_SOC_LPF_ALPHA = 0.5
+120 -3
View File
@@ -67,6 +67,7 @@ class NetworkManager:
self._queue_lock = threading.Lock() self._queue_lock = threading.Lock()
self._send_event = threading.Event() self._send_event = threading.Event()
self._uart4g_lock = threading.Lock() self._uart4g_lock = threading.Lock()
self._terminal_send_event = threading.Event()
self._device_id = None self._device_id = None
self._password = None self._password = None
self._raw_line_data = [] self._raw_line_data = []
@@ -676,7 +677,7 @@ class NetworkManager:
except OSError: except OSError:
pass pass
w = network.wifi.Wifi() w = network.wifi.Wifi()
e = w.connect(ssid, password, wait=True, timeout=15) e = w.connect(ssid, password, wait=True, timeout=10)
err.check_raise(e, "connect wifi failed") err.check_raise(e, "connect wifi failed")
if self.logger: if self.logger:
self.logger.info(f"[ota] Connect success, got ip{w.get_ip()}") self.logger.info(f"[ota] Connect success, got ip{w.get_ip()}")
@@ -715,6 +716,29 @@ class NetworkManager:
self._enqueue((msg_type, data_dict, sent_event), high) self._enqueue((msg_type, data_dict, sent_event), high)
return bool(sent_event.wait(max(0, int(timeout_ms)) / 1000.0)) return bool(sent_event.wait(max(0, int(timeout_ms)) / 1000.0))
def safe_replace_queue_and_wait(self, data_dict, msg_type=2, timeout_ms=30000):
"""Drop queued messages, enqueue one terminal message, and wait for its TCP write."""
sent_event = threading.Event()
with self._queue_lock:
self._high_send_queue.clear()
self._normal_send_queue.clear()
self._high_send_queue.append((msg_type, data_dict, sent_event))
self._send_event.set()
return bool(sent_event.wait(max(0, int(timeout_ms)) / 1000.0))
def safe_terminal_send_and_wait(self, data_dict, msg_type=2, timeout_ms=30000):
"""Cancel ordinary 4G waits and replace queued work with one terminal message."""
sent_event = threading.Event()
result = {"sent": False}
self._terminal_send_event.set()
with self._queue_lock:
self._high_send_queue.clear()
self._normal_send_queue.clear()
self._high_send_queue.append((msg_type, data_dict, sent_event, "terminal", result))
self._send_event.set()
completed = sent_event.wait(max(0, int(timeout_ms)) / 1000.0)
return bool(completed and result["sent"])
def connect_server(self): def connect_server(self):
""" """
连接到服务器(自动选择WiFi或4G) 连接到服务器(自动选择WiFi或4G)
@@ -1114,8 +1138,12 @@ class NetworkManager:
return False return False
try: try:
for _ in range(max_retries): for _ in range(max_retries):
if self._terminal_send_event.is_set():
return False
cmd = f'AT+MIPSEND={link_id},{len(data)}' cmd = f'AT+MIPSEND={link_id},{len(data)}'
if ">" not in hardware_manager.at_client.send(cmd, ">", 2000): if ">" not in hardware_manager.at_client.send(cmd, ">", 2000):
if self._terminal_send_event.is_set():
return False
time.sleep_ms(50) time.sleep_ms(50)
continue continue
@@ -1130,14 +1158,73 @@ class NetworkManager:
hardware_manager.uart4g.write(b"\x1A") hardware_manager.uart4g.write(b"\x1A")
with hardware_manager.at_client._q_lock: with hardware_manager.at_client._q_lock:
hardware_manager.at_client._rx = b"" hardware_manager.at_client._rx = b""
r = hardware_manager.at_client.send("", "OK", 8000) r = hardware_manager.at_client.send(
"", "OK", 8000, abort_event=self._terminal_send_event
)
if ("SEND OK" in r) or ("OK" in r) or ("+MIPSEND" in r): if ("SEND OK" in r) or ("OK" in r) or ("+MIPSEND" in r):
return True return True
if self._terminal_send_event.is_set():
return False
time.sleep_ms(50) time.sleep_ms(50)
return False return False
finally: finally:
self._uart4g_lock.release() self._uart4g_lock.release()
def _tcp_send_terminal_raw(self, data: bytes) -> bool:
if not self._tcp_connected:
return False
if self._network_type == "wifi":
return self._tcp_send_raw_via_wifi(data, max_retries=1)
if self._network_type != "4g":
return False
link_id = getattr(config, "TCP_LINK_ID", 0)
lock_timeout_sec = float(
getattr(config, "CHARGING_4G_UART_LOCK_TIMEOUT_SEC", 2.5)
)
prompt_timeout_ms = int(
getattr(config, "CHARGING_4G_PROMPT_TIMEOUT_MS", 1500)
)
confirm_timeout_ms = int(
getattr(config, "CHARGING_4G_CONFIRM_TIMEOUT_MS", 1000)
)
lock_start_ms = time.ticks_ms()
if not self._uart4g_lock.acquire(timeout=max(0.0, lock_timeout_sec)):
self.logger.warning(
f"[CHARGE-4G] uart_lock timeout timeout_sec={lock_timeout_sec}"
)
return False
try:
lock_elapsed_ms = abs(time.ticks_diff(time.ticks_ms(), lock_start_ms))
cmd = f'AT+MIPSEND={link_id},{len(data)}'
prompt_start_ms = time.ticks_ms()
if ">" not in hardware_manager.at_client.send(
cmd, ">", max(0, prompt_timeout_ms)):
prompt_elapsed_ms = abs(time.ticks_diff(time.ticks_ms(), prompt_start_ms))
self.logger.warning(
f"[CHARGE-4G] prompt failed lock_ms={lock_elapsed_ms} "
f"prompt_ms={prompt_elapsed_ms}"
)
return False
prompt_elapsed_ms = abs(time.ticks_diff(time.ticks_ms(), prompt_start_ms))
confirm_start_ms = time.ticks_ms()
r = hardware_manager.at_client.send_raw_and_wait(
data,
expect="OK",
timeout_ms=max(0, confirm_timeout_ms),
suffix=b"\x1A",
)
confirm_elapsed_ms = abs(time.ticks_diff(time.ticks_ms(), confirm_start_ms))
sent = ("SEND OK" in r) or ("OK" in r) or ("+MIPSEND" in r)
self.logger.warning(
f"[CHARGE-4G] send_done lock_ms={lock_elapsed_ms} "
f"prompt_ms={prompt_elapsed_ms} confirm_ms={confirm_elapsed_ms} "
f"sent={sent}"
)
return sent
finally:
self._uart4g_lock.release()
def _configure_ssl_before_connect(self, link_id: int) -> bool: def _configure_ssl_before_connect(self, link_id: int) -> bool:
"""按手册:MSSLCFG(auth) -> (可选) MSSLCERTWR -> MSSLCFG(cert) -> MIPCFG(ssl)""" """按手册:MSSLCFG(auth) -> (可选) MSSLCERTWR -> MSSLCFG(cert) -> MIPCFG(ssl)"""
ssl_id = getattr(config, "SSL_ID", 1) ssl_id = getattr(config, "SSL_ID", 1)
@@ -1874,12 +1961,25 @@ class NetworkManager:
pending_cleared = False pending_cleared = False
last_heartbeat_ack_time = time.ticks_ms() last_heartbeat_ack_time = time.ticks_ms()
last_heartbeat_send_time = time.ticks_ms() last_heartbeat_send_time = time.ticks_ms()
last_wifi_sta_check_time = time.ticks_ms()
while True: while True:
# 如果底层连接已断开,尽快跳出内层循环触发重连/重选网络 # 如果底层连接已断开,尽快跳出内层循环触发重连/重选网络
if not self._tcp_connected: if not self._tcp_connected:
break break
if self._network_type == "wifi":
now_ms = time.ticks_ms()
if abs(time.ticks_diff(now_ms, last_wifi_sta_check_time)) >= 1000:
last_wifi_sta_check_time = now_ms
if not wifi_manager.is_sta_associated():
self.logger.warning(
"[WIFI-TCP] STA disconnected; leave WiFi session and reselect network"
)
wifi_manager.disconnect_wifi()
self._tcp_connected = False
break
# OTA 期间暂停 TCP 活动 # OTA 期间暂停 TCP 活动
try: try:
from ota_manager import ota_manager from ota_manager import ota_manager
@@ -2312,8 +2412,23 @@ class NetworkManager:
if item: if item:
msg_type, data_dict = item[:2] msg_type, data_dict = item[:2]
sent_event = item[2] if len(item) > 2 else None sent_event = item[2] if len(item) > 2 else None
item_is_terminal = len(item) > 3 and item[3] == "terminal"
terminal_result = item[4] if item_is_terminal and len(item) > 4 else None
pkt = self._netcore.make_packet(msg_type, data_dict) pkt = self._netcore.make_packet(msg_type, data_dict)
if not self.tcp_send_raw(pkt): send_ok = (
self._tcp_send_terminal_raw(pkt)
if item_is_terminal
else self.tcp_send_raw(pkt)
)
if not send_ok:
if item_is_terminal:
if terminal_result is not None:
terminal_result["sent"] = False
if sent_event is not None:
sent_event.set()
break
if self._terminal_send_event.is_set():
continue
# 发送失败:将消息放回队首(队列满则丢弃) # 发送失败:将消息放回队首(队列满则丢弃)
with self.get_queue_lock(): with self.get_queue_lock():
if item_is_high: if item_is_high:
@@ -2329,6 +2444,8 @@ class NetworkManager:
pass pass
break break
if sent_event is not None: if sent_event is not None:
if terminal_result is not None:
terminal_result["sent"] = True
sent_event.set() sent_event.set()
# 发送激光校准结果 # 发送激光校准结果
+25 -2
View File
@@ -179,6 +179,11 @@ def charging_shutdown_monitor():
if current_ma < -abs(threshold_ma): if current_ma < -abs(threshold_ma):
confirm_count += 1 confirm_count += 1
if logger:
logger.warning(
f"[CHARGE-TIMING] sample tick_ms={maix_time.ticks_ms()} "
f"current={current_ma:.1f}mA confirm={confirm_count}/{confirm_required}"
)
if logger: if logger:
logger.info( logger.info(
f"[CHARGE] INA226 充电电流 {current_ma:.1f}mA " f"[CHARGE] INA226 充电电流 {current_ma:.1f}mA "
@@ -209,12 +214,30 @@ def charging_shutdown_monitor():
0, 0,
int(getattr(config, "CHARGING_NOTIFY_TIMEOUT_MS", 30000)), int(getattr(config, "CHARGING_NOTIFY_TIMEOUT_MS", 30000)),
) )
notification_sent = network_manager.safe_enqueue_and_wait( notify_start_ms = maix_time.ticks_ms()
if logger:
logger.warning(
f"[CHARGE-TIMING] enqueue_start tick_ms={notify_start_ms} "
f"network={network_manager.network_type} "
f"tcp_connected={network_manager.tcp_connected} "
f"timeout_ms={notify_timeout_ms}"
)
notification_sent = network_manager.safe_terminal_send_and_wait(
{"poweroff": "充电中"}, {"poweroff": "充电中"},
2, 2,
high=True,
timeout_ms=notify_timeout_ms, timeout_ms=notify_timeout_ms,
) )
notify_end_ms = maix_time.ticks_ms()
notify_elapsed_ms = abs(
maix_time.ticks_diff(notify_end_ms, notify_start_ms)
)
if logger:
logger.warning(
f"[CHARGE-TIMING] enqueue_done tick_ms={notify_end_ms} "
f"elapsed_ms={notify_elapsed_ms} sent={bool(notification_sent)} "
f"network={network_manager.network_type} "
f"tcp_connected={network_manager.tcp_connected}"
)
if notification_sent: if notification_sent:
if logger: if logger:
logger.info("[CHARGE] 充电状态已发送到服务器") logger.info("[CHARGE] 充电状态已发送到服务器")
Binary file not shown.
+1
View File
@@ -36,3 +36,4 @@
# 2.15.24 空改测试 # 2.15.24 空改测试
# 2.15.25 修复整合后关机失败和ota格式更新问题 # 2.15.25 修复整合后关机失败和ota格式更新问题
# 2.15.26 # 2.15.26
# 2.15.33 26-8-12 14:03 修改充4g电关机时间 修复切换网络卡住bug
+1 -1
View File
@@ -4,6 +4,6 @@
应用版本号 应用版本号
每次 OTA 更新时,只需要更新这个文件中的版本号 每次 OTA 更新时,只需要更新这个文件中的版本号
""" """
VERSION = '2.15.31' VERSION = '2.15.33'
+14 -7
View File
@@ -549,15 +549,20 @@ class WiFiManager:
on_poor_quality_callback: WiFi质量差时的回调函数 on_poor_quality_callback: WiFi质量差时的回调函数
""" """
with self._wifi_quality_lock: with self._wifi_quality_lock:
if self._wifi_quality_monitor_thread is not None and self._wifi_quality_monitor_thread.is_alive(): current_thread = self._wifi_quality_monitor_thread
current_stop_event = self._wifi_quality_stop_event
if (current_thread is not None and current_thread.is_alive()
and not current_stop_event.is_set()):
self.logger.warning("[WiFi Monitor] 监测线程已在运行") self.logger.warning("[WiFi Monitor] 监测线程已在运行")
return return
self._network_type_callback = network_type_callback self._network_type_callback = network_type_callback
self._on_poor_quality_callback = on_poor_quality_callback self._on_poor_quality_callback = on_poor_quality_callback
self._wifi_quality_stop_event.clear() stop_event = threading.Event()
self._wifi_quality_stop_event = stop_event
self._wifi_quality_monitor_thread = threading.Thread( self._wifi_quality_monitor_thread = threading.Thread(
target=self._quality_monitor_loop, target=self._quality_monitor_loop,
args=(stop_event,),
daemon=True, daemon=True,
name="wifi_quality_monitor" name="wifi_quality_monitor"
) )
@@ -568,13 +573,14 @@ class WiFiManager:
"""停止 WiFi 质量监测线程""" """停止 WiFi 质量监测线程"""
with self._wifi_quality_lock: with self._wifi_quality_lock:
t = self._wifi_quality_monitor_thread t = self._wifi_quality_monitor_thread
stop_event = self._wifi_quality_stop_event
if t is None: if t is None:
return return
if not t.is_alive(): if not t.is_alive():
self._wifi_quality_monitor_thread = None self._wifi_quality_monitor_thread = None
return return
self._wifi_quality_stop_event.set() stop_event.set()
try: try:
t.join(timeout=2.0) t.join(timeout=2.0)
except Exception as e: except Exception as e:
@@ -588,12 +594,12 @@ class WiFiManager:
self._wifi_quality_monitor_thread = None self._wifi_quality_monitor_thread = None
self.logger.info("[WiFi Monitor] 已停止后台监测线程") self.logger.info("[WiFi Monitor] 已停止后台监测线程")
def _quality_monitor_loop(self): def _quality_monitor_loop(self, stop_event):
""" """
WiFi 质量监测循环(后台线程) WiFi 质量监测循环(后台线程)
每 5 秒检查 STA 关联状态和 RSSI,发现断链或质量差则触发切换 每 5 秒检查 STA 关联状态和 RSSI,发现断链或质量差则触发切换
""" """
while not self._wifi_quality_stop_event.is_set(): while not stop_event.is_set():
try: try:
# 只在 WiFi 连接时才测量 # 只在 WiFi 连接时才测量
network_type = self._network_type_callback() network_type = self._network_type_callback()
@@ -631,7 +637,8 @@ class WiFiManager:
self.logger.warning("[WiFi Monitor] 质量差,切换前快速重试 2 次(每次间隔1秒)") self.logger.warning("[WiFi Monitor] 质量差,切换前快速重试 2 次(每次间隔1秒)")
for retry_idx in range(2): for retry_idx in range(2):
time.sleep_ms(1000) if stop_event.wait(1.0):
return
reachable2 = self.is_sta_associated() reachable2 = self.is_sta_associated()
rtt2 = None rtt2 = None
rssi2 = self._get_wifi_rssi_dbm() rssi2 = self._get_wifi_rssi_dbm()
@@ -660,7 +667,7 @@ class WiFiManager:
self._on_poor_quality_callback() self._on_poor_quality_callback()
# 休眠 5 秒 # 休眠 5 秒
time.sleep(5) stop_event.wait(5.0)
except Exception as e: except Exception as e:
self.logger.error(f"[WiFi Monitor] 监测异常:{e}") self.logger.error(f"[WiFi Monitor] 监测异常:{e}")