本方案已通过实机测试通过,可以跑通,如果想要在别的电脑上进行需要修改ip即可。
本文档详细介绍如何通过 Socket 通信实现 FlexSim 与 Python 之间的实时数据交互。采用 **Python 作为服务器(Server)**、**FlexSim 作为客户端(Client)** 的架构,可有效避免 FlexSim 作为服务器时可能出现的 `socketinit()` 初始化失败问题。
FlexSim 作为客户端时,使用 `clientcreate()` 创建 Socket,使用 `clientconnect()` 连接服务器。FlexSim 最多可同时创建 50 个客户端 Socket。
## 二、Python 服务器端设置
### 2.1 环境要求
- Python 3.6 或以上版本
- 无需安装额外依赖库(仅使用标准库 `socket`、`json`、`csv`、`datetime`)
### 2.2 创建 Python 服务器脚本
创建文件 `flexsim_server.py`,写入以下完整代码:
"""TCP Socket 服务器 - 接收 FlexSim 数据"""import socketimport jsonimport csvfrom datetime import datetime# ===== 配置 =====SERVER_IP = '0.0.0.0' # 监听所有网络接口SERVER_PORT = 9000CSV_FILENAME = "sim_data.csv"# ===== 状态映射表 =====STATE_MAP = { 0: "未定义", 1: "空闲", 2: "处理中", 3: "忙碌", 4: "堵塞", 5: "生成中", 6: "空", 7: "收集中", 8: "释放中", 9: "等待操作员", 10: "等待运输工具", 11: "故障/停机", 12: "计划停机", 13: "暂停中", 14: "装载中", 15: "卸载中", 16: "空载偏移行进", 17: "负载偏移行进",}def get_timestamp(): return datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]def parse_and_display(raw_line): """解析FlexSim发送的JSON数据并显示""" try: clean_str = raw_line.strip() if not clean_str: return None parsed = json.loads(clean_str) t = parsed.get('timestamp', 0) r1_code = parsed.get('robot1_state', -1) r2_code = parsed.get('robot2_state', -1) r1_desc = STATE_MAP.get(r1_code, f"未知({r1_code})") r2_desc = STATE_MAP.get(r2_code, f"未知({r2_code})") r1_task = parsed.get('robot1_task', '无任务') r2_task = parsed.get('robot2_task', '无任务') b1 = parsed.get('buff1_content', 0) agv1_load = parsed.get('agv1_load', 0) agv2_load = parsed.get('agv2_load', 0) agv1_x = parsed.get('agv1_x', 0) agv1_y = parsed.get('agv1_y', 0) agv1_z = parsed.get('agv1_z', 0) agv2_x = parsed.get('agv2_x', 0) agv2_y = parsed.get('agv2_y', 0) agv2_z = parsed.get('agv2_z', 0) print(f"[{t:.3f}]", flush=True) print(f" robot1: {r1_desc} | 任务: {r1_task}", flush=True) print(f" robot2: {r2_desc} | 任务: {r2_task}", flush=True) print(f" buff1: {b1}", flush=True) print(f" AGV1: 坐标({agv1_x:.2f}, {agv1_y:.2f}, {agv1_z:.2f}) | 载货: {agv1_load}", flush=True) print(f" AGV2: 坐标({agv2_x:.2f}, {agv2_y:.2f}, {agv2_z:.2f}) | 载货: {agv2_load}", flush=True) print("-" * 50, flush=True) return { 'timestamp': t, 'robot1_state': r1_desc, 'robot2_state': r2_desc, 'robot1_task': r1_task, 'robot2_task': r2_task, 'buff1': b1, 'agv1_load': agv1_load, 'agv2_load': agv2_load, 'agv1_x': agv1_x, 'agv1_y': agv1_y, 'agv1_z': agv1_z, 'agv2_x': agv2_x, 'agv2_y': agv2_y, 'agv2_z': agv2_z, } except json.JSONDecodeError: print(f"[{get_timestamp()}] JSON解析失败,原始数据: {raw_line[:100]}") except Exception as e: print(f"[{get_timestamp()}] 解析异常: {e}") return Nonedef save_to_csv(parsed): """将数据保存到CSV文件""" if parsed is None: return try: with open(CSV_FILENAME, 'a', newline='', encoding='utf-8') as f: fieldnames = [ "接收时间", "仿真时间", "robot1状态", "robot2状态", "robot1任务", "robot2任务", "buff1", "AGV1_载货", "AGV2_载货", "AGV1_X", "AGV1_Y", "AGV1_Z", "AGV2_X", "AGV2_Y", "AGV2_Z" ] writer = csv.DictWriter(f, fieldnames=fieldnames) if f.tell() == 0: writer.writeheader() writer.writerow({ "接收时间": get_timestamp(), "仿真时间": parsed['timestamp'], "robot1状态": parsed['robot1_state'], "robot2状态": parsed['robot2_state'], "robot1任务": parsed['robot1_task'], "robot2任务": parsed['robot2_task'], "buff1": parsed['buff1'], "AGV1_载货": parsed['agv1_load'], "AGV2_载货": parsed['agv2_load'], "AGV1_X": parsed['agv1_x'], "AGV1_Y": parsed['agv1_y'], "AGV1_Z": parsed['agv1_z'], "AGV2_X": parsed['agv2_x'], "AGV2_Y": parsed['agv2_y'], "AGV2_Z": parsed['agv2_z'], }) except Exception as e: print(f"[{get_timestamp()}] CSV写入失败: {e}")def main(): print("=" * 70) print("FlexSim 数据接收器 (Python 作为服务器)") print(f"监听: {SERVER_IP}:{SERVER_PORT}") print(f"数据存储: {CSV_FILENAME}") print("按 Ctrl+C 停止") print("=" * 70) # 初始化 CSV 文件(每次启动覆盖旧数据) try: with open(CSV_FILENAME, 'w', newline='', encoding='utf-8') as f: fieldnames = [ "接收时间", "仿真时间", "robot1状态", "robot2状态", "robot1任务", "robot2任务", "buff1", "AGV1_载货", "AGV2_载货", "AGV1_X", "AGV1_Y", "AGV1_Z", "AGV2_X", "AGV2_Y", "AGV2_Z" ] writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() print(f"[{get_timestamp()}] CSV 文件已初始化") except Exception as e: print(f"[{get_timestamp()}] CSV 初始化失败: {e}") # 创建服务器 Socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((SERVER_IP, SERVER_PORT)) server_socket.listen(1) print(f"[{get_timestamp()}] 服务器已启动,等待 FlexSim 连接...") while True: try: conn, addr = server_socket.accept() print(f"[{get_timestamp()}] FlexSim 已连接: {addr}") conn_file = conn.makefile('r', encoding='utf-8', buffering=1) while True: line = conn_file.readline() if not line: print(f"[{get_timestamp()}] FlexSim 断开连接") break parsed = parse_and_display(line) if parsed: save_to_csv(parsed) conn.close() except KeyboardInterrupt: print(f"\n[{get_timestamp()}] 用户中断") break except Exception as e: print(f"[{get_timestamp()}] 错误: {e}") continue server_socket.close() print(f"[{get_timestamp()}] 服务器已关闭")if __name__ == "__main__": main()
## 三、FlexSim 端设置
### 3.1 模型对象命名
在 FlexSim 的 3D 视图中添加需要监控的对象,并重命名为**英文名称**(避免中文导致 `node()` 函数找不到对象):
我这里是只采用了两个机械臂,一个暂存区还有两台agv,需要更多数量的自己添加节点在代码里面就好。
> **注意**:如果你使用不同的名称,请相应修改 DataSender 代码中 `node("MODEL:/对象名", model())` 的字符串。
### 3.2 设置 OnRunStart 触发器
**操作路径**:`工具箱(Toolbox)` → `模型触发器(Model Triggers)` → 双击 `OnRunStart`
**作用**:模型开始运行时,初始化客户端 Socket 并准备连接 Python 服务器。
// ============================================// OnRunStart - 初始化客户端连接// ============================================print("OnRunStart 开始执行...");treenode tools = node("/Tools", model());if (!objectexists(tools)) tools = node(" >Tools", model());// 创建 client 节点并初始化为 0treenode clientNode = assertsubnode(tools, "client", DATATYPE_NUMBER);setnodenum(clientNode, 0);print("OnRunStart 执行完毕,DataSender 将管理连接");return 1;
### 3.3 创建 DataSender 用户事件
**操作路径**:`工具箱(Toolbox)` → 点击 `添加(Add)` → 选择 `用户事件(User Events)`
**事件代码**:粘贴以下完整代码
```flexscript/** Custom Code */print("========== DataSender 开始执行 (时间: " + numtostring(time(), 3) + ") ==========");// 1. 获取 Tools 和 client 节点treenode tools = node("/Tools", model());if (!objectexists(tools)) { tools = node(" >Tools", model());}treenode clientNode = node("client", tools);if (!objectexists(clientNode)) { clientNode = assertsubnode(tools, "client", DATATYPE_NUMBER); setnodenum(clientNode, 0);}int clientID = getnodenum(clientNode);// 2. 如果 clientID 无效,建立新连接if (clientID <= 0) { print("DataSender: 创建新连接..."); int newID = clientcreate(); // 创建客户端 Socket if (newID <= 0) { print("DataSender: 创建客户端失败"); return 0; } int conn = clientconnect(newID, "127.0.0.1", 9000); // 连接 Python 服务器 if (!conn) { print("DataSender: 连接服务器失败"); clientclose(newID); setnodenum(clientNode, 0); return 0; } clientID = newID; setnodenum(clientNode, clientID); print("DataSender: 连接成功!");} else { print("DataSender: 使用已有连接 ID=" + numtostring(clientID, 0));}// 3. 获取对象节点treenode robot1 = node("MODEL:/robot1", model());treenode robot2 = node("MODEL:/robot2", model());treenode buff1 = node("MODEL:/buff1", model());treenode agv1 = node("MODEL:/AGV1", model());treenode agv2 = node("MODEL:/AGV2", model());if (!objectexists(robot1) || !objectexists(robot2) || !objectexists(buff1) || !objectexists(agv1) || !objectexists(agv2)) { print("DataSender: 部分对象未找到,跳过本次发送"); return 0; }// 4. 采集机械臂状态int state1 = getstatenum(robot1, 0);int state2 = getstatenum(robot2, 0);// 5. 采集机械臂任务信息string robot1_task_desc = "无任务";string robot2_task_desc = "无任务";TaskExecuter te1 = robot1.as(TaskExecuter);var task1 = te1.activeTask;if (task1 != 0) { int taskType = task1.type; var obj2 = task1.involved2; string name2 = ""; if (objectexists(obj2)) name2 = getname(obj2); if (taskType == TASKTYPE_LOAD) robot1_task_desc = "从 [" + name2 + "] 装载"; else if (taskType == TASKTYPE_UNLOAD) robot1_task_desc = "在 [" + name2 + "] 卸载"; else if (taskType == TASKTYPE_TRAVEL) robot1_task_desc = "前往 [" + name2 + "]";}TaskExecuter te2 = robot2.as(TaskExecuter);var task2 = te2.activeTask;if (task2 != 0) { int taskType = task2.type; var obj2 = task2.involved2; string name2 = ""; if (objectexists(obj2)) name2 = getname(obj2); if (taskType == TASKTYPE_LOAD) robot2_task_desc = "从 [" + name2 + "] 装载"; else if (taskType == TASKTYPE_UNLOAD) robot2_task_desc = "在 [" + name2 + "] 卸载"; else if (taskType == TASKTYPE_TRAVEL) robot2_task_desc = "前往 [" + name2 + "]";}// 6. 采集 AGV 坐标和载货量double agv1X = xloc(agv1);double agv1Y = yloc(agv1);double agv1Z = zloc(agv1);double agv2X = xloc(agv2);double agv2Y = yloc(agv2);double agv2Z = zloc(agv2);int agv1_load = agv1.subnodes.length;int agv2_load = agv2.subnodes.length;// 7. 采集暂存区存货量int content1 = content(buff1);// 8. 构建 JSON 数据包double t = time();string json = "{";json = json + "\"timestamp\":" + numtostring(t, 3) + ",";json = json + "\"robot1_state\":" + numtostring(state1, 0) + ",";json = json + "\"robot2_state\":" + numtostring(state2, 0) + ",";json = json + "\"robot1_task\":\"" + robot1_task_desc + "\",";json = json + "\"robot2_task\":\"" + robot2_task_desc + "\",";json = json + "\"buff1_content\":" + numtostring(content1, 0) + ",";json = json + "\"agv1_load\":" + numtostring(agv1_load, 0) + ",";json = json + "\"agv2_load\":" + numtostring(agv2_load, 0) + ",";json = json + "\"agv1_x\":" + numtostring(agv1X, 2) + ",";json = json + "\"agv1_y\":" + numtostring(agv1Y, 2) + ",";json = json + "\"agv1_z\":" + numtostring(agv1Z, 2) + ",";json = json + "\"agv2_x\":" + numtostring(agv2X, 2) + ",";json = json + "\"agv2_y\":" + numtostring(agv2Y, 2) + ",";json = json + "\"agv2_z\":" + numtostring(agv2Z, 2);json = json + "}";// 9. 发送数据int sendResult = clientsend(clientID, json + "\n");if (sendResult == 0) { print("DataSender: 发送失败,连接已断开"); clientclose(clientID); setnodenum(clientNode, 0);} else { print("DataSender: 【FlexSim已发出】" + json);}print("========== DataSender 执行完毕 ==========");return 1;```
> **代码说明**:
> - `clientcreate()` 创建新的客户端 Socket
> - `clientconnect()` 连接到指定的 IP 和端口
> - `clientsend()` 发送数据
> - `clientclose()` 关闭连接
> - 用户事件的“重复事件”功能驱动定时执行,无需在代码中使用 `senddelayedmessage` 自循环
### 3.4 设置 OnReset 触发器
**操作路径**:`工具箱(Toolbox)` → `模型触发器(Model Triggers)` → 双击 `OnReset`
**作用**:模型重置时关闭 Socket 连接并清理状态。
```flexscript// ============================================// OnReset - 清理连接// ============================================print("OnReset 开始执行...");treenode tools = node("/Tools", model());if (!objectexists(tools)) tools = node(" >Tools", model());treenode clientNode = node("client", tools);if (objectexists(clientNode)) { int id = getnodenum(clientNode); if (id > 0) { clientclose(id); print("已关闭客户端连接 ID: " + numtostring(id, 0)); } setnodenum(clientNode, 0);}print("OnReset 执行完毕");return 1;```
## 四、测试与验证
### 4.1 启动顺序(严格按顺序)
1. **启动 Python 服务器**:
看到 `服务器已启动,等待 FlexSim 连接...` 表示服务器就绪。
2. **打开 FlexSim 模型**,确保已完成上述所有设置。
3. **点击“重置”** → **点击“运行”**。
### 4.2 预期输出
**FlexSim 控制台**应显示:```OnRunStart 开始执行...OnRunStart 执行完毕,DataSender 将管理连接========== DataSender 开始执行 (时间: 0) ==========DataSender: 创建新连接...DataSender: 连接成功!DataSender: 【FlexSim已发出】{"timestamp":0,...}========== DataSender 执行完毕 ==================== DataSender 开始执行 (时间: 1) ==========DataSender: 使用已有连接 ID=1DataSender: 【FlexSim已发出】{"timestamp":1,...}========== DataSender 执行完毕 ==========```
**Python 服务器**应显示:```[2026-08-03 14:10:36.495] FlexSim 已连接: ('127.0.0.1', 52742)[0.000] robot1: 空闲 | 任务: 无任务 robot2: 空闲 | 任务: 无任务 buff1: 0 AGV1: 坐标(102.00, 18.00, 0.00) | 载货: 0 AGV2: 坐标(102.00, 15.00, 0.00) | 载货: 0--------------------------------------------------[1.000] ...```
### 4.3 数据说明
## 五、常见问题排查
## 六、架构优势说明
本方案采用 **Python 作为服务器、FlexSim 作为客户端** 的架构,相比 FlexSim 作为服务器有以下优势:
1. **避开 `socketinit()` 问题**:FlexSim 作为服务器时需要调用 `socketinit()` 初始化 Windows Socket 后台进程,某些环境下可能因权限或系统配置失败。作为客户端时只需 `clientcreate()` + `clientconnect()`,更加稳定可靠。
2. **连接管理更简单**:客户端负责主动连接,断线后可自动重连。
3. **Python 生态优势**:Python 作为服务器可以方便地扩展数据存储、可视化、机器学习等高级功能。
4. **调试更方便**:Python 服务器可以独立运行和测试,无需依赖 FlexSim 环境。