From 35503cad6214ca569268bfddf12b0b98b554dfcc Mon Sep 17 00:00:00 2001 From: julecn Date: Fri, 25 Apr 2025 16:33:08 +0000 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20boot.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 初始化boot文件。 Signed-off-by: julecn --- boot.py | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 boot.py diff --git a/boot.py b/boot.py new file mode 100644 index 0000000..92bcf68 --- /dev/null +++ b/boot.py @@ -0,0 +1,290 @@ +import network +import socket +import time +from machine import reset, Pin +import ubinascii +import urandom +import hashlib +import _thread +import ujson + + +from InterphoneHandler import InterphoneHandler + +# Wi-Fi配置 +WIFI_SSID = "JULM" +WIFI_PASSWORD = "11223344" + +# WebSocket服务器配置 +WS_SERVER = "wss://websocket.julecn.com:80" +HOST, PORT = WS_SERVER.replace("wss://", "").split(":") +PORT = int(PORT) + +action_handlers = { + 'interphone': InterphoneHandler() +} + + +def connect_wifi(): + sta_if = network.WLAN(network.STA_IF) + if not sta_if.isconnected(): + print("正在连接Wi-Fi...") + sta_if.active(True) + sta_if.connect(WIFI_SSID, WIFI_PASSWORD) + while not sta_if.isconnected(): + time.sleep(1) + print("Wi-Fi连接成功", sta_if.ifconfig()) + return sta_if + + +def websocket_handshake(sock): + # 初始化一个空的字节数组,用于存储随机字节 + random_bytes = bytearray() + + # 循环4次,每次生成32位(4字节)的随机数 + for _ in range(4): + # 生成32位随机数 + rand_32_bits = urandom.getrandbits(32) + # 将32位随机数转换为4字节,并添加到字节数组中 + random_bytes.extend(rand_32_bits.to_bytes(4, 'big')) + + # 将随机字节转换为十六进制字符串 + key = ubinascii.hexlify(random_bytes).decode() + + magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" # 修正固定GUID + combined = (key + magic).encode() # 拼接密钥和固定GUID + + # 使用hashlib计算SHA1哈希并进行Base64编码 + sha1_hash = hashlib.sha1(combined).digest() # 获取原始SHA1哈希字节数据 + accept_key = ubinascii.b2a_base64(sha1_hash).decode().strip() # 转换为Base64字符串并去除换行符 + + print(f"key: {key}") + + handshake = f"GET / HTTP/1.1\r\n" \ + f"Host: {HOST}\r\n" \ + f"Upgrade: websocket\r\n" \ + f"Connection: Upgrade\r\n" \ + f"Sec-WebSocket-Key: {key}\r\n" \ + f"Sec-WebSocket-Version: 13\r\n\r\n" + sock.send(handshake.encode()) + + response = b"" + while b"\r\n\r\n" not in response: + response += sock.recv(1024) + headers, _ = response.split(b"\r\n\r\n", 1) + print(headers, response) + + if b"101 Switching Protocols" not in headers: + raise Exception("握手失败") + + # 提取响应头中的Sec-WebSocket-Accept字段的值 + accept_header = None + for line in headers.split(b'\r\n'): + if line.startswith(b'Sec-WebSocket-Accept: '): + accept_header = line.split(b': ')[1].decode().strip() + break + + if not accept_header or accept_header != accept_key: + raise Exception("握手验证失败") + + print("WebSocket握手成功") + + +def websocket_receive_thread(sock): + while True: + try: + msg = receive_message(sock) + if msg: + # 有新数据时调用处理方法 + handle_new_data(msg) + except Exception as e: + print(f"接收数据出错: {e}") + + +def handle_action(action, data): + """ + 根据action调用对应处理器的方法 + :param action: 格式为"类名.方法名"的字符串 + :param data: 需要传递的参数 + """ + try: + # 分割action为类名和方法名 + if '.' not in action: + return + class_part, method_part = action.split('.', 1) + + # 获取对应的处理器实例 + handler = action_handlers.get(class_part.lower()) + if not handler: + print(f"未注册的处理器类型: {class_part}") + return + + # 通过反射获取方法 + method = getattr(handler, method_part, None) + if method and callable(method): + print(f"执行 {action} 方法") + method(data) + else: + print(f"处理器 {class_part} 没有方法: {method_part}") + except Exception as e: + print(f"执行 {action} 失败: {str(e)}") + + +def handle_new_data(data): + print(f"接收到新数据: {data}") + # 修改后的处理逻辑 + try: + # 解析JSON数据 + message = ujson.loads(data) + action = message.get('action') + params = message.get('data') + + if action: + print(f"解析到动作指令: {action}") + handle_action(action, params) + except ValueError: + print("无效的JSON格式") + except Exception as e: + print(f"数据处理异常: {str(e)}") + + +def receive_message(sock): + header = b"" + while len(header) < 2: # 确保接收到至少2字节头部 + header += sock.recv(2 - len(header)) + + opcode = header[0] & 0x0F + mask = header[1] & 0x80 + payload_len = header[1] & 0x7F + + # 处理扩展载荷长度 + if payload_len == 126: + header += sock.recv(2) # 接收额外2字节长度 + payload_len = int.from_bytes(header[2:4], "big") + elif payload_len == 127: + header += sock.recv(8) # 接收额外8字节长度 + payload_len = int.from_bytes(header[2:10], "big") + + # 提取掩码密钥(如果有) + mask_key = b"" + if mask: + mask_key = sock.recv(4) + + # 接收有效载荷并去除头部影响 + payload = b"" + while len(payload) < payload_len: + payload += sock.recv(payload_len - len(payload)) + + # 应用掩码(如果需要) + if mask: + payload = bytearray(payload) + for i in range(len(payload)): + payload[i] ^= mask_key[i % 4] + payload = bytes(payload) + + return payload.decode() if opcode == 1 else None # 仅处理文本帧 + + +def send_text(sock, message): + """ + 发送WebSocket文本帧的通用方法 + :param sock: 已连接的socket对象 + :param message: 要发送的文本内容(字符串) + """ + try: + # 生成4字节随机掩码密钥(RFC6455要求) + mask_key = bytearray(4) + + # 将消息编码为UTF-8字节流 + payload = message.encode('utf-8') + payload_len = len(payload) + + # 构建基础帧头 + fin_rsv_opcode = 0x81 # FIN=1, Opcode=0x01(文本帧) + mask_bit = 0x80 # 掩码位必须为1(客户端发送) + + # 处理不同长度的payload(参考RFC6455分帧规则) + if payload_len <= 125: + frame_header = bytearray([fin_rsv_opcode, mask_bit | payload_len]) + elif payload_len <= 65535: + frame_header = bytearray([fin_rsv_opcode, mask_bit | 126]) + frame_header += payload_len.to_bytes(2, 'big') + else: + frame_header = bytearray([fin_rsv_opcode, mask_bit | 127]) + frame_header += payload_len.to_bytes(8, 'big') + + # 添加掩码密钥到帧头 + frame_header += mask_key + + # 应用掩码到payload(必须步骤) + masked_payload = bytearray(payload) + for i in range(len(masked_payload)): + masked_payload[i] ^= mask_key[i % 4] + + # 发送完整帧 + sock.send(frame_header + masked_payload) + print(f"已发送文本:{message}") + + except Exception as e: + print(f"发送失败:{str(e)}") + raise # 抛出异常供上层处理 + + +def ws_client(): + while True: + try: + sta_if = connect_wifi() + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((HOST, PORT)) + + websocket_handshake(sock) + + print("开始接收消息...") + # 启动接收数据的线程 + _thread.start_new_thread(websocket_receive_thread, (sock,)) + + while True: + time.sleep(30) + send_text(sock, '{"action":"sys.ping"}') + + except OSError as e: + print(f"连接异常: {str(e)}") + if 'sock' in locals(): + sock.close() + if 'sta_if' in locals(): + sta_if.active(False) + time.sleep(5) + reset() + except Exception as e: + print(f"发生错误: {str(e)}") + if 'sock' in locals(): + sock.close() + time.sleep(5) + + +def force_cleanup(sock): + """强制清理残留资源""" + try: + sock.shutdown(socket.SHUT_RDWR) # 完全关闭套接字 + except: + pass + finally: + sock.close() + + # 终止相关线程(需配合线程管理) + _thread.exit() # MicroPython的线程终止方式 + + +def check_connection_alive(sock): + """连接活性检测(参考TCP状态检测)""" + try: + # 发送空数据检测写缓冲区 + sock.send(b'\x00') + return True + except OSError as e: + if e.args[0] == 9: # EBADF: 套接字已关闭 + return False + raise + + +ws_client()