杰理 AC792N WebSocket 客户端例程使用测试教程

杰理 AC792N WebSocket 客户端例程使用测试教程

本教程介绍如何使用杰理 AC792N 的 WebSocket 客户端例程,并配合 PC 端 Python 服务器进行测试。


一、客户端配置(AC792N 端)

1.1 例程代码位置

复制代码
apps\common\example\network_protocols\websocket\main.c

1.2 编译配置

  1. 添加到 Makefile

    • apps\common\example\network_protocols\websocket\main.c 添加到 Makefile 中进行编译
  2. 启用测试例程

    • main.c 文件开头定义宏来打开测试例程:
    c 复制代码
    #define USE_WEBSOCKET_TEST

1.3 配置服务器地址

  1. 查看 PC 的 IP 地址

    • 在 PC 端的 CMD/PowerShell 命令行运行:
    bash 复制代码
    ipconfig
    • 找到类似以下信息:

      无线局域网适配器 WLAN:
      IPv4 地址 . . . . . . . . . . . . : 192.168.1.100

  2. 修改客户端代码

    • main.c 中找到 OBJ_URL 定义
    • 将 IP 地址修改为你的 PC IP:
    c 复制代码
    #define OBJ_URL "ws://192.168.1.100:9010"

1.4 运行说明

  • 客户端代码检测到连接上网络后会自动运行
  • 直接编译下载即可,无需手动启动websocket客户端

二、服务器配置(PC 端)

2.1 客户端代码

python 复制代码
#!/usr/bin/env python3
"""
WebSocket服务器 GUI版本
支持可视化配置和消息收发
"""

import asyncio
import websockets
import tkinter as tk
from tkinter import scrolledtext, ttk
from datetime import datetime
import threading
import queue


class WebSocketServerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("WebSocket 服务器")
        self.root.geometry("800x600")
        
        # 服务器状态
        self.server = None
        self.server_task = None
        self.is_running = False
        self.connected_clients = {}  # {websocket: {"addr": str, "var": BooleanVar, "checkbox": Checkbutton}}
        self.message_queue = queue.Queue()
        self.auto_echo = tk.BooleanVar(value=False)  # 自动回显开关,默认关闭
        
        # 创建界面
        self.create_widgets()
        
        # 启动消息处理
        self.process_messages()
    
    def create_widgets(self):
        """创建GUI组件"""
        # 配置区域
        config_frame = ttk.LabelFrame(self.root, text="服务器配置", padding=10)
        config_frame.pack(fill=tk.X, padx=10, pady=5)
        
        # IP地址
        ttk.Label(config_frame, text="监听地址:").grid(row=0, column=0, sticky=tk.W, padx=5)
        self.ip_entry = ttk.Entry(config_frame, width=20)
        self.ip_entry.insert(0, "0.0.0.0")
        self.ip_entry.grid(row=0, column=1, padx=5)
        
        # 端口
        ttk.Label(config_frame, text="端口:").grid(row=0, column=2, sticky=tk.W, padx=5)
        self.port_entry = ttk.Entry(config_frame, width=10)
        self.port_entry.insert(0, "9010")
        self.port_entry.grid(row=0, column=3, padx=5)
        
        # 启动/停止按钮
        self.start_button = ttk.Button(config_frame, text="启动服务器", command=self.start_server)
        self.start_button.grid(row=0, column=4, padx=10)
        
        # 状态标签
        self.status_label = ttk.Label(config_frame, text="状态: 未启动", foreground="red")
        self.status_label.grid(row=0, column=5, padx=5)
        
        # 自动回显选项
        self.echo_checkbox = ttk.Checkbutton(config_frame, text="自动回显", variable=self.auto_echo)
        self.echo_checkbox.grid(row=0, column=6, padx=5)
        
        # 主内容区域(左右分栏)
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        # 左侧:客户端列表
        client_frame = ttk.LabelFrame(main_frame, text="已连接客户端", padding=10)
        client_frame.pack(side=tk.LEFT, fill=tk.BOTH, padx=(0, 5))
        
        # 客户端列表容器(带滚动条)
        client_canvas_frame = ttk.Frame(client_frame)
        client_canvas_frame.pack(fill=tk.BOTH, expand=True)
        
        client_scroll = ttk.Scrollbar(client_canvas_frame)
        client_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        
        self.client_canvas = tk.Canvas(client_canvas_frame, yscrollcommand=client_scroll.set, width=250)
        self.client_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        client_scroll.config(command=self.client_canvas.yview)
        
        # 客户端列表内部容器
        self.client_list_frame = ttk.Frame(self.client_canvas)
        self.client_canvas_window = self.client_canvas.create_window((0, 0), window=self.client_list_frame, anchor=tk.NW)
        
        # 绑定滚动区域更新
        self.client_list_frame.bind("<Configure>", lambda e: self.client_canvas.configure(scrollregion=self.client_canvas.bbox("all")))
        
        # 客户端列表按钮
        client_btn_frame = ttk.Frame(client_frame)
        client_btn_frame.pack(fill=tk.X, pady=(5, 0))
        ttk.Button(client_btn_frame, text="全选", command=self.select_all_clients).pack(side=tk.LEFT, padx=2)
        ttk.Button(client_btn_frame, text="全不选", command=self.deselect_all_clients).pack(side=tk.LEFT, padx=2)
        
        # 右侧:日志显示
        log_frame = ttk.LabelFrame(main_frame, text="消息日志", padding=10)
        log_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        
        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD)
        self.log_text.pack(fill=tk.BOTH, expand=True)
        
        # 配置文本标签
        self.log_text.tag_config("info", foreground="blue")
        self.log_text.tag_config("success", foreground="green")
        self.log_text.tag_config("error", foreground="red")
        self.log_text.tag_config("recv", foreground="purple")
        
        # 消息发送区域
        send_frame = ttk.LabelFrame(self.root, text="发送消息", padding=10)
        send_frame.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(send_frame, text="消息内容:").pack(side=tk.LEFT, padx=5)
        self.send_entry = ttk.Entry(send_frame, width=50)
        self.send_entry.insert(0, "Hello from server!")
        self.send_entry.pack(side=tk.LEFT, padx=5)
        
        self.send_button = ttk.Button(send_frame, text="发送到选中客户端", command=self.send_message, state=tk.DISABLED)
        self.send_button.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(send_frame, text="清空日志", command=self.clear_log).pack(side=tk.LEFT, padx=5)
    
    def select_all_clients(self):
        """全选客户端"""
        for websocket, info in self.connected_clients.items():
            info["var"].set(True)
    
    def deselect_all_clients(self):
        """取消全选"""
        for websocket, info in self.connected_clients.items():
            info["var"].set(False)
    
    def update_client_list(self):
        """更新客户端列表显示"""
        # 清空现有列表
        for widget in self.client_list_frame.winfo_children():
            widget.destroy()
        
        # 重新创建客户端列表
        for idx, (websocket, info) in enumerate(self.connected_clients.items()):
            client_frame = ttk.Frame(self.client_list_frame)
            client_frame.pack(fill=tk.X, pady=2)
            
            # 复选框
            checkbox = ttk.Checkbutton(
                client_frame,
                text=info["addr"],
                variable=info["var"]
            )
            checkbox.pack(side=tk.LEFT, fill=tk.X, expand=True)
            info["checkbox"] = checkbox
    
    def log(self, message, tag="info"):
        """添加日志"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.message_queue.put((f"[{timestamp}] {message}\n", tag))
    
    def process_messages(self):
        """处理消息队列"""
        try:
            while True:
                message, tag = self.message_queue.get_nowait()
                self.log_text.insert(tk.END, message, tag)
                self.log_text.see(tk.END)
        except queue.Empty:
            pass
        self.root.after(100, self.process_messages)
    
    def clear_log(self):
        """清空日志"""
        self.log_text.delete(1.0, tk.END)
    
    def start_server(self):
        """启动服务器"""
        if not self.is_running:
            host = self.ip_entry.get()
            try:
                port = int(self.port_entry.get())
            except ValueError:
                self.log("错误: 端口必须是数字", "error")
                return
            
            # 禁用配置输入
            self.ip_entry.config(state=tk.DISABLED)
            self.port_entry.config(state=tk.DISABLED)
            self.start_button.config(text="停止服务器")
            self.send_button.config(state=tk.NORMAL)
            self.status_label.config(text="状态: 运行中", foreground="green")
            
            # 启动服务器线程
            self.is_running = True
            threading.Thread(target=self.run_server, args=(host, port), daemon=True).start()
            self.log(f"服务器启动: ws://{host}:{port}", "success")
        else:
            self.stop_server()
    
    def stop_server(self):
        """停止服务器"""
        self.is_running = False
        self.ip_entry.config(state=tk.NORMAL)
        self.port_entry.config(state=tk.NORMAL)
        self.start_button.config(text="启动服务器")
        self.send_button.config(state=tk.DISABLED)
        self.status_label.config(text="状态: 已停止", foreground="red")
        self.log("服务器已停止", "info")
    
    def run_server(self, host, port):
        """运行服务器(在新线程中)"""
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        
        try:
            loop.run_until_complete(self.start_websocket_server(host, port))
        except Exception as e:
            self.log(f"服务器错误: {e}", "error")
            self.is_running = False
    
    async def start_websocket_server(self, host, port):
        """启动WebSocket服务器"""
        async with websockets.serve(self.handle_client, host, port):
            while self.is_running:
                await asyncio.sleep(0.1)
    
    async def handle_client(self, websocket):
        """处理客户端连接"""
        client_addr = f"{websocket.remote_address[0]}:{websocket.remote_address[1]}"
        # 创建客户端信息,默认选中
        client_var = tk.BooleanVar(value=True)
        self.connected_clients[websocket] = {
            "addr": client_addr,
            "var": client_var,
            "checkbox": None
        }
        self.log(f"客户端连接: {client_addr}", "success")
        
        # 更新客户端列表
        self.root.after(0, self.update_client_list)
        
        try:
            async for message in websocket:
                self.log(f"收到 [{client_addr}]: {message}", "recv")
                
                # 自动回显(如果启用)
                if self.auto_echo.get():
                    response = f"服务器收到: {message}"
                    await websocket.send(response)
                    self.log(f"发送 [{client_addr}]: {response}", "info")
        
        except websockets.exceptions.ConnectionClosed:
            self.log(f"客户端断开: {client_addr}", "info")
        except Exception as e:
            self.log(f"错误 [{client_addr}]: {e}", "error")
        finally:
            if websocket in self.connected_clients:
                del self.connected_clients[websocket]
            self.log(f"当前连接数: {len(self.connected_clients)}", "info")
            # 更新客户端列表
            self.root.after(0, self.update_client_list)
    
    def send_message(self):
        """发送消息到选中的客户端"""
        message = self.send_entry.get()
        if not message:
            self.log("错误: 消息不能为空", "error")
            return
        
        if not self.connected_clients:
            self.log("错误: 没有连接的客户端", "error")
            return
        
        # 获取选中的客户端
        selected_clients = []
        for websocket, info in self.connected_clients.items():
            if info["var"].get():  # 检查复选框是否勾选
                selected_clients.append(websocket)
        
        if not selected_clients:
            self.log("错误: 请至少选择一个客户端", "error")
            return
        
        # 在新线程中发送
        threading.Thread(target=self.async_send_message, args=(message, selected_clients), daemon=True).start()
    
    def async_send_message(self, message, selected_clients):
        """异步发送消息到选中的客户端"""
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        
        async def send():
            tasks = []
            sent_count = 0
            for client in selected_clients:
                if client in self.connected_clients:
                    try:
                        tasks.append(client.send(message))
                        client_addr = self.connected_clients[client]["addr"]
                        self.log(f"发送到 [{client_addr}]: {message}", "info")
                        sent_count += 1
                    except Exception as e:
                        self.log(f"发送失败: {e}", "error")
            
            if tasks:
                await asyncio.gather(*tasks, return_exceptions=True)
                self.log(f"消息已发送到 {sent_count} 个客户端", "success")
        
        loop.run_until_complete(send())


def main():
    root = tk.Tk()
    app = WebSocketServerGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()

2.2 安装依赖

在 PC 端安装 Python WebSocket 库:

bash 复制代码
pip install websockets

2.3 运行服务器

GUI 版本
bash 复制代码
python websocket_server_gui.py

GUI 功能特性:

  • ✅ 可视化配置监听地址和端口
  • ✅ 一键启动/停止服务器
  • ✅ 实时显示连接状态
  • ✅ 查看收到的消息(带颜色区分)
  • ✅ 选择性发送消息到指定客户端
  • ✅ 自动回显功能(可选)
  • ✅ 清空日志功能

2.4 服务器配置说明

配置项 默认值 说明
监听地址 0.0.0.0 监听所有网络接口(推荐)
端口 9010 与客户端代码中的端口一致
协议 ws:// WebSocket 协议
Origin 验证 关闭 测试时可关闭,生产环境建议启用

监听地址说明:

  • 0.0.0.0:监听所有网络接口(推荐,IP 变化时仍可用)
  • 具体 IP(如 192.168.1.100):只监听指定接口(IP 变化后需重新配置)

2.5 启用 Origin 验证(可选)

如需启用 Origin 验证,在 websocket_server.py 中修改:

python 复制代码
ENABLE_ORIGIN_CHECK = True  # 启用 Origin 验证
ALLOWED_ORIGINS = [
    "http://coolaf.com",        # 客户端代码中的 origin
    "http://your-domain.com",   # 添加你允许的域名
]

三、测试步骤

3.1 启动服务器

  1. 在 PC 端运行:

    bash 复制代码
    python websocket_server_gui.py
  2. 点击"启动服务器"按钮

3.2 配置设备联网

  1. 下载编译好的 AC792N WebSocket 客户端固件
  2. 通过杰理智能机器人小程序进行配网
  3. 确保设备连接到与 PC 同一局域网的 WiFi

注意事项:

  • 如果使用公司或公共 WiFi,可能存在 AP 隔离,导致无法连接
  • 建议使用手机热点进行测试,PC 和设备都连接到手机热点

3.3 观察测试结果

服务器端(PC)

在 GUI 界面应该能看到:

  • 客户端连接信息(IP 和端口)
  • 收到的消息(字母表字符串)
  • 发送的回复消息
客户端端(AC792N)

通过串口打印观察:

连接成功的打印:

复制代码
[00:00:10.190]Connecting to the network...
[00:00:10.190]Network connection is successful!
[00:00:10.191][Info]: [OS]threat_create:websockets_client_main,pid:0x0
[00:00:10.192] . ----------------- Client Websocket ------------------
[00:00:10.193][Info]: [WEBSOCKET]---> sizeof struct websocket_struct = 3564
[00:00:10.194][Info]: [WEBSOCKET]  . url : ws://192.168.110.163:9010
[00:00:10.195][Info]: [WEBSOCKET]  . host addr : 192.168.110.163
[00:00:10.196][Info]: [WEBSOCKET]  . remoute ip : 192.168.110.163  port : 9010
[00:00:10.442][RECV SYN]
[00:00:10.450][Info]: [WEBSOCKET]key check : vzs840oysc8e++MVr+FhVakX0aY=
[00:00:10.450] . Handshake success 
[00:00:10.451][Info]: [OS]find websocket_client_heart task info...
[00:00:10.453][Info]: [OS]threat_create:websocket_client_heart,pid:0x180993e8
[00:00:10.453][Info]: [OS]find websocket_client_recv task info...
[00:00:10.455][Info]: [OS]threat_create:websocket_client_recv,pid:0x180993ec
[00:00:11.450]  . Send massage : ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789

连接失败的打印:

复制代码
[00:00:15.190]Connecting to the network...
[00:00:15.190]Network connection is successful!
[00:00:15.194][Info]: [WEBSOCKET]  . url : ws://10.177.16.51:9010
[00:00:15.195][Info]: [WEBSOCKET]  . host addr : 10.177.16.51
[00:00:15.196][Info]: [WEBSOCKET]  . remoute ip : 10.177.16.51  port : 9010
[00:00:30.200]  . ! Handshake error !!!
[00:00:30.200][Info]: [OS]kill for:websockets_client_main succ

四、常见问题

4.1 连接失败(Handshake error)

可能原因:

  1. PC 和设备不在同一网络
  2. PC 的 IP 地址配置错误
  3. 服务器未启动或端口被占用
  4. 防火墙阻止连接
  5. WiFi 启用了 AP 隔离

解决方法:

  1. 确认 PC 和设备连接到同一 WiFi
  2. 重新运行 ipconfig 确认 PC 的 IP 地址
  3. 确保服务器已启动并监听正确端口
  4. 临时关闭 Windows 防火墙测试
  5. 使用手机热点代替公司 WiFi

4.2 Ping 不通但能连接

这是正常现象!Windows 防火墙默认阻止 ICMP(ping)请求,但允许 TCP 连接。只要 WebSocket 能连接就没问题。

4.3 服务器绑定失败

如果使用具体 IP 地址(如 192.168.110.162)作为监听地址失败,改用 0.0.0.0 即可。


五、扩展功能

5.1 自定义消息处理

修改服务器代码中的 handle_client 函数,可以实现:

  • 自定义消息处理逻辑
  • 消息广播功能
  • 特定业务逻辑
  • 保存消息到文件或数据库

5.2 多客户端管理

GUI 版本支持:

  • 查看所有已连接客户端
  • 选择性发送消息到指定客户端
  • 全选/全不选功能

六、技术支持

如有问题,请检查:

  1. 网络连接状态
  2. IP 地址配置
  3. 防火墙设置
  4. 串口日志输出
  5. 服务器日志输出
相关推荐
刘某某.2 小时前
RPC分类
网络·网络协议·rpc
shy^-^cky2 小时前
Python程序设计完整复习要点(含实例)
python·期末复习
CQ_YM2 小时前
51单片机(2)
单片机·嵌入式硬件·51单片机
项目題供诗2 小时前
C语言基础(三)
c语言·c++
不脱发的程序猿2 小时前
CAN总线如何区分和识别帧类型
单片机·嵌入式硬件·嵌入式·can
码农水水2 小时前
中国电网Java面试被问:流批一体架构的实现和状态管理
java·c语言·开发语言·面试·职场和发展·架构·kafka
代码游侠2 小时前
应用——基于51单片机的按键控制蜂鸣器
stm32·单片机·嵌入式硬件
做萤石二次开发的哈哈2 小时前
萤石开放平台 萤石可编程设备 | 设备脚本自定义开发
开发语言·python·萤石云·萤石·萤石开放平台