ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Robocity实战:从零搭建多机器人任务调度中台

Robocity实战:从零搭建多机器人任务调度中台 2026 年还远吗从技术演进角度看真正值得关注的不是某个时间节点而是接下来围绕“机器人走进真实场景”的软件体系会如何成型。Robocity 这个词最近被越来越多开发者讨论它可以是 Robo City 的组合也可以理解成机器人技术从一个封闭的实验室课题走向人群、社区、园区、工厂的底层基础设施。本文不讨论商业概念而从后端开发者最容易切入的角度出发梳理 Robocity 场景背后的技术架构并带大家从零搭建一个可运行的多机器人任务调度中台 Demo。如果你正在规划机器人类项目或想提前积累具身智能、多机协同方向的项目经验这篇文章会很有参考价值。1. 为什么说“2026 年的一切都只是 Robocity 的序幕”1.1 先理解 Robocity 这个词很多人第一次看到 Robocity会觉得它是一个新产品或者新平台的名字。其实更稳妥的理解方式是把它拆开Robo City也就是“机器人 城市/社区/生活场景”。它表达的不只是某一个机器人本身而是大量机器人在物理空间中协同完成服务、巡检、配送、引导、搬运等任务的软件基础设施。过去十年我们谈机器人更多是谈单体能力这个机械臂重复定位精度多少那个无人车能否识别红绿灯。但从 Robocity 的视角看单体能力只是“执行单元”真正决定系统上限的是机器人之间如何通信、任务如何拆解、异常如何恢复、状态如何可视化。这也是为什么 2026 年的一切只是序幕因为单机智能成熟之后下一步必然是多机协同和场景化运营。1.2 单机智能到集群服务的拐点机器人行业正在经历一个明显拐点从“一个机器人完成一条固定动作”转向“一群机器人动态响应不确定需求”。前者很像传统工业自动化流程固定、环境固定后者更像互联网后端服务请求随时到达资源需要弹性调度系统需要持续在线。在这个拐点下开发者面对的挑战也变了以前主要调试电机、传感器、控制器。现在还要考虑任务队列、优先级、机器人状态上报、故障转移。以前机器人只跟 PLC 通信。现在机器人需要对接业务系统、语音助手、大模型 Agent。后端工程师过去积累的分布式系统经验反而成了 Robocity 时代的重要能力。本文要做的 Demo就是把“多机调度”这件事抽象成后端开发者熟悉的模型先用模拟器跑通闭环再逐步替换成真实机器人。2. Robocity 背后的技术底座在动手之前先认识一下 Robocity 类应用通常会用到哪些技术底座。这部分不要求全部掌握但理解全貌有助于后续设计模块边界。2.1 端侧智能与模型驱动机器人本体上会运行感知、决策、运动控制等程序。早期这些程序以规则和传统 CV 算法为主现在越来越多模块开始由深度学习模型承担。端侧智能的价值在于低延迟和隐私保护比如机器人遇到行人需要立即停止不可能每一帧都发到云端再等结果。对于后端开发者来说端侧智能带来的变化是接口语义更丰富。以前机器人上报的是“电机转速”现在上报的是“检测到前方 2 米有障碍物”“当前可执行任务类型是哪些”。也就是说机器人越来越像一个带状态的边缘服务而不是一个单纯的执行设备。2.2 仿真、模拟器与真机之间的迁移Robocity 项目开发周期里仿真和模拟器的重要性非常高。真机测试成本高、场景受限不适合做大规模并发验证。业界常见的路线是先在仿真环境里验证算法和调度策略再把软件部署到真实机器人上。本文的 Demo 也遵循这个思想用 Python 线程模拟机器人执行任务和上报心跳。代码层面的接口一旦稳定后续把RobotSimulator替换成真实机器人的 SDK 适配层即可。这样做的好处是后端调度逻辑可以先独立开发和测试不被硬件问题阻塞。2.3 面向场景的工程化能力如果说仿真解决“能不能跑通”那工程化解决“能不能持续稳定运行”。一个 Robocity 系统里后端基础设施至少涉及以下几个方面任务接入层接收用户指令或业务系统生成的请求。意图理解层把自然语言或业务事件转换成结构化任务。调度决策层决定把任务派给哪台机器人。机器人接入层管理机器人注册、心跳、状态查询。监控运维层展示任务状态、机器人电量、异常告警。这个分层模型和传统微服务架构很像但差异在于领域对象。机器人不是一个无状态服务它有物理位置、电量、当前动作、任务进度写代码时必须把这些状态显式建模。3. 拆解 Robocity 应用开发的核心抽象写代码之前先定义清楚领域模型。很多多机器人系统做得混乱根本原因是机器人和任务的状态没有统一抽象。下面介绍我会在实战中使用的四个核心抽象。3.1 机器人本体抽象机器人在系统里不能只存一个 ID至少应该包含robot_id全局唯一标识。position当前位置可以是区域名或者坐标。skills能执行的任务类型用于任务匹配。online是否在线。current_task_id当前正在执行的任务。heartbeat_ts最近心跳时间。completed_count已完成任务数。用面向对象的方式建模可以让调度器代码保持清晰。真实场景中这些字段会对应到机器人 SDK 上报的实时数据但在 Demo 阶段我们用本地对象模拟。3.2 任务模型任务需要经历多个状态。本文采用最简单但够用的状态机pending - dispatched - running - success/failedpending任务已创建等待调度。dispatched已分配给某台机器人但还没有开始执行。running机器人正在执行。success执行成功。failed执行失败。实际生产环境还会增加timeout、cancelled、retrying等状态。任务字段至少包括content、target_robot、priority、status、created_at、updated_at。3.3 调度器调度器是整个系统的核心。它不断从任务池里取出pending任务找到空闲且在线的机器人将任务分配出去。最简单的调度规则可以是任务按优先级排序数字越小越优先。挑选空闲机器人时优先选择技能匹配的机器人。如果任务指定了target_robot则只会分给指定机器人。真实场景中调度器还要考虑电量、距离、机械臂类型、网络分区等约束但核心模式是一致的就是“任务筛选 资源匹配 状态变更”。3.4 意图解析层在 Robocity 场景里用户不会总是通过表单提交任务更多时候会输入一句话比如“让机器人去 B 区巡检”。意图解析层负责把自然语言转成结构化任务。最简单的实现是基于关键词匹配的规则引擎复杂一点可以接入大模型让模型输出JSON格式的任务描述。实战中我们先写规则版本保证离线可运行之后再留出接入大模型的接口。4. 实战搭建一个可运行的多机器人调度中台这一节我们从零开始搭建一个最小但完整的多机器人调度中台。它不依赖 ROS、不依赖实体硬件只需要 Python 和 Flask 就能运行。4.1 项目结构与初始化我们先创建项目目录mkdir robocity-demo cd robocity-demo项目结构如下robocity-demo/ ├── app.py ├── robot.py ├── scheduler.py ├── simulator.py ├── task.py └── requirements.txt建议先创建虚拟环境python -m venv .venv source .venv/bin/activateWindows 下激活命令是.venv\Scripts\activate创建依赖文件requirements.txtflask然后安装pip install -r requirements.txt如果你网络环境不方便安装 Flask也可以只运行核心调度逻辑但为了看到 Web API 效果本文仍以 Flask 作为演示依赖。版本不需要纠结使用当前稳定版即可。4.2 实现任务模型 task.pytask.py定义任务数据结构和状态枚举。这里使用 Python 的dataclass代码更简洁。# task.py import time import uuid from dataclasses import dataclass, field from enum import Enum class TaskStatus(str, Enum): PENDING pending DISPATCHED dispatched RUNNING running SUCCESS success FAILED failed dataclass class Task: content: str target_robot: str priority: int 5 task_id: str field(default_factorylambda: uuid.uuid4().hex[:8]) status: str TaskStatus.PENDING.value created_at: float field(default_factorytime.time) updated_at: float field(default_factorytime.time) def to_dict(self): return { task_id: self.task_id, content: self.content, target_robot: self.target_robot, priority: self.priority, status: self.status, created_at: self.created_at, updated_at: self.updated_at, }关键点task_id使用uuid4生成短 ID避免并发冲突。priority默认 5数字越小越优先。created_at和updated_at记录任务生命周期。4.3 实现机器人抽象 robot.pyrobot.py描述一台机器人的状态并提供心跳、任务分配、任务完成等基础方法。# robot.py import time class Robot: def __init__(self, robot_id: str, position: str 待命区, skillsNone): self.robot_id robot_id self.position position self.skills skills or [搬运, 巡检, 引导] self.online False self.current_task_id None self.heartbeat_ts None self.completed_count 0 def heartbeat(self): self.online True self.heartbeat_ts time.time() def assign(self, task_id: str): self.current_task_id task_id def finish(self, task_id: str): if self.current_task_id task_id: self.current_task_id None self.completed_count 1 def to_dict(self): return { robot_id: self.robot_id, position: self.position, skills: self.skills, online: self.online, current_task_id: self.current_task_id, heartbeat_ts: self.heartbeat_ts, completed_count: self.completed_count, }这里把current_task_id设计为单个任务说明 Demo 中的机器人同一时间只能执行一个任务。真实场景中大型复合机器人可能支持多个子任务并行但为降低复杂度本文先保持单任务模型。4.4 实现调度器 scheduler.py调度器是整个 Demo 最核心的部分。它负责维护任务池和机器人列表每个调度周期扫描一次待执行任务并分配给空闲机器人。# scheduler.py import threading import time from task import Task, TaskStatus class TaskScheduler: def __init__(self): self.tasks {} self.robots {} self.lock threading.Lock() self._stop False def stop(self): self._stop True def register_robot(self, robot): with self.lock: self.robots[robot.robot_id] robot return robot.to_dict() def update_heartbeat(self, robot_id): robot self.robots.get(robot_id) if robot: robot.heartbeat() return True return False def create_task(self, content, target_robot, priority5): task Task(contentcontent, target_robottarget_robot, prioritypriority) with self.lock: self.tasks[task.task_id] task return task.task_id def dispatch_once(self): with self.lock: pending_tasks [ task for task in self.tasks.values() if task.status TaskStatus.PENDING.value ] pending_tasks.sort(keylambda task: task.priority) idle_robots [ robot for robot in self.robots.values() if robot.online and robot.current_task_id is None ] matched [] for task in pending_tasks: if not idle_robots: break if task.target_robot: robot next( (r for r in idle_robots if r.robot_id task.target_robot), None ) if robot is None: continue else: robot idle_robots[0] robot.assign(task.task_id) task.target_robot robot.robot_id task.status TaskStatus.DISPATCHED.value task.updated_at time.time() idle_robots.remove(robot) matched.append({ task_id: task.task_id, robot_id: robot.robot_id }) return matched def finish_task(self, task_id, successTrue): with self.lock: task self.tasks.get(task_id) if not task: return False robot self.robots.get(task.target_robot) if robot: robot.finish(task_id) task.status TaskStatus.SUCCESS.value if success else TaskStatus.FAILED.value task.updated_at time.time() return True def dispatch_loop(self, interval1.0): while not self._stop: self.dispatch_once() time.sleep(interval) def summary(self): with self.lock: robot_states [robot.to_dict() for robot in self.robots.values()] task_list sorted(self.tasks.values(), keylambda task: task.created_at) return { total_tasks: len(self.tasks), pending_tasks: sum( 1 for t in self.tasks.values() if t.status TaskStatus.PENDING.value ), running_tasks: sum( 1 for t in self.tasks.values() if t.status in (TaskStatus.DISPATCHED.value, TaskStatus.RUNNING.value) ), success_tasks: sum( 1 for t in self.tasks.values() if t.status TaskStatus.SUCCESS.value ), failed_tasks: sum( 1 for t in self.tasks.values() if t.status TaskStatus.FAILED.value ), robots: robot_states, recent_tasks: [task.to_dict() for task in task_list[-10:]] }调度逻辑有几个值得留意的点。首先所有操作都放在self.lock保护下因为调度线程、心跳线程和 HTTP 请求线程会同时访问共享数据。其次dispatch_once每次只处理当前时刻的pending任务只要任务状态没有变化下一次循环还会继续尝试分配这天然具备了重试能力。最后任务指定target_robot后只会选择目标机器人不会把任务派给其他机器人。4.5 实现机器人模拟器 simulator.py真实系统中机器人会通过 MQTT、WebSocket 或者 HTTP 上报状态。这里我们用 Python 线程模拟机器人的心跳和任务执行过程。# simulator.py import threading import time class RobotSimulator: def __init__(self, scheduler, robot, execute_seconds3): self.scheduler scheduler self.robot robot self.execute_seconds execute_seconds self._stop False self.thread threading.Thread(targetself._run, daemonTrue) def start(self): self.thread.start() def stop(self): self._stop True def _run(self): while not self._stop: # 模拟周期性心跳上报 self.scheduler.update_heartbeat(self.robot.robot_id) task_id self.robot.current_task_id if task_id: # 模拟机器人行走/作业耗时 time.sleep(self.execute_seconds) self.scheduler.finish_task(task_id, successTrue) else: time.sleep(1)每个RobotSimulator对应一台机器人循环体做的事很简单上报心跳如果发现分配到了任务就等待几秒模拟执行然后调用finish_task完成任务。daemonTrue保证主进程退出时模拟线程能自动结束。4.6 实现 Web API app.py为了让系统可以被外部调用我们用 Flask 暴露三个接口查看全局状态、创建任务、通过自然语言创建任务。# app.py import threading from flask import Flask, jsonify, request from robot import Robot from scheduler import TaskScheduler from simulator import RobotSimulator app Flask(__name__) scheduler TaskScheduler() simulators [] def create_mock_robots(): carrier Robot(robot_idcarrier-01, positionA区仓库, skills[搬运, 取货]) patrol Robot(robot_idpatrol-02, positionB区走廊, skills[巡检, 引导]) scheduler.register_robot(carrier) scheduler.register_robot(patrol) sim_carrier RobotSimulator(scheduler, carrier, execute_seconds4) sim_patrol RobotSimulator(scheduler, patrol, execute_seconds3) sim_carrier.start() sim_patrol.start() simulators.extend([sim_carrier, sim_patrol]) INTENT_RULES [ ((巡检, 巡逻), 前往 B 区执行一次安全巡检), ((搬运, 取货, 送物料), 到 A 区仓库领取物料并送往 C 区), ((引导, 带路), 到 D 区前台为访客提供引导), ] def parse_intent(text): for keywords, canned_task in INTENT_RULES: if any(keyword in text for keyword in keywords): return { raw_intent: text, canonical_task: canned_task, rule: RULE_MATCH, } return { raw_intent: text, canonical_task: text, rule: PASSTHROUGH, } app.route(/api/status, methods[GET]) def status(): return jsonify(scheduler.summary()) app.route(/api/tasks, methods[POST]) def create_task(): payload request.get_json(silentTrue) or {} content (payload.get(content) or ).strip() if not content: return jsonify({error: content is required}), 400 target_robot payload.get(target_robot, ) priority int(payload.get(priority, 5)) task_id scheduler.create_task( contentcontent, target_robottarget_robot, prioritypriority ) return jsonify({task_id: task_id, status: pending}), 201 app.route(/api/intent, methods[POST]) def intent(): payload request.get_json(silentTrue) or {} text (payload.get(text) or ).strip() if not text: return jsonify({error: text is required}), 400 parsed parse_intent(text) task_id scheduler.create_task(contentparsed[canonical_task]) parsed[task_id] task_id return jsonify(parsed), 201 if __name__ __main__: create_mock_robots() dispatch_thread threading.Thread( targetscheduler.dispatch_loop, args(1.0,), daemonTrue ) dispatch_thread.start() app.run(host127.0.0.1, port5000, debugFalse)接口说明GET /api/status查看调度器当前状态、机器人列表、最近任务列表。POST /api/tasks手动创建一个任务请求体可以是{content: 去 A 区执行巡检, priority: 1}。POST /api/intent模拟自然语言入口内部先用规则解析再生成任务。4.7 启动与验证在项目根目录执行python app.py启动成功后终端会显示类似信息* Running on http://127.0.0.1:5000新开一个终端先查看系统状态curl http://127.0.0.1:5000/api/status因为两个模拟机器人刚启动状态里会看到online变为truecurrent_task_id为null。接着创建一个任务curl -X POST http://127.0.0.1:5000/api/tasks \ -H Content-Type: application/json \ -d {content: 前往 B 区执行一次安全巡检, priority: 1}返回结果类似{ task_id: a1b2c3d4, status: pending }再请求一次状态接口可以看到任务已经变成dispatched并分配给了空闲的巡逻机器人。等待几秒后再次请求状态任务会变成success机器人completed_count增加 1。也可以测试自然语言接口curl -X POST http://127.0.0.1:5000/api/intent \ -H Content-Type: application/json \ -d {text: 请安排机器人去 D 区带路}返回结果里会包含canonical_task和task_id表示意图解析和任务创建都成功了。5. 从规则解析到 Agent意图层如何升级5.1 当前实现的问题上面的parse_intent使用关键词匹配优点是零依赖、可离线运行但缺点也很明显同一种意图的不同表达方式覆盖不全。无法理解复合指令比如“先巡检再回充电桩”。无法结合上下文比如“刚才说的那个区域”。不支持输出结构化参数只是把指令映射成固定文本。真实 Robocity 系统里用户指令会非常口语化甚至包含歧义。这时规则引擎就不够用了需要引入大模型或专门训练的意图模型。5.2 用大模型补齐意图理解接入大模型后通常会让模型输出一段固定结构的 JSON再交给调度器。这里给出一个函数替换思路不绑定具体模型 SDK# 示例思路替换 parse_intent 时的核心函数 def parse_intent_with_llm(text: str) - dict: prompt f 你是一个机器人任务调度助手。 请把用户输入解析成一条结构化任务。 用户输入{text} 请只输出 JSON格式如下 {{canonical_task: 任务描述, target_area: 目标区域, priority: 5}} # 实际项目中把下面的注释替换成你使用的模型客户端 # resp your_llm_client.complete(prompt) # result json.loads(resp[choices][0][message][content]) result { canonical_task: 前往 D 区执行带路任务, target_area: D区, priority: 5, } return result需要注意上面的代码是接口约定示意不能直接复制运行因为your_llm_client需要按你实际使用的模型 SDK 替换。工程上建议把规则引擎和模型解析放在同一个抽象层里先走规则规则未命中再调用模型这样既节省成本也能在模型异常时兜底。5.3 升级后的数据流接入模型后完整的请求链路可以画成下面几步用户说一句话“帮我叫台机器人去 D 区带路。”意图解析层调用模型输出结构化 JSON。调度器创建任务并投入待执行队列。调度循环发现有匹配技能的机器人则执行分配。机器人模拟器上报心跳并完成执行。用户通过状态接口看到最终结果。可以看出第 4、5、6 步完全不需要改动升级只发生在意图解析层。这就是模块化的好处把变化点限制在单一模块内。6. 常见问题与排查实际运行这个 Demo 时可能遇到以下几种问题。我整理了一张速查表。问题现象常见原因解决思路启动时端口被占用5000 端口已被其他程序使用修改app.run中的port或先结束占用进程请求/api/status返回连接失败Flask 服务未启动或启动失败检查终端是否在执行python app.py后进入阻塞态任务一直处于pending机器人未上报心跳或没有在线机器人查看/api/status中机器人的online字段任务指定了机器人但一直不执行指定机器人不在线或正在执行其他任务在任务中不指定target_robot让调度器自动选择修改代码后没有生效Flask 进程未重启停止python app.py后重新启动提示No module named flask虚拟环境未激活或依赖未安装执行pip install -r requirements.txt6.1 任务一直不被调度怎么办首先通过状态接口确认机器人是否在线curl http://127.0.0.1:5000/api/status如果看到online为false说明模拟线程可能没有启动或者调度器对象和模拟器对象不是同一个实例。常见错误是在app.py里创建了两个TaskScheduler注册机器人和调度循环使用不同对象导致机器人状态永远不同步。如果机器人是在线的但仍然没有调度成功要检查任务里是否指定了不存在的机器人。比如target_robot填成了robot-99而系统里只有carrier-01和patrol-02那么任务会一直等待目标机器人出现。6.2 任务执行完成状态没有变化怎么办模拟器的执行逻辑每 1 秒检查一次current_task_id。如果execute_seconds设置得很大任务状态看起来会“卡”在dispatched一段时间这属于正常现象。如果等了很久状态仍未变化可能是调度线程已经停止。检查终端是否有异常堆栈输出并在app.py里确认dispatch_thread.start()只被调用了一次。6.3 如何观察后台状态Demo 中查看状态最直接的方式是curl http://127.0.0.1:5000/api/status | python -m json.tool这样可以格式化输出 JSON方便观察任务列表和机器人状态。生产环境中更推荐把状态指标接入 Prometheus 或 Grafana但 Demo 阶段用接口观察就足够了。7. 工程最佳实践把 Demo 跑通只是第一步真正上生产时要考虑的问题会更多。这里给出几条我总结的工程建议。7.1 状态机与超时管理任务状态不能只靠前端判断后端必须处理超时。比如机器人收到任务后 10 秒没有确认开始执行调度器应该将该任务重新置为pending或者标记为failed并告警。建议在任务模型里增加几个字段ack_timeout分配后等待确认的最长时间。execute_deadline任务整体执行的截止时间。retry_count已重试次数避免无限重试。同时要注意状态流转只能由调度器统一修改不要在模拟器或其他业务代码里直接改任务状态否则并发场景下很容易出现状态覆盖。7.2 协议版本与消息边界真实机器人不会直接共享 Python 对象而是通过网络上报消息。无论是 MQTT 还是 WebSocket都要考虑消息协议版本。机器人端和服务器端发布节奏不同
返回列表