wifi support tsl
This commit is contained in:
165
network.py
165
network.py
@@ -25,6 +25,39 @@ from logger_manager import logger_manager
|
|||||||
from wifi import wifi_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:
|
class NetworkManager:
|
||||||
"""网络通信管理器(单例)"""
|
"""网络通信管理器(单例)"""
|
||||||
_instance = None
|
_instance = None
|
||||||
@@ -181,6 +214,16 @@ class NetworkManager:
|
|||||||
|
|
||||||
def read_device_id(self):
|
def read_device_id(self):
|
||||||
"""从 /device_key 文件读取设备唯一 ID,失败则使用默认值"""
|
"""从 /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:
|
try:
|
||||||
with open("/device_key", "r") as f:
|
with open("/device_key", "r") as f:
|
||||||
device_id = f.read().strip()
|
device_id = f.read().strip()
|
||||||
@@ -188,7 +231,7 @@ class NetworkManager:
|
|||||||
self.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._device_id = device_id
|
||||||
self._password = device_id + "."
|
_set_password_for_device_id(device_id)
|
||||||
return device_id
|
return device_id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"[ERROR] 无法读取 /device_key: {e}")
|
self.logger.error(f"[ERROR] 无法读取 /device_key: {e}")
|
||||||
@@ -196,7 +239,7 @@ class NetworkManager:
|
|||||||
# 使用默认值
|
# 使用默认值
|
||||||
default_id = "DEFAULT_DEVICE_ID"
|
default_id = "DEFAULT_DEVICE_ID"
|
||||||
self._device_id = default_id
|
self._device_id = default_id
|
||||||
self._password = default_id + "."
|
_set_password_for_device_id(default_id)
|
||||||
return default_id
|
return default_id
|
||||||
|
|
||||||
# ==================== WiFi 管理方法(委托给 wifi_manager)====================
|
# ==================== WiFi 管理方法(委托给 wifi_manager)====================
|
||||||
@@ -461,6 +504,31 @@ class NetworkManager:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return None
|
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):
|
def _apply_session_force_4g(self):
|
||||||
"""锁定本次会话为 4G(直到关机,期间不再回切 WiFi)"""
|
"""锁定本次会话为 4G(直到关机,期间不再回切 WiFi)"""
|
||||||
self._session_force_4g = True
|
self._session_force_4g = True
|
||||||
@@ -653,25 +721,78 @@ class NetworkManager:
|
|||||||
return self._connect_tcp_via_4g()
|
return self._connect_tcp_via_4g()
|
||||||
return False
|
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):
|
def _connect_tcp_via_wifi(self):
|
||||||
"""通过WiFi建立TCP连接"""
|
"""通过WiFi建立TCP连接(USE_TCP_SSL 时在 TCP 之上走 tls)"""
|
||||||
try:
|
try:
|
||||||
# 创建TCP socket
|
# 创建TCP socket
|
||||||
wifi_manager.wifi_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
wifi_manager.wifi_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
wifi_manager.wifi_socket.settimeout(5.0) # 5秒超时
|
wifi_manager.wifi_socket.settimeout(5.0) # 5秒超时
|
||||||
|
|
||||||
# 连接到服务器
|
# 连接到服务器
|
||||||
addr_info = socket.getaddrinfo(config.SERVER_IP, config.SERVER_PORT,
|
use_ssl = getattr(config, "USE_TCP_SSL", False)
|
||||||
socket.AF_INET, socket.SOCK_STREAM)[0]
|
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])
|
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)
|
wifi_manager.wifi_socket.setblocking(False)
|
||||||
# 加快消息发送
|
# 加快消息发送
|
||||||
wifi_manager.wifi_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
wifi_manager.wifi_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
|
||||||
self._tcp_connected = True
|
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 质量后台检测
|
# 启动 WiFi 质量后台检测
|
||||||
self._start_wifi_quality_monitor()
|
self._start_wifi_quality_monitor()
|
||||||
@@ -721,6 +842,14 @@ class NetworkManager:
|
|||||||
"""检查WiFi TCP连接是否仍然有效"""
|
"""检查WiFi TCP连接是否仍然有效"""
|
||||||
if not wifi_manager.wifi_socket:
|
if not wifi_manager.wifi_socket:
|
||||||
return False
|
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:
|
try:
|
||||||
# send(b"") 在很多实现里是 no-op,无法可靠探测断线。
|
# send(b"") 在很多实现里是 no-op,无法可靠探测断线。
|
||||||
# 用非阻塞 peek 来判断:若对端已关闭,recv 会返回 b""。
|
# 用非阻塞 peek 来判断:若对端已关闭,recv 会返回 b""。
|
||||||
@@ -728,6 +857,13 @@ class NetworkManager:
|
|||||||
if data == b"":
|
if data == b"":
|
||||||
raise OSError("wifi socket closed")
|
raise OSError("wifi socket closed")
|
||||||
return True
|
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:
|
except BlockingIOError:
|
||||||
# 无数据可读但连接仍在(EAGAIN)
|
# 无数据可读但连接仍在(EAGAIN)
|
||||||
return True
|
return True
|
||||||
@@ -861,7 +997,13 @@ class NetworkManager:
|
|||||||
# 标准 socket 发送
|
# 标准 socket 发送
|
||||||
total_sent = 0
|
total_sent = 0
|
||||||
while total_sent < len(data):
|
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:
|
if sent == 0:
|
||||||
# socket连接已断开
|
# socket连接已断开
|
||||||
self.logger.warning(f"[WIFI-TCP] 发送失败,socket已断开(尝试 {attempt+1}/{max_retries})")
|
self.logger.warning(f"[WIFI-TCP] 发送失败,socket已断开(尝试 {attempt+1}/{max_retries})")
|
||||||
@@ -1011,6 +1153,8 @@ class NetworkManager:
|
|||||||
# 无数据可读是正常的
|
# 无数据可读是正常的
|
||||||
return b""
|
return b""
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
if _wifi_tls_would_block(e):
|
||||||
|
return b""
|
||||||
# socket错误(连接断开等)
|
# socket错误(连接断开等)
|
||||||
self.logger.warning(f"[WIFI-TCP] 接收数据失败: {e}")
|
self.logger.warning(f"[WIFI-TCP] 接收数据失败: {e}")
|
||||||
|
|
||||||
@@ -1309,6 +1453,8 @@ class NetworkManager:
|
|||||||
"vol": vol_val,
|
"vol": vol_val,
|
||||||
"vol_per": voltage_to_percent(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.make_packet(1, login_data)):
|
||||||
if not self.tcp_send_raw(self._netcore.make_packet(1, login_data)):
|
if not self.tcp_send_raw(self._netcore.make_packet(1, login_data)):
|
||||||
self._tcp_connected = False
|
self._tcp_connected = False
|
||||||
@@ -1347,7 +1493,7 @@ class NetworkManager:
|
|||||||
if self._network_type == "wifi":
|
if self._network_type == "wifi":
|
||||||
data = self.receive_tcp_data_via_wifi(timeout_ms=5)
|
data = self.receive_tcp_data_via_wifi(timeout_ms=5)
|
||||||
if data:
|
if data:
|
||||||
self.logger.info(f"[NET] 接收WiFi数据, {time.time()}")
|
# self.logger.info(f"[NET] 接收WiFi数据, {time.time()}")
|
||||||
wifi_manager.recv_buffer += data
|
wifi_manager.recv_buffer += data
|
||||||
while len(wifi_manager.recv_buffer) >= 12:
|
while len(wifi_manager.recv_buffer) >= 12:
|
||||||
try:
|
try:
|
||||||
@@ -1394,6 +1540,9 @@ class NetworkManager:
|
|||||||
logged_in = True
|
logged_in = True
|
||||||
last_heartbeat_ack_time = time.ticks_ms()
|
last_heartbeat_ack_time = time.ticks_ms()
|
||||||
self.logger.info("登录成功")
|
self.logger.info("登录成功")
|
||||||
|
if iccid_pending_marker:
|
||||||
|
self._create_iccid_marker_file()
|
||||||
|
iccid_pending_marker = False
|
||||||
|
|
||||||
# 检查 ota_pending.json
|
# 检查 ota_pending.json
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user