杰理 AC792N WebSocket 客户端例程使用测试教程
本教程介绍如何使用杰理 AC792N 的 WebSocket 客户端例程,并配合 PC 端 Python 服务器进行测试。
一、客户端配置(AC792N 端)
1.1 例程代码位置
apps\common\example\network_protocols\websocket\main.c
1.2 编译配置
-
添加到 Makefile
- 将
apps\common\example\network_protocols\websocket\main.c添加到 Makefile 中进行编译
- 将
-
启用测试例程
- 在
main.c文件开头定义宏来打开测试例程:
c#define USE_WEBSOCKET_TEST - 在
1.3 配置服务器地址
-
查看 PC 的 IP 地址
- 在 PC 端的 CMD/PowerShell 命令行运行:
bashipconfig-
找到类似以下信息:
无线局域网适配器 WLAN:
IPv4 地址 . . . . . . . . . . . . : 192.168.1.100
-
修改客户端代码
- 在
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 启动服务器
-
在 PC 端运行:
bashpython websocket_server_gui.py -
点击"启动服务器"按钮
3.2 配置设备联网
- 下载编译好的 AC792N WebSocket 客户端固件
- 通过杰理智能机器人小程序进行配网
- 确保设备连接到与 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)
可能原因:
- PC 和设备不在同一网络
- PC 的 IP 地址配置错误
- 服务器未启动或端口被占用
- 防火墙阻止连接
- WiFi 启用了 AP 隔离
解决方法:
- 确认 PC 和设备连接到同一 WiFi
- 重新运行
ipconfig确认 PC 的 IP 地址 - 确保服务器已启动并监听正确端口
- 临时关闭 Windows 防火墙测试
- 使用手机热点代替公司 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 版本支持:
- 查看所有已连接客户端
- 选择性发送消息到指定客户端
- 全选/全不选功能
六、技术支持
如有问题,请检查:
- 网络连接状态
- IP 地址配置
- 防火墙设置
- 串口日志输出
- 服务器日志输出