wifi support tsl
This commit is contained in:
171
network.py
171
network.py
@@ -25,6 +25,39 @@ from logger_manager import logger_manager
|
||||
from wifi import wifi_manager
|
||||
|
||||
|
||||
def _calculate_tcp_ssl_password(device_id, iccid):
|
||||
"""
|
||||
与服务器 calculatePassword(deviceId, iccid) 一致:
|
||||
hex(md5(hex(md5(deviceId)) + iccid)),iccid 为空则不拼接。
|
||||
"""
|
||||
md5_device_hex = hashlib.md5(device_id.encode("utf-8")).hexdigest()
|
||||
if iccid:
|
||||
md5_device_hex = md5_device_hex + iccid
|
||||
return hashlib.md5(md5_device_hex.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _wifi_tls_would_block(exc):
|
||||
"""
|
||||
非阻塞 TLS 下 recv/send 常抛出 WANT_READ / WANT_WRITE(或等价文案),
|
||||
表示需等待对端/内核缓冲区,不是断线。
|
||||
"""
|
||||
try:
|
||||
import ssl as _ssl
|
||||
except ImportError:
|
||||
_ssl = None
|
||||
if _ssl is not None and isinstance(exc, _ssl.SSLError):
|
||||
err = getattr(exc, "errno", None)
|
||||
if err in (
|
||||
getattr(_ssl, "SSL_ERROR_WANT_READ", 2),
|
||||
getattr(_ssl, "SSL_ERROR_WANT_WRITE", 3),
|
||||
):
|
||||
return True
|
||||
msg = str(exc).lower()
|
||||
if "did not complete" in msg and ("read" in msg or "write" in msg):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class NetworkManager:
|
||||
"""网络通信管理器(单例)"""
|
||||
_instance = None
|
||||
@@ -181,6 +214,16 @@ class NetworkManager:
|
||||
|
||||
def read_device_id(self):
|
||||
"""从 /device_key 文件读取设备唯一 ID,失败则使用默认值"""
|
||||
def _set_password_for_device_id(device_id):
|
||||
if getattr(config, "USE_TCP_SSL", False):
|
||||
iccid = self.get_4g_mccid()
|
||||
iccid = iccid if iccid else ""
|
||||
print(f"iccid: {iccid}")
|
||||
self._password = _calculate_tcp_ssl_password(device_id, iccid)
|
||||
else:
|
||||
self._password = device_id + "."
|
||||
print(f"self._password: {self._password}")
|
||||
|
||||
try:
|
||||
with open("/device_key", "r") as f:
|
||||
device_id = f.read().strip()
|
||||
@@ -188,7 +231,7 @@ class NetworkManager:
|
||||
self.logger.debug(f"[INFO] 从 /device_key 读取到 DEVICE_ID: {device_id}")
|
||||
# 设置内部状态
|
||||
self._device_id = device_id
|
||||
self._password = device_id + "."
|
||||
_set_password_for_device_id(device_id)
|
||||
return device_id
|
||||
except Exception as e:
|
||||
self.logger.error(f"[ERROR] 无法读取 /device_key: {e}")
|
||||
@@ -196,7 +239,7 @@ class NetworkManager:
|
||||
# 使用默认值
|
||||
default_id = "DEFAULT_DEVICE_ID"
|
||||
self._device_id = default_id
|
||||
self._password = default_id + "."
|
||||
_set_password_for_device_id(default_id)
|
||||
return default_id
|
||||
|
||||
# ==================== WiFi 管理方法(委托给 wifi_manager)====================
|
||||
@@ -461,6 +504,31 @@ class NetworkManager:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _maybe_add_iccid_to_login(self, login_data):
|
||||
"""
|
||||
若应用目录下尚无 iccid 标记文件,则在登录包中增加 iccid 字段(值为当前卡号)。
|
||||
标记文件仅在「本次登录携带了 iccid 且服务器返回登录成功」后创建,见 _create_iccid_marker_file。
|
||||
Returns:
|
||||
bool: 本次登录是否携带了 iccid(即成功后需要创建标记文件)
|
||||
"""
|
||||
marker_path = os.path.join(config.APP_DIR, "iccid")
|
||||
if os.path.exists(marker_path):
|
||||
return False
|
||||
iccid_val = self.get_4g_mccid()
|
||||
login_data["iccid"] = iccid_val if iccid_val is not None else ""
|
||||
return True
|
||||
|
||||
def _create_iccid_marker_file(self):
|
||||
"""登录成功且曾携带 iccid 后创建空标记文件,后续登录不再带 iccid。"""
|
||||
marker_path = os.path.join(config.APP_DIR, "iccid")
|
||||
if os.path.exists(marker_path):
|
||||
return
|
||||
try:
|
||||
with open(marker_path, "w"):
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning(f"[NET] 创建 iccid 标记文件失败: {e}")
|
||||
|
||||
def _apply_session_force_4g(self):
|
||||
"""锁定本次会话为 4G(直到关机,期间不再回切 WiFi)"""
|
||||
self._session_force_4g = True
|
||||
@@ -653,17 +721,67 @@ class NetworkManager:
|
||||
return self._connect_tcp_via_4g()
|
||||
return False
|
||||
|
||||
def _wrap_wifi_tls(self, plain_sock, hostname):
|
||||
"""
|
||||
在已建立的 TCP socket 上做 TLS(WiFi 走主机 ssl 库;4G 仍用模组 AT+SSL)。
|
||||
校验与 config.SSL_VERIFY_MODE、SSL_CERT_PATH 一致。
|
||||
"""
|
||||
import ssl
|
||||
|
||||
verify_mode = getattr(config, "SSL_VERIFY_MODE", 0)
|
||||
cert_path = getattr(config, "SSL_CERT_PATH", None) or ""
|
||||
ca_ok = verify_mode == 1 and cert_path and os.path.exists(cert_path)
|
||||
cert_none = getattr(ssl, "CERT_NONE", 0)
|
||||
cert_required = getattr(ssl, "CERT_REQUIRED", 2)
|
||||
|
||||
if hasattr(ssl, "SSLContext"):
|
||||
try:
|
||||
proto = getattr(ssl, "PROTOCOL_TLS_CLIENT", None)
|
||||
if proto is None:
|
||||
proto = getattr(ssl, "PROTOCOL_TLS", 0)
|
||||
ctx = ssl.SSLContext(proto)
|
||||
if ca_ok:
|
||||
ctx.load_verify_locations(cafile=cert_path)
|
||||
ctx.verify_mode = cert_required
|
||||
ctx.check_hostname = True
|
||||
else:
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = cert_none
|
||||
return ctx.wrap_socket(plain_sock, server_hostname=hostname)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"[WIFI-TCP] SSLContext.wrap_socket 失败,改用 wrap_socket: {e}")
|
||||
|
||||
if ca_ok:
|
||||
try:
|
||||
return ssl.wrap_socket(
|
||||
plain_sock,
|
||||
server_hostname=hostname,
|
||||
cert_reqs=cert_required,
|
||||
ca_certs=cert_path,
|
||||
)
|
||||
except TypeError:
|
||||
return ssl.wrap_socket(plain_sock, cert_reqs=cert_required, ca_certs=cert_path)
|
||||
try:
|
||||
return ssl.wrap_socket(plain_sock, server_hostname=hostname, cert_reqs=cert_none)
|
||||
except TypeError:
|
||||
return ssl.wrap_socket(plain_sock, cert_reqs=cert_none)
|
||||
|
||||
def _connect_tcp_via_wifi(self):
|
||||
"""通过WiFi建立TCP连接"""
|
||||
"""通过WiFi建立TCP连接(USE_TCP_SSL 时在 TCP 之上走 tls)"""
|
||||
try:
|
||||
# 创建TCP socket
|
||||
wifi_manager.wifi_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
wifi_manager.wifi_socket.settimeout(5.0) # 5秒超时
|
||||
|
||||
# 连接到服务器
|
||||
addr_info = socket.getaddrinfo(config.SERVER_IP, config.SERVER_PORT,
|
||||
socket.AF_INET, socket.SOCK_STREAM)[0]
|
||||
use_ssl = getattr(config, "USE_TCP_SSL", False)
|
||||
host = self._server_ip
|
||||
port = getattr(config, "TCP_SSL_PORT", 443) if use_ssl else config.SERVER_PORT
|
||||
addr_info = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)[0]
|
||||
wifi_manager.wifi_socket.connect(addr_info[-1])
|
||||
|
||||
if use_ssl:
|
||||
wifi_manager.wifi_socket = self._wrap_wifi_tls(wifi_manager.wifi_socket, host)
|
||||
|
||||
# 设置非阻塞模式(用于接收数据)
|
||||
wifi_manager.wifi_socket.setblocking(False)
|
||||
@@ -671,7 +789,10 @@ class NetworkManager:
|
||||
wifi_manager.wifi_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
|
||||
self._tcp_connected = True
|
||||
self.logger.info("[WIFI-TCP] TCP 连接已建立")
|
||||
if use_ssl:
|
||||
self.logger.info("[WIFI-TCP] TLS 连接已建立")
|
||||
else:
|
||||
self.logger.info("[WIFI-TCP] TCP 连接已建立")
|
||||
|
||||
# 启动 WiFi 质量后台检测
|
||||
self._start_wifi_quality_monitor()
|
||||
@@ -721,6 +842,14 @@ class NetworkManager:
|
||||
"""检查WiFi TCP连接是否仍然有效"""
|
||||
if not wifi_manager.wifi_socket:
|
||||
return False
|
||||
# TLS(ssl.wrap_socket/SSLContext.wrap_socket) 后的 socket 往往不支持 MSG_PEEK/MSG_DONTWAIT。
|
||||
# 这种情况下“主动探测”反而容易误报断线;让真正的 send/recv 去判定更稳。
|
||||
try:
|
||||
if getattr(config, "USE_TCP_SSL", False) or hasattr(wifi_manager.wifi_socket, "cipher"):
|
||||
return True
|
||||
except Exception:
|
||||
# 任何探测异常都不应导致断线清理
|
||||
return True
|
||||
try:
|
||||
# send(b"") 在很多实现里是 no-op,无法可靠探测断线。
|
||||
# 用非阻塞 peek 来判断:若对端已关闭,recv 会返回 b""。
|
||||
@@ -728,6 +857,13 @@ class NetworkManager:
|
||||
if data == b"":
|
||||
raise OSError("wifi socket closed")
|
||||
return True
|
||||
except TypeError as e:
|
||||
# 某些实现(尤其是 TLS socket)不支持 flags 参数;不要误判断线
|
||||
try:
|
||||
self.logger.warning(f"[WIFI-TCP] conncheck flags unsupported (TypeError): {e}")
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
except BlockingIOError:
|
||||
# 无数据可读但连接仍在(EAGAIN)
|
||||
return True
|
||||
@@ -861,7 +997,13 @@ class NetworkManager:
|
||||
# 标准 socket 发送
|
||||
total_sent = 0
|
||||
while total_sent < len(data):
|
||||
sent = wifi_manager.wifi_socket.send(data[total_sent:])
|
||||
try:
|
||||
sent = wifi_manager.wifi_socket.send(data[total_sent:])
|
||||
except (BlockingIOError, OSError) as se:
|
||||
if _wifi_tls_would_block(se):
|
||||
time.sleep_ms(2)
|
||||
continue
|
||||
raise
|
||||
if sent == 0:
|
||||
# socket连接已断开
|
||||
self.logger.warning(f"[WIFI-TCP] 发送失败,socket已断开(尝试 {attempt+1}/{max_retries})")
|
||||
@@ -1011,9 +1153,11 @@ class NetworkManager:
|
||||
# 无数据可读是正常的
|
||||
return b""
|
||||
except OSError as e:
|
||||
if _wifi_tls_would_block(e):
|
||||
return b""
|
||||
# socket错误(连接断开等)
|
||||
self.logger.warning(f"[WIFI-TCP] 接收数据失败: {e}")
|
||||
|
||||
|
||||
# 关闭socket
|
||||
try:
|
||||
wifi_manager.wifi_socket.close()
|
||||
@@ -1021,7 +1165,7 @@ class NetworkManager:
|
||||
pass
|
||||
wifi_manager.wifi_socket = None
|
||||
self._tcp_connected = False
|
||||
|
||||
|
||||
return b""
|
||||
except Exception as e:
|
||||
self.logger.error(f"[WIFI-TCP] 接收数据异常: {e}")
|
||||
@@ -1309,6 +1453,8 @@ class NetworkManager:
|
||||
"vol": vol_val,
|
||||
"vol_per": voltage_to_percent(vol_val)
|
||||
}
|
||||
iccid_pending_marker = self._maybe_add_iccid_to_login(login_data)
|
||||
print(f"login_data: {login_data}")
|
||||
# if not self.tcp_send_raw(self.make_packet(1, login_data)):
|
||||
if not self.tcp_send_raw(self._netcore.make_packet(1, login_data)):
|
||||
self._tcp_connected = False
|
||||
@@ -1347,7 +1493,7 @@ class NetworkManager:
|
||||
if self._network_type == "wifi":
|
||||
data = self.receive_tcp_data_via_wifi(timeout_ms=5)
|
||||
if data:
|
||||
self.logger.info(f"[NET] 接收WiFi数据, {time.time()}")
|
||||
# self.logger.info(f"[NET] 接收WiFi数据, {time.time()}")
|
||||
wifi_manager.recv_buffer += data
|
||||
while len(wifi_manager.recv_buffer) >= 12:
|
||||
try:
|
||||
@@ -1394,7 +1540,10 @@ class NetworkManager:
|
||||
logged_in = True
|
||||
last_heartbeat_ack_time = time.ticks_ms()
|
||||
self.logger.info("登录成功")
|
||||
|
||||
if iccid_pending_marker:
|
||||
self._create_iccid_marker_file()
|
||||
iccid_pending_marker = False
|
||||
|
||||
# 检查 ota_pending.json
|
||||
try:
|
||||
pending_path = f"{config.APP_DIR}/ota_pending.json"
|
||||
|
||||
Reference in New Issue
Block a user