diff --git a/network.py b/network.py index d30dc94..0569212 100644 --- a/network.py +++ b/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"