refind logger

This commit is contained in:
gcw_4spBpAfv
2026-01-20 18:40:54 +08:00
parent 0ce140a210
commit 945077a453
10 changed files with 621 additions and 661 deletions

View File

@@ -55,11 +55,15 @@ class NetworkManager:
self._wifi_socket_lock = threading.Lock()
self._prefer_wifi = True # 是否优先使用WiFi
self._wifi_recv_buffer = b"" # WiFi接收缓冲区
self._initialized = True
# ==================== 状态访问(只读属性)====================
@property
def logger(self):
"""获取 logger 对象"""
return logger_manager.logger
@property
def tcp_connected(self):
"""TCP连接状态"""
@@ -162,17 +166,13 @@ class NetworkManager:
with open("/device_key", "r") as f:
device_id = f.read().strip()
if device_id:
logger = logger_manager.logger
if logger:
logger.debug(f"[INFO] 从 /device_key 读取到 DEVICE_ID: {device_id}")
self.logger.debug(f"[INFO] 从 /device_key 读取到 DEVICE_ID: {device_id}")
# 设置内部状态
self._device_id = device_id
self._password = device_id + "."
return device_id
except Exception as e:
logger = logger_manager.logger
if logger:
logger.error(f"[ERROR] 无法读取 /device_key: {e}")
self.logger.error(f"[ERROR] 无法读取 /device_key: {e}")
# 使用默认值
default_id = "DEFAULT_DEVICE_ID"
@@ -246,18 +246,14 @@ class NetworkManager:
if ip:
self._wifi_connected = True
self._wifi_ip = ip
logger = logger_manager.logger
if logger:
logger.info(f"[WIFI] 已连接IP: {ip}")
self.logger.info(f"[WIFI] 已连接IP: {ip}")
return ip, None
std_time.sleep(1)
return None, "Timeout: No IP obtained"
except Exception as e:
logger = logger_manager.logger
if logger:
logger.error(f"[WIFI] 连接失败: {e}")
self.logger.error(f"[WIFI] 连接失败: {e}")
return None, f"Exception: {str(e)}"
def is_server_reachable(self, host, port=80, timeout=5):
@@ -270,9 +266,7 @@ class NetworkManager:
s.close()
return True
except Exception as e:
logger = logger_manager.logger
if logger:
logger.warning(f"[NET] 无法连接 {host}:{port} - {e}")
self.logger.warning(f"[NET] 无法连接 {host}:{port} - {e}")
return False
# ==================== 网络选择策略 ====================
@@ -290,31 +284,25 @@ class NetworkManager:
if prefer_wifi is None:
prefer_wifi = self._prefer_wifi
logger = logger_manager.logger
# 策略1如果指定优先WiFi且WiFi可用使用WiFi
if prefer_wifi and self.is_wifi_connected():
# 检查WiFi是否能连接到服务器
if self.is_server_reachable(config.SERVER_IP, config.SERVER_PORT, timeout=3):
self._network_type = "wifi"
if logger:
logger.info(f"[NET] 选择WiFi网络IP: {self._wifi_ip}")
self.logger.info(f"[NET] 选择WiFi网络IP: {self._wifi_ip}")
return "wifi"
else:
if logger:
logger.warning("[NET] WiFi已连接但无法访问服务器尝试4G")
self.logger.warning("[NET] WiFi已连接但无法访问服务器尝试4G")
# 策略2如果WiFi可用使用WiFi
if self.is_wifi_connected():
if self.is_server_reachable(config.SERVER_IP, config.SERVER_PORT, timeout=3):
self._network_type = "wifi"
if logger:
logger.info(f"[NET] 选择WiFi网络IP: {self._wifi_ip}")
self.logger.info(f"[NET] 选择WiFi网络IP: {self._wifi_ip}")
return "wifi"
# 策略3回退到4G
if logger:
logger.info("[NET] WiFi不可用或无法连接服务器使用4G网络")
self.logger.info("[NET] WiFi不可用或无法连接服务器使用4G网络")
self._network_type = "4g"
return "4g"
@@ -362,9 +350,7 @@ class NetworkManager:
if not network_type:
return False
logger = logger_manager.logger
if logger:
logger.info(f"连接到服务器(使用{network_type.upper()}...")
self.logger.info(f"连接到服务器,使用{network_type.upper()}...")
# 根据网络类型建立TCP连接
if network_type == "wifi":
@@ -389,15 +375,11 @@ class NetworkManager:
self._wifi_socket.setblocking(False)
self._tcp_connected = True
logger = logger_manager.logger
if logger:
logger.info("[WIFI-TCP] TCP连接已建立")
self.logger.info("[WIFI-TCP] TCP连接已建立")
return True
except Exception as e:
logger = logger_manager.logger
if logger:
logger.error(f"[WIFI-TCP] 连接失败: {e}")
self.logger.error(f"[WIFI-TCP] 连接失败: {e}")
if self._wifi_socket:
try:
@@ -409,16 +391,30 @@ class NetworkManager:
return False
def _connect_tcp_via_4g(self):
"""通过4G模块建立TCP连接"""
"""通过4G模块建立TCP连接(支持按手册绑定 SSL"""
link_id = getattr(config, "TCP_LINK_ID", 0)
use_ssl = getattr(config, "USE_TCP_SSL", False)
host = config.SERVER_IP
port = getattr(config, "TCP_SSL_PORT", 443) if use_ssl else config.SERVER_PORT
tail = getattr(config, "MIPOPEN_TAIL", "")
with self.get_uart_lock():
hardware_manager.at_client.send("AT+MIPCLOSE=0", "OK", 1000)
res = hardware_manager.at_client.send(f'AT+MIPOPEN=0,"TCP","{config.SERVER_IP}",{config.SERVER_PORT}', "+MIPOPEN", 8000)
if "+MIPOPEN: 0,0" in res:
resp = hardware_manager.at_client.send(f"AT+MIPCLOSE={link_id}", "OK", 1000)
self.logger.info(f"[4G-TCP] AT+MIPCLOSE={link_id} response: {resp}")
if use_ssl:
ok = self._configure_ssl_before_connect(link_id)
if not ok:
return False
# 按手册AT+MIPOPEN=1,"TCP","host",443,,0
# cmd = f'AT+MIPOPEN={link_id},"TCP","{host}",{port}{tail}'
cmd = f'AT+MIPOPEN={link_id},"TCP","{host}",{port}'
res = hardware_manager.at_client.send(cmd, "+MIPOPEN", 8000)
self.logger.info(f"[4G-TCP] {cmd} response: {res}")
if f"+MIPOPEN: {link_id},0" in res:
self._tcp_connected = True
return True
else:
logger = logger_manager.logger
logger.error(f"[4G-TCP] 连接失败: {res}")
return False
def _check_wifi_connection(self):
@@ -442,9 +438,7 @@ class NetworkManager:
def disconnect_server(self):
"""断开TCP连接"""
if self._tcp_connected:
logger = logger_manager.logger
if logger:
logger.info("与服务器断开链接")
self.logger.info("与服务器断开链接")
if self._network_type == "wifi":
self._disconnect_tcp_via_wifi()
@@ -465,9 +459,10 @@ class NetworkManager:
self._wifi_socket = None
def _disconnect_tcp_via_4g(self):
"""断开4G TCP连接"""
link_id = getattr(config, "TCP_LINK_ID", 0)
with self.get_uart_lock():
hardware_manager.at_client.send("AT+MIPCLOSE=0", "OK", 1000)
hardware_manager.at_client.send(f"AT+MIPCLOSE={link_id}", "OK", 1000)
def tcp_send_raw(self, data: bytes, max_retries=2) -> bool:
"""
@@ -489,9 +484,7 @@ class NetworkManager:
elif self._network_type == "4g":
return self._tcp_send_raw_via_4g(data, max_retries)
else:
logger = logger_manager.logger
if logger:
logger.error("[NET] 未选择网络类型,无法发送数据")
self.logger.error("[NET] 未选择网络类型,无法发送数据")
return False
def _tcp_send_raw_via_wifi(self, data: bytes, max_retries=2) -> bool:
@@ -508,9 +501,7 @@ class NetworkManager:
sent = self._wifi_socket.send(data[total_sent:])
if sent == 0:
# socket连接已断开
logger = logger_manager.logger
if logger:
logger.warning(f"[WIFI-TCP] 发送失败socket已断开尝试 {attempt+1}/{max_retries}")
self.logger.warning(f"[WIFI-TCP] 发送失败socket已断开尝试 {attempt+1}/{max_retries}")
break
total_sent += sent
@@ -521,23 +512,19 @@ class NetworkManager:
time.sleep_ms(50)
except OSError as e:
logger = logger_manager.logger
if logger:
logger.error(f"[WIFI-TCP] 发送异常: {e}(尝试 {attempt+1}/{max_retries}")
self.logger.error(f"[WIFI-TCP] 发送异常: {e}(尝试 {attempt+1}/{max_retries}")
time.sleep_ms(50)
except Exception as e:
logger = logger_manager.logger
if logger:
logger.error(f"[WIFI-TCP] 未知错误: {e}(尝试 {attempt+1}/{max_retries}")
self.logger.error(f"[WIFI-TCP] 未知错误: {e}(尝试 {attempt+1}/{max_retries}")
time.sleep_ms(50)
return False
def _tcp_send_raw_via_4g(self, data: bytes, max_retries=2) -> bool:
"""通过4G模块发送TCP数据"""
link_id = getattr(config, "TCP_LINK_ID", 0)
with self.get_uart_lock():
for _ in range(max_retries):
cmd = f'AT+MIPSEND=0,{len(data)}'
cmd = f'AT+MIPSEND={link_id},{len(data)}'
if ">" not in hardware_manager.at_client.send(cmd, ">", 2000):
time.sleep_ms(50)
continue
@@ -551,15 +538,62 @@ class NetworkManager:
total += n
hardware_manager.uart4g.write(b"\x1A")
r = hardware_manager.at_client.send("", "OK", 8000)
if ("SEND OK" in r) or ("OK" in r) or ("+MIPSEND" in r):
return True
time.sleep_ms(50)
return False
def _configure_ssl_before_connect(self, link_id: int) -> bool:
"""按手册MSSLCFG(auth) -> (可选) MSSLCERTWR -> MSSLCFG(cert) -> MIPCFG(ssl)"""
ssl_id = getattr(config, "SSL_ID", 1)
auth_mode = getattr(config, "SSL_AUTH_MODE", 1)
verify_mode = getattr(config, "SSL_VERIFY_MODE", 0)
# 1) 配置认证方式
# r = hardware_manager.at_client.send(f'AT+MSSLCFG="auth",{ssl_id},{auth_mode}', "OK", 3000)
r = hardware_manager.at_client.send(f'AT+MSSLCFG="auth",{ssl_id},0', "OK", 3000)
self.logger.info(f"[4G-TCP] AT+MSSLCFG=\"auth\",{ssl_id},0 response: {r}")
if "OK" not in r:
return False
# 2) 写入根证书(只有 verify_mode=1 才需要)
# if verify_mode == 1:
if False:
cert_filename = getattr(config, "SSL_CERT_FILENAME", None)
cert_path = getattr(config, "SSL_CERT_PATH", None)
if not cert_filename or not cert_path:
return False
# 读取证书文件(设备侧路径)
with open(cert_path, "rb") as f:
cert_data = f.read()
# 按手册AT+MSSLCERTWR="file",0,size -> 等待 ">" -> 写入证书内容 -> 等 OK
r = hardware_manager.at_client.send(f'AT+MSSLCERTWR="{cert_filename}",0,{len(cert_data)}', ">", 5000)
if ">" not in r:
return False
# 直接写原始字节,不要额外拼 \r\n
hardware_manager.uart4g.write(cert_data)
r = hardware_manager.at_client.send("", "OK", 8000)
if "OK" not in r:
return False
# 3) 引用根证书
r = hardware_manager.at_client.send(f'AT+MSSLCFG="cert",{ssl_id},"{cert_filename}"', "OK", 3000)
if "OK" not in r:
return False
# 4) 绑定 TCP 通道到 ssl_id并启用
r = hardware_manager.at_client.send(f'AT+MIPCFG="ssl",{link_id},{ssl_id},1', "OK", 3000)
self.logger.info(f"[4G-TCP] AT+MIPCFG=\"ssl\",{link_id},{ssl_id},1 response: {r}")
if "OK" not in r:
return False
return True
def receive_tcp_data_via_wifi(self, timeout_ms=100):
"""
通过WiFi接收TCP数据
@@ -586,9 +620,7 @@ class NetworkManager:
return b""
except OSError as e:
# socket错误连接断开等
logger = logger_manager.logger
if logger:
logger.warning(f"[WIFI-TCP] 接收数据失败: {e}")
self.logger.warning(f"[WIFI-TCP] 接收数据失败: {e}")
# 关闭socket
try:
@@ -600,9 +632,7 @@ class NetworkManager:
return b""
except Exception as e:
logger = logger_manager.logger
if logger:
logger.error(f"[WIFI-TCP] 接收数据异常: {e}")
self.logger.error(f"[WIFI-TCP] 接收数据异常: {e}")
return b""
@@ -615,9 +645,7 @@ class NetworkManager:
def send_http_cmd(self, cmd_str, timeout_ms=3000):
"""发送 HTTP 相关 AT 指令(调试用)"""
logger = logger_manager.logger
if logger:
logger.debug(f"[HTTP AT] => {cmd_str}")
self.logger.debug(f"[HTTP AT] => {cmd_str}")
return hardware_manager.at_client.send(cmd_str, "OK", timeout_ms)
@@ -644,9 +672,7 @@ class NetworkManager:
import _thread
from maix import camera
logger = logger_manager.logger
if logger:
logger.info("[NET] TCP主线程启动")
self.logger.info("[NET] TCP主线程启动")
send_hartbeat_fail_count = 0
last_charging_check = 0
@@ -666,9 +692,7 @@ class NetworkManager:
time.sleep_ms(200)
continue
except Exception as e:
logger = logger_manager.logger
if logger:
logger.error(f"[NET] OTA检查异常: {e}")
self.logger.error(f"[NET] OTA检查异常: {e}")
time.sleep_ms(200)
continue
@@ -690,8 +714,7 @@ class NetworkManager:
time.sleep_ms(2000)
continue
if logger:
logger.info("➡️ 登录包已发送,等待确认...")
self.logger.info("➡️ 登录包已发送,等待确认...")
logged_in = False
pending_cleared = False
last_heartbeat_ack_time = time.ticks_ms()
@@ -705,9 +728,7 @@ class NetworkManager:
time.sleep_ms(200)
continue
except Exception as e:
logger = logger_manager.logger
if logger:
logger.error(f"[NET] OTA检查异常: {e}")
self.logger.error(f"[NET] OTA检查异常: {e}")
time.sleep_ms(200)
continue
@@ -752,8 +773,7 @@ class NetworkManager:
if not logged_in:
try:
if logger:
logger.debug(f"[TCP] rx link={link_id} len={len(payload)} head={payload[:12].hex()}")
self.logger.debug(f"[TCP] rx link={link_id} len={len(payload)} head={payload[:12].hex()}")
except:
pass
@@ -764,8 +784,7 @@ class NetworkManager:
if body and body.get("cmd") == 1 and body.get("data") == "登录成功":
logged_in = True
last_heartbeat_ack_time = time.ticks_ms()
if logger:
logger.info("登录成功")
self.logger.info("登录成功")
# 检查 ota_pending.json
try:
@@ -777,19 +796,16 @@ class NetworkManager:
except:
pending_obj = {}
self.safe_enqueue({"result": "ota_ok", "url": pending_obj.get("url", "")}, 2)
if logger:
logger.info("[OTA] 已上报 ota_ok等待心跳确认后删除 pending")
self.logger.info("[OTA] 已上报 ota_ok等待心跳确认后删除 pending")
except Exception as e:
if logger:
logger.error(f"[OTA] ota_ok 上报失败: {e}")
self.logger.error(f"[OTA] ota_ok 上报失败: {e}")
else:
break
# 处理心跳 ACK
elif logged_in and msg_type == 4:
last_heartbeat_ack_time = time.ticks_ms()
if logger:
logger.debug("✅ 收到心跳确认")
self.logger.debug("✅ 收到心跳确认")
# 处理命令40分片下载
elif logged_in and msg_type == 40:
@@ -806,8 +822,7 @@ class NetworkManager:
self._raw_line_data.clear()
self._raw_line_data.append(body)
if len(self._raw_line_data) >= int(t):
if logger:
logger.info(f"下载完成")
self.logger.info(f"下载完成")
from ota_manager import ota_manager
stock_array = list(map(lambda x: x.get('d'), self._raw_line_data))
local_filename = config.LOCAL_FILENAME
@@ -816,8 +831,7 @@ class NetworkManager:
ota_manager.apply_ota_and_reboot(None, local_filename)
else:
self.safe_enqueue({'data':{'l': len(self._raw_line_data), 'v': v}, 'cmd': 41})
if logger:
logger.info(f"已下载{len(self._raw_line_data)} 全部:{t} 版本:{v}")
self.logger.info(f"已下载{len(self._raw_line_data)} 全部:{t} 版本:{v}")
# 处理业务指令
elif logged_in and isinstance(body, dict):
@@ -843,8 +857,7 @@ class NetworkManager:
battery_percent = voltage_to_percent(voltage)
battery_data = {"battery": battery_percent, "voltage": round(voltage, 3)}
self.safe_enqueue(battery_data, 2)
if logger:
logger.info(f"电量上报: {battery_percent}%")
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")
@@ -853,8 +866,7 @@ class NetworkManager:
mode = (inner_data.get("mode") or "").strip().lower()
if not ota_url:
if logger:
logger.error("ota missing_url")
self.logger.error("ota missing_url")
self.safe_enqueue({"result": "missing_url"}, 2)
continue
@@ -865,17 +877,14 @@ class NetworkManager:
# 自动判断模式如果没有明确指定根据WiFi连接状态和凭证决定
if mode not in ("4g", "wifi"):
if logger:
logger.info("ota missing mode, auto-detecting...")
self.logger.info("ota missing mode, auto-detecting...")
# 只有同时满足WiFi已连接 且 提供了WiFi凭证才使用WiFi
if self.is_wifi_connected() and ssid and password:
mode = "wifi"
if logger:
logger.info("ota auto-selected: wifi (WiFi connected and credentials provided)")
self.logger.info("ota auto-selected: wifi (WiFi connected and credentials provided)")
else:
mode = "4g"
if logger:
logger.info("ota auto-selected: 4g (WiFi not available or no credentials)")
self.logger.info("ota auto-selected: 4g (WiFi not available or no credentials)")
if mode == "4g":
ota_manager._set_ota_url(ota_url) # 记录 OTA URL供命令7使用
@@ -883,8 +892,7 @@ class NetworkManager:
_thread.start_new_thread(ota_manager.direct_ota_download_via_4g, (ota_url,))
else: # mode == "wifi"
if not ssid or not password:
if logger:
logger.error("ota wifi mode requires ssid and 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()
@@ -914,20 +922,17 @@ class NetworkManager:
# 如果 ota_manager.ota_url 为 None需要从其他地方获取
ota_url_to_use = ota_manager.ota_url
if not ota_url_to_use:
if logger:
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)
else:
ota_manager._start_update_thread()
_thread.start_new_thread(ota_manager.direct_ota_download, (ota_url_to_use,))
elif inner_cmd == 41:
if logger:
logger.info("[TEST] 收到TCP射箭触发命令")
self.logger.info("[TEST] 收到TCP射箭触发命令")
self._manual_trigger_flag = True
self.safe_enqueue({"result": "trigger_ack"}, 2)
elif inner_cmd == 42: # 关机命令
if logger:
logger.info("[SHUTDOWN] 收到TCP关机命令准备关机...")
self.logger.info("[SHUTDOWN] 收到TCP关机命令准备关机...")
self.safe_enqueue({"result": "shutdown_ack"}, 2)
time.sleep_ms(1000)
self.disconnect_server()
@@ -975,22 +980,19 @@ class NetworkManager:
if logged_in and current_time - last_heartbeat_send_time > config.HEARTBEAT_INTERVAL * 1000:
vol_val = get_bus_voltage()
if not self.tcp_send_raw(self.make_packet(4, {"vol": vol_val, "vol_per": voltage_to_percent(vol_val)})):
if logger:
logger.error("心跳发送失败")
self.logger.error("心跳发送失败")
time.sleep_ms(3000)
send_hartbeat_fail_count += 1
if send_hartbeat_fail_count >= 3:
send_hartbeat_fail_count = 0
if logger:
logger.error("连续3次发送心跳失败重连")
self.logger.error("连续3次发送心跳失败重连")
break
else:
continue
else:
send_hartbeat_fail_count = 0
last_heartbeat_send_time = current_time
if logger:
logger.debug("心跳已发送")
self.logger.debug("心跳已发送")
# 删除 pending 文件(心跳发送成功后)
if not pending_cleared:
@@ -1000,39 +1002,28 @@ class NetworkManager:
try:
os.remove(pending_path)
pending_cleared = True
if logger:
logger.info("[OTA] 心跳发送成功,已删除 ota_pending.json")
self.logger.info("[OTA] 心跳发送成功,已删除 ota_pending.json")
except Exception as e:
if logger:
logger.error(f"[OTA] 删除 pending 文件失败: {e}")
self.logger.error(f"[OTA] 删除 pending 文件失败: {e}")
except Exception as e:
if logger:
logger.error(f"[OTA] 检查 pending 文件时出错: {e}")
self.logger.error(f"[OTA] 检查 pending 文件时出错: {e}")
# 心跳超时重连
if logged_in and current_time - last_heartbeat_ack_time > 1000*60*10:
if logger:
logger.error("十分钟无心跳ACK重连")
self.logger.error("十分钟无心跳ACK重连")
break
time.sleep_ms(50)
self._tcp_connected = False
if logger:
logger.error("连接异常2秒后重连...")
self.logger.error("连接异常2秒后重连...")
time.sleep_ms(2000)
except Exception as e:
# TCP主循环的顶层异常捕获防止线程静默退出
logger = logger_manager.logger
if logger:
logger.error(f"[NET] TCP主循环异常: {e}")
import traceback
logger.error(traceback.format_exc())
else:
print(f"[NET] TCP主循环异常: {e}")
import traceback
traceback.print_exc()
self.logger.error(f"[NET] TCP主循环异常: {e}")
import traceback
self.logger.error(traceback.format_exc())
self._tcp_connected = False
time.sleep_ms(5000) # 等待5秒后重试连接