0. 决策摘要
基础任务是原子节点;送药、宣教、心理评测是按顺序展开的组合任务。
h_robot_task_queue 控制每台机器人;robot_id + queue_seq 决定任务顺序。
节点成功才递增 current_node_seq,不需要中断恢复复杂上下文。
持续低电量时冻结队列,终止当前可停止节点,导航至本任务地图的 HP。
0.1 不可破坏的约束
- 每个任务必须有
area_id;需要移动的任务和节点必须有明确map_id。 h_map.area_id是区域到地图的一对多单向关系,不在h_area回填map_id,不新增弱价值关系表。- 同一区域可以有多张地图,但移动任务只在机器人已确认加载的同一
map_id内执行;一期不跨地图自动切换或规划。 - 新任务只追加到队尾;正常业务任务不抢占正在运行的任务。
- 导航只下发目标点位和坐标快照,服务端不计算路径、不模拟避障。
- 任务、节点、命令、事件、投递结果均不物理删除;终态长期留存。
- HTTP/5000 回执只代表“命令已受理”,最终成败必须由 6000 事实事件确认。
1. CR101 现状与升级边界
以下结论来自当前工程与 2026-09-14 数据库快照,明确区分“已经存在”和“本方案新增”。
| 能力 | 当前 CR101 | 升级后 | 变更性质 |
|---|---|---|---|
| 任务数据 | task + task_result,旧写入口默认冻结 | h_robot_task + node + item + command + event + outbox | 新增并迁移只读历史 |
| 后台页面 | 旧任务页只读;送药任务页为占位 | 创建向导、设备队列、任务驾驶舱、异常处置 | 替换任务域页面 |
| 机器人 Pad | 当前任务、药箱、返程、历史入口为占位 | 当前节点、患者核验、内容互动、人工确认与异常提示 | 填补业务页 |
| 药站 Pad | 尚无任务域闭环 | 装药校验、交接机器人、发车确认、回收与补偿 | 新增业务闭环 |
| WebSocket | robotMap/controlMap/pending/pendingCmdById 已存在 | 保留连接层,增加持久化命令、事件和任务投影 | 复用并增强 |
| 设备事件可靠性 | 边缘有界队列、6001 ACK、断线重连;服务端去重主要在内存 | ACK 前持久化 Inbox/Event,数据库唯一键幂等 | 关键补强 |
| 边缘任务执行 | start_task/stop_task 有实现文件及 Mock;真机装配仍受理即拒 | 先以细粒度 function 编排,或经专项验收后接通 RobotTask | 上线硬门禁 |
| 导航 | go_target_point/cancel_go_target_point 真机已接线,ROS2 自主规划 | 作为 MOVE_A2B 基础节点执行器 | 直接复用 |
| 地图 | h_map.area_id 已有;当前唯一键限制一区一张发布图 | 一区多张可用图,任务冻结 map_id,运行时核对当前图 | 解除过度约束 |
| 药箱 | h_med_box/h_med_box_slot/h_medicine_pack/h_med_box_fill_log 已投入现役 | 继续作为权威源,任务只保存不可变投递快照 | 复用,不建重复主数据 |
h_map.current_area_key + uk_h_map_current_area 会限制同一区域只能有一张 status=1 的地图。进入任务系统开发前,须先完成引用代码排查和测试库演练,再删除该唯一索引;现有 idx_h_map_area_status(area_id,status) 可继续承担查询。current_area_key 生成列待无引用后再单独删除,不在同一变更中冒险处理。绝不在 h_area 增加 map_id。1.1 对 Opus5 示例的取舍
分层架构、基础任务与组合任务、
seq 顺序、编排画布、动画执行、实时日志、能力注册。MQTT、gRPC、任意 DAG、多机时空冲突规划、后端模拟路径、低电量直接抢占。它们与 CR101 当前目标或能力边界不符。
2. 总体架构
2.1 模块职责
| 模块 | 只负责 | 明确不负责 |
|---|---|---|
| Task Compiler | 业务校验、模板展开、快照冻结、写入队尾 | 不连接机器人、不等待任务执行 |
| FIFO Scheduler | 锁定机器人 QueueCursor,选择最小待执行 queue_seq,持有设备租约 | 不跳过队头、不按普通优先级抢占 |
| Node Executor | 解释单个基础节点、生成命令、消费事实事件 | 不直接修改后续节点输入 |
| WS Hub | 连接、鉴权、收发、广播、短时关联 | 不作为队列和任务状态的最终真相 |
| hb_edge | ROS2 调用、自主导航、硬件执行、安全停车、事实上报 | 不决定云端任务顺序,不自行领取下一任务 |
2.2 CR101 核心三端协同边界
| 工程端 | 任务编排职责 | 状态真相 | 本次扩展重点 |
|---|---|---|---|
hb_admin | 创建、预校验、查看队列与进度,发起暂停/继续/召回/变更 | REST 重拉的数据库投影;WS 只负责增量刷新 | 新增任务向导、驾驶舱、异常处置和审计视图 |
hb_server | 模板编译、FIFO 游标、节点状态机、命令/事件账本、恢复对账 | MySQL 是任务唯一持久真相,内存 Map 只保存在线连接 | 新增任务域表、Scheduler、Executor、Inbox/Outbox |
hb_edge | 执行白名单基础能力,调用 ROS2/硬件,实施本地安全并上报事实 | 机器人运动、门态和硬件结果的事实源 | 补齐任务上下文、幂等键、事件序号、断线恢复与围栏校验 |
hb_robot_app 与 hb_site_app 若作为机器人 Pad、药站 Pad 投入使用,应复用 hb_server 的同一 REST/WS 契约;它们是伴随操作界面,不另建任务队列或边缘调度器。
3. 统一任务模型
3.1 基础任务:可执行的最小能力
这是目标能力目录,不代表全部已经接通:当前真机可复用 go_target_point / cancel_go_target_point、emotion_control、rotate_pill_box_ctrl、robot_self_test 及已接线的上肢/颈部函数;RETURN_HOME 和 WAIT_CONFIRM 由服务端编排;语音播放/互动、身份核验、Pad 内容会话仍需三端实现并通过契约与真机验收后,才能在能力注册表标记为 ENABLED。
go_target_point。输入必须包含 area/map/point 快照。emotion_control。失败默认不影响移动安全,可由模板决定是否阻断。rotate_pill_box_ctrl,必须与箱格快照绑定。robot_self_test 并扩展分项结果。map_id 中的 HP 点位导航。action_code + schema_version + handler + timeout + retry_policy,通过校验后才可进入模板。3.2 组合任务:基础节点的有序模板
身份核验失败不得开格。一次任务可包含多个患者投递段;每个投递段引用独立 DeliveryItem,已投递格与未投递格分别留存结果。
URL 只由后台选择已发布内容,任务冻结内容 ID、版本、URL 摘要和校验值;机器人 Pad 上报打开、互动、提交和关闭事件。
评测结果与执行事件分表存储,任务事件只保存引用、摘要和状态;敏感答案遵循最小权限和脱敏展示。
自检是任务,但不允许通过自检动作解除急停、自动开全部仓门或改变药包归属。涉及物理动作的分项必须显式授权。
3.3 模板不是运行实例
模板用于复用编排;提交任务时,模板当前版本会展开为不可变的 h_robot_task_node 行。模板以后升级,不影响已经排队或完成的任务。任务变更时递增主表 revision:旧修订尚未执行的 Node/DeliveryItem 标记 CANCELED,新修订另插新行;已完成节点、投递证据和事件绝不改写。迟到事件必须匹配当前 task_revision + node_id + lease_epoch 才能推进。
4. 单机器人 FIFO 任务队列
每台机器人拥有一条逻辑队列。queue_seq 是该机器人内单调递增序号;node_seq 是单任务内从 1 开始的执行顺序。所谓“掏出执行”是状态迁移,不是删除记录。
next_queue_seq=105 · active_task_id=NULL · queue_status=READY
它是并发互斥门和恢复锚点。
#101 运行中,#102/#103/#104 排队。完成、失败、召回都只改状态,不删除、不重排。
只允许 QueueCursor 指向的任务拥有一个活动节点;成功后原子切换到下一 node_seq。
4.1 数据库里实际如何排队
4.2 入队事务
- 锁定目标机器人的
h_robot_task_queue行,读取并保留本次next_queue_seq;游标行缺失则失败关闭,不在业务请求中临时补建。 - 校验设备、
area_id、任务权限和能力清单;队列即使正在执行或因电量冻结,仍可按策略允许追加,但绝不改变活动任务。 - 对送药任务锁定药箱、格子、药包和装药流水,校验药箱确实挂载到该机器人。
- 确定
map_id,校验地图属于任务area_id、文件就绪、所需 HP 和目标点存在。 - 使用刚才保留的序号创建 Task(或把既有 DRAFT 转为 QUEUED)、Node 和 DeliveryItem 快照,再把游标的
next_queue_seq加 1;不执行MAX(queue_seq)+1。 - 同一事务写入
TASK_QUEUED事件和 Outbox 后提交;任何一步失败均整体回滚,HTTP 成功时才返回202 Accepted。
4.3 队头消费算法
BEGIN;
SELECT robot_id, active_task_id, active_node_id, queue_status, lease_expire_at
FROM h_robot_task_queue
WHERE robot_id = :robotId
FOR UPDATE;
// 应用层守卫:active_task_id 必须为空,queue_status 必须为 READY;
// 含移动节点时,reported_map_id 还必须等于任务 map_id 且上报未过期,否则阻断。
SELECT id, queue_seq
FROM h_robot_task
WHERE robot_id = :robotId AND status = 'QUEUED' AND deleted = 0
ORDER BY queue_seq ASC
LIMIT 1 FOR UPDATE;
UPDATE h_robot_task
SET status='DISPATCHING', version=version+1
WHERE id=:taskId AND status='QUEUED';
UPDATE h_robot_task_queue
SET active_task_id=:taskId, queue_status='EXECUTING',
lease_owner=:instanceId, lease_expire_at=DATE_ADD(NOW(3), INTERVAL 30 SECOND),
lease_epoch=lease_epoch+1, version=version+1
WHERE robot_id=:robotId AND active_task_id IS NULL AND queue_status='READY';
COMMIT;
// 提交后只执行 active_task 的首个 READY 节点;节点推进也先锁同一 QueueCursor 行。
该算法兼容 CR101 当前 MySQL 5.7,不使用 SKIP LOCKED。同一机器人的所有入队、领取、节点推进、终态释放和安全冻结都先锁同一游标行,因此数据库层能保证单活动任务;租约过期只允许新调度器先对账接管,绝不直接重发副作用命令。
5. 数据结构与存储
5.1 现有权威表,不复制主数据
| 现有表 | 任务系统用途 | 关键事实 |
|---|---|---|
h_device | 机器人和药站身份、区域、在线落档 | 任务引用机器人主键;实时在线仍以 robotMap 为准 |
h_area | 任务权限和活动范围 | 所有新任务必须写入 area_id |
h_map | 导航地图元数据和文件就绪状态 | 一条地图属于一个区域;一个区域允许多张地图 |
h_map_point | HP、CP、房门、床旁和普通点 | 运行任务冻结点位名称与坐标,不在事件返回后重新查坐标 |
h_med_box | 流动药箱、挂载设备、物理格数和业务态 | slot_count 是规格,业务不得写死 15 |
h_med_box_slot | 当前格口、占用、门态和药包关联 | 它是当前态,不承担历史任务结果 |
h_med_box_fill_log | 药包进入/离开格子的生命周期 | 创建任务时用于核对当前装载事实 |
h_medicine_pack | 药包与住院流水、医嘱和配送状态 | 现有 task_id 指向旧 task.id,新版不得混写;由 DeliveryItem 关联新任务并冻结快照 |
h_patient_admission / h_bed / h_room | 收件人住院身份和目的地 | 床位变更后,排队任务必须走受控变更,不可静默漂移 |
5.2 建议新增表总览
5.3 核心 DDL 与字段字典
数据库兼容基线:当前快照标明服务器为 MySQL 5.7.44-log。以下 DDL 不使用 SKIP LOCKED、窗口函数、函数索引或 CHECK 约束,JSON 可空列也不声明默认值。DDL 只是开发基线,不是本次执行脚本;上线前仍需在同版本测试库执行迁移、数据量评估与回滚演练。
表 5-1 h_robot_task_queue:机器人队列游标 / 并发互斥表
用途:每台机器人固定一行,原子分配 queue_seq,并保存当前活动任务、活动节点、队列阻断态和调度租约。它不保存任务内容,而是保证任何进程都不能让同一机器人同时推进两个任务。
CREATE TABLE `h_robot_task_queue` (
`robot_id` BIGINT NOT NULL COMMENT '机器人ID,逻辑FK→h_device.id;每机器人唯一一行',
`next_queue_seq` BIGINT NOT NULL DEFAULT 1 COMMENT '下一次入队分配的序号;事务内读取并递增',
`active_task_id` BIGINT DEFAULT NULL COMMENT '当前活动任务ID;空表示没有任务占用执行权',
`active_node_id` BIGINT DEFAULT NULL COMMENT '当前活动节点ID;仅用于快速定位和恢复对账',
`reported_map_id` BIGINT DEFAULT NULL COMMENT '边缘端最近确认已加载的地图ID;移动任务派发门禁',
`map_reported_at` DATETIME(3) DEFAULT NULL COMMENT '已加载地图事实的最近上报时间;过期时禁止派发移动',
`queue_status` VARCHAR(24) NOT NULL DEFAULT 'READY' COMMENT 'READY/EXECUTING/PAUSED/OFFLINE_BLOCKED/ENERGY_BLOCKED/FAULT_BLOCKED',
`block_code` VARCHAR(64) DEFAULT NULL COMMENT '电量、门态、急停、离线或人工阻断的稳定码',
`block_message` VARCHAR(512) DEFAULT NULL COMMENT '面向运维的阻断说明,不保存敏感详情',
`lease_owner` VARCHAR(64) DEFAULT NULL COMMENT '当前调度实例标识;单实例阶段同样写入',
`lease_expire_at` DATETIME(3) DEFAULT NULL COMMENT '设备队列租约到期时间;过期后必须先对账',
`lease_epoch` BIGINT NOT NULL DEFAULT 0 COMMENT '每次接管递增的围栏代次,防止旧执行器继续推进',
`version` BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本;游标状态更新必须带旧version',
`create_time` DATETIME(3) NOT NULL COMMENT '创建时间;设备纳入任务系统时初始化',
`update_time` DATETIME(3) NOT NULL COMMENT '最后更新时间',
PRIMARY KEY (`robot_id`),
UNIQUE KEY `uk_queue_active_task` (`active_task_id`),
KEY `idx_queue_lease` (`queue_status`,`lease_expire_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='机器人任务队列游标及单活动任务互斥门';
| 字段组 | 字段 | 含义与使用规则 |
|---|---|---|
| 序号 | next_queue_seq | 所有创建入口先锁本行再取号;提交后递增,回滚时任务和序号一起回滚。 |
| 活动指针 | active_task_id / active_node_id | 快速判断执行权归属;最终真相仍由对应 Task/Node 行和事件共同校验。 |
| 地图事实 | reported_map_id / map_reported_at | 来自已鉴权边缘连接;移动节点下发前必须与任务冻结 map_id 一致且未过期。 |
| 调度门禁 | queue_status / block_* | READY 才可领取队头;暂停、离线、电量或故障只冻结领取,不改变既有 queue_seq。 |
| 接管 | lease_owner / lease_expire_at / lease_epoch | 服务重启或未来多实例接管时先抢租约、递增代次、再和机器人对账。 |
| 并发 | version | 防止迟到线程覆盖较新游标;受影响行数不是 1 时必须放弃推进。 |
表 5-2 h_robot_task:任务主表 / 设备 FIFO 队列表
用途:一行代表一个可审计任务;robot_id + queue_seq 唯一决定该机器人队列顺序。任务进入终态后仍保留原序号,不删除、不复用。
CREATE TABLE `h_robot_task` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '任务主键',
`task_no` VARCHAR(40) NOT NULL COMMENT '业务任务号,全局唯一,如 RT202609150001',
`robot_id` BIGINT NOT NULL COMMENT '执行机器人,逻辑FK→h_device.id(type=ROBOT)',
`area_id` BIGINT NOT NULL COMMENT '任务所属区域,逻辑FK→h_area.id;所有任务必填',
`map_id` BIGINT DEFAULT NULL COMMENT '任务使用地图,逻辑FK→h_map.id;含移动节点时必填',
`queue_seq` BIGINT DEFAULT NULL COMMENT '机器人内单调递增队列序号;DRAFT为空,正式提交时分配',
`task_type` VARCHAR(32) NOT NULL COMMENT '队列展示类型:MOVE_A2B/DELIVERY/EDUCATION/ASSESSMENT/SELF_CHECK/ACTION/EXPRESSION/VOICE_PLAY/VOICE_INTERACT/VERIFY_IDENTITY/DISPLAY_CONTENT/CUSTOM',
`template_code` VARCHAR(64) DEFAULT NULL COMMENT '来源模板编码;单基础任务可为空',
`template_version` INT DEFAULT NULL COMMENT '创建时采用的模板版本',
`status` VARCHAR(24) NOT NULL COMMENT 'DRAFT/QUEUED/DISPATCHING/RUNNING/PAUSE_REQUESTED/PAUSED/RETURNING/SUCCEEDED/PARTIAL_SUCCESS/FAILED/CANCELED/BLOCKED',
`current_node_seq` INT DEFAULT NULL COMMENT '当前revision内的节点序号;未开始为空',
`progress` DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '任务展示进度0.00~100.00,由节点权重计算',
`source_type` VARCHAR(24) NOT NULL COMMENT '来源:ADMIN/ROBOT_PAD/SITE_PAD/SYSTEM/API',
`source_ref` VARCHAR(64) DEFAULT NULL COMMENT '外部业务幂等键或来源记录号',
`payload_snapshot` JSON NULL COMMENT '提交时业务参数快照,不作为节点执行真相',
`return_policy` VARCHAR(24) NOT NULL DEFAULT 'RETURN_HOME' COMMENT '结束策略:RETURN_HOME/STAY/NONE',
`energy_policy` VARCHAR(24) NOT NULL DEFAULT 'SAFE_RETURN' COMMENT '电量策略:SAFE_RETURN/BLOCK_CREATE',
`parent_task_id` BIGINT DEFAULT NULL COMMENT '补偿/重试/变更来源任务ID',
`revision` INT NOT NULL DEFAULT 1 COMMENT '任务修订号;变更排队任务时递增',
`version` INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本,所有状态更新必须带旧version',
`failure_code` VARCHAR(64) DEFAULT NULL COMMENT '稳定失败码',
`failure_message` VARCHAR(512) DEFAULT NULL COMMENT '面向运维的失败说明,不放敏感详情',
`queued_at` DATETIME(3) DEFAULT NULL COMMENT '正式进入队尾时间',
`started_at` DATETIME(3) DEFAULT NULL COMMENT '首节点开始时间',
`finished_at` DATETIME(3) DEFAULT NULL COMMENT '进入终态时间',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人',
`create_time` DATETIME(3) NOT NULL COMMENT '创建时间',
`update_by` BIGINT DEFAULT NULL COMMENT '最后修改人',
`update_time` DATETIME(3) NOT NULL COMMENT '最后修改时间',
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除;终态任务原则上也不删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_robot_task_no` (`task_no`),
UNIQUE KEY `uk_robot_queue_seq` (`robot_id`,`queue_seq`),
UNIQUE KEY `uk_robot_source_ref` (`robot_id`,`source_type`,`task_type`,`source_ref`),
KEY `idx_robot_queue_pick` (`robot_id`,`status`,`queue_seq`),
KEY `idx_area_time` (`area_id`,`create_time`),
KEY `idx_parent_task` (`parent_task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='机器人任务主表兼设备FIFO队列';
| 字段组 | 字段 | 含义与使用规则 |
|---|---|---|
| 身份 | id / task_no | 内部主键与对外稳定任务号;协议优先传 task_no,数据库关联用 id。 |
| 归属 | robot_id / area_id / map_id | 固定机器人和业务区域;移动任务 map_id 必填且必须属于 area_id。 |
| 队列 | queue_seq | DRAFT 为空;正式提交后每机器人单调递增,不因取消、失败或删除而重排。 |
| 类型 | task_type / template_* | 前台显示任务类型,并追溯由哪个模板版本展开。 |
| 运行 | status / current_node_seq / progress | 任务实时投影;状态只允许按状态机迁移,禁止任意 UPDATE。 |
| 幂等 | source_type / source_ref | source_ref 使用来源命名空间内稳定请求号;同类型重复提交返回原任务,空字符串必须规范化为 NULL。 |
| 策略 | return_policy / energy_policy | 正常结束与能量异常行为;不能由前端自由注入动作。 |
| 并发 | version | 防止迟到请求覆盖较新任务状态;设备级租约和接管信息存放在 QueueCursor。 |
| 审计 | parent_task_id / revision / failure_* | 补偿、重试和变更均保留来源,失败使用稳定码。 |
表 5-3 h_robot_task_node:任务节点实例表
用途:一行代表一次基础能力执行。组合任务在入队时展开成多行;同任务只允许一个节点处于 RUNNING。
CREATE TABLE `h_robot_task_node` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '节点主键',
`task_id` BIGINT NOT NULL COMMENT '所属任务,逻辑FK→h_robot_task.id',
`task_revision` INT NOT NULL COMMENT '节点所属任务修订号,必须等于生成该节点时的task.revision',
`node_seq` INT NOT NULL COMMENT '任务内顺序,从1开始,成功后只向后推进',
`node_type` VARCHAR(32) NOT NULL COMMENT 'MOVE/ACTION/VERIFY/DISPLAY/INTERACT/BOX/SELF_CHECK/WAIT',
`action_code` VARCHAR(64) NOT NULL COMMENT '能力白名单编码,如 MOVE_A2B/VERIFY_IDENTITY',
`area_id` BIGINT NOT NULL COMMENT '节点区域快照,必须等于任务area_id',
`map_id` BIGINT DEFAULT NULL COMMENT '导航节点地图快照,逻辑FK→h_map.id',
`target_point_id` BIGINT DEFAULT NULL COMMENT '目标点ID快照来源,逻辑FK→h_map_point.id',
`target_snapshot` JSON NULL COMMENT '点位名称、类型、坐标及途经点不可变快照',
`input_snapshot` JSON NULL COMMENT '经schema校验后的动作输入快照',
`status` VARCHAR(20) NOT NULL DEFAULT 'READY' COMMENT 'READY/DISPATCHING/RUNNING/PAUSED/SUCCEEDED/FAILED/SKIPPED/CANCELED',
`weight` DECIMAL(6,3) NOT NULL DEFAULT 1 COMMENT '计算任务进度的权重',
`attempt_no` INT NOT NULL DEFAULT 0 COMMENT '已发起执行次数',
`max_attempts` INT NOT NULL DEFAULT 1 COMMENT '最多尝试次数;物理副作用默认1',
`timeout_sec` INT NOT NULL COMMENT '节点执行超时秒数',
`idempotency_key` VARCHAR(96) NOT NULL COMMENT '节点副作用幂等键,重发保持不变',
`started_at` DATETIME(3) DEFAULT NULL COMMENT '首次开始时间',
`finished_at` DATETIME(3) DEFAULT NULL COMMENT '节点结束时间',
`result_code` VARCHAR(64) DEFAULT NULL COMMENT '节点结果码',
`result_message` VARCHAR(512) DEFAULT NULL COMMENT '节点结果说明',
`result_snapshot` JSON NULL COMMENT '结构化结果;敏感数据只存引用/摘要',
`create_time` DATETIME(3) NOT NULL COMMENT '创建时间',
`update_time` DATETIME(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_task_revision_node_seq` (`task_id`,`task_revision`,`node_seq`),
UNIQUE KEY `uk_node_idempotency` (`idempotency_key`),
KEY `idx_task_node_pick` (`task_id`,`status`,`node_seq`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='机器人任务有序节点实例表';
| 字段 | 代表含义 | 关键约束 |
|---|---|---|
| task_revision / node_seq | 某次任务修订内的步骤顺序 | 同修订从 1 连续递增;旧修订尾部只取消不覆盖,不在已越过的位置插入。 |
| action_code | 基础能力编码 | 必须在能力注册表存在且机器人声明支持。 |
| target_snapshot | 目标点和途经点快照 | 实际执行使用快照;原点位变化不改变在途任务。 |
| input_snapshot | 动作参数 | 已经按 schema 过滤;禁止任意 function 透传。 |
| status | 节点状态 | 运行时同一 task 只能一行 RUNNING,由应用事务和守卫共同保证。 |
| attempt_no / max_attempts | 尝试次数 | 开仓等不可安全重复副作用默认不自动重试,先对账。 |
| idempotency_key | 副作用唯一键 | 服务重发时不变;边缘端需维护近期已执行键结果缓存。 |
| result_snapshot | 输出结果 | 核验、自检、互动结果结构化保存;敏感明细放专属业务表。 |
表 5-4 h_robot_task_delivery_item:送药投递快照表
用途:冻结某次送药所需的箱格、药包、患者住院身份、床位和目标点。后续患者转床、格子清空或点位编辑不会篡改历史。
CREATE TABLE `h_robot_task_delivery_item` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '投递明细主键',
`task_id` BIGINT NOT NULL COMMENT '送药任务ID',
`task_revision` INT NOT NULL COMMENT '投递明细所属任务修订号',
`item_seq` INT NOT NULL COMMENT '任务内投递顺序,从1开始',
`delivery_node_seq` INT NOT NULL COMMENT '所属投递段起始节点序号',
`box_id` BIGINT NOT NULL COMMENT '药箱ID快照来源→h_med_box.id',
`box_code` VARCHAR(32) NOT NULL COMMENT '药箱硬件编码快照',
`slot_id` BIGINT NOT NULL COMMENT '格子行ID快照来源→h_med_box_slot.id',
`slot_no` INT NOT NULL COMMENT '物理格号快照,下发开格使用',
`pack_id` BIGINT NOT NULL COMMENT '药包ID快照来源→h_medicine_pack.id',
`pack_snapshot` JSON NOT NULL COMMENT '医嘱编码、药品、剂量等最小必要快照',
`admission_id` BIGINT NOT NULL COMMENT '住院流水ID快照来源',
`patient_id` BIGINT NOT NULL COMMENT '患者ID快照来源',
`patient_snapshot` JSON NOT NULL COMMENT '姓名/住院号/腕带核验摘要等快照',
`bed_id` BIGINT NOT NULL COMMENT '床位ID快照来源',
`room_id` BIGINT DEFAULT NULL COMMENT '房间ID快照来源',
`map_point_id` BIGINT NOT NULL COMMENT '床旁目标点ID快照来源',
`destination_snapshot` JSON NOT NULL COMMENT '区域/地图/房间/床位/点位及坐标快照',
`verify_mode` VARCHAR(24) NOT NULL COMMENT 'WRISTBAND_QR/MANUAL/OTHER',
`status` VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/VERIFYING/OPENED/DELIVERED/FAILED/RETURNED/CANCELED',
`verified_at` DATETIME(3) DEFAULT NULL COMMENT '身份核验成功时间',
`delivered_at` DATETIME(3) DEFAULT NULL COMMENT '确认取药完成时间',
`failure_code` VARCHAR(64) DEFAULT NULL COMMENT '本投递项失败码',
`active_pack_key` BIGINT GENERATED ALWAYS AS ((CASE WHEN `status` IN ('PENDING','VERIFYING','OPENED') THEN `pack_id` ELSE NULL END)) STORED COMMENT '未终结投递的药包互斥键',
`active_slot_key` BIGINT GENERATED ALWAYS AS ((CASE WHEN `status` IN ('PENDING','VERIFYING','OPENED') THEN `slot_id` ELSE NULL END)) STORED COMMENT '未终结投递的格子互斥键',
`create_time` DATETIME(3) NOT NULL COMMENT '创建时间',
`update_time` DATETIME(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_task_revision_item_seq` (`task_id`,`task_revision`,`item_seq`),
UNIQUE KEY `uk_task_revision_pack` (`task_id`,`task_revision`,`pack_id`),
UNIQUE KEY `uk_delivery_active_pack` (`active_pack_key`),
UNIQUE KEY `uk_delivery_active_slot` (`active_slot_key`),
KEY `idx_slot_active` (`slot_id`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='送药任务投递对象不可变快照';
| 字段组 | 用途 | 为什么必须快照 |
|---|---|---|
| box / slot | 知道开哪台箱的哪个物理格 | 格子当前态会在取药后清空。 |
| pack | 关联药包和医嘱 | 药包状态会从待派送变为配送中、已送达或失败。 |
| patient / admission | 核验本次住院身份 | 患者姓名、住院流水和腕带属于本次交付证据。 |
| destination | 记录实际采用的床位、点位和坐标 | 患者可能转床,地图点位也可能后续重标。 |
| status / timestamps | 每格独立结果 | 支持一项失败、其他项成功以及精准补偿。 |
| active_*_key | 未终结药包和格子的数据库互斥 | MySQL 5.7 生成列配合唯一键,阻止并发请求把同一药包或格子占入两个活动任务。 |
表 5-5 h_robot_task_command:下行命令账本
用途:记录节点产生的每一次 4000 命令、发送尝试、5000 受理回执和最终关联事件。它解决进程重启后 pending Map 丢失的问题。
CREATE TABLE `h_robot_task_command` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '命令主键',
`command_id` VARCHAR(64) NOT NULL COMMENT '稳定命令ID,重发保持不变',
`task_id` BIGINT NOT NULL COMMENT '所属任务ID',
`task_revision` INT NOT NULL COMMENT '命令创建时的任务修订号',
`node_id` BIGINT NOT NULL COMMENT '所属节点ID',
`robot_id` BIGINT NOT NULL COMMENT '目标机器人ID',
`lease_epoch` BIGINT NOT NULL COMMENT '下发时的机器人队列围栏代次',
`command_seq` INT NOT NULL COMMENT '节点内命令顺序',
`function_name` VARCHAR(64) NOT NULL COMMENT 'CR101 function白名单名称',
`payload` JSON NOT NULL COMMENT '实际下发参数快照,含idempotency_key',
`status` VARCHAR(20) NOT NULL COMMENT 'CREATED/SENDING/ACCEPTED/REJECTED/TIMEOUT/CONFIRMED/UNKNOWN',
`attempt_no` INT NOT NULL DEFAULT 0 COMMENT '发送尝试次数',
`message_id` VARCHAR(64) DEFAULT NULL COMMENT '最近一次4000/5000关联ID',
`response_code` INT DEFAULT NULL COMMENT '5000受理回执code',
`response_payload` JSON NULL COMMENT '5000回执内容',
`sent_at` DATETIME(3) DEFAULT NULL COMMENT '最近发送时间',
`accepted_at` DATETIME(3) DEFAULT NULL COMMENT '设备受理时间',
`confirmed_at` DATETIME(3) DEFAULT NULL COMMENT '事实事件确认完成时间',
`last_error` VARCHAR(512) DEFAULT NULL COMMENT '最近发送错误',
`create_time` DATETIME(3) NOT NULL COMMENT '创建时间',
`update_time` DATETIME(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_command_id` (`command_id`),
UNIQUE KEY `uk_node_command_seq` (`node_id`,`command_seq`),
KEY `idx_command_recovery` (`robot_id`,`status`,`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='机器人下行命令及受理回执账本';
表 5-6 h_robot_task_event:设备事实事件 Inbox
用途:先持久化、后 ACK、再投影任务状态。相同设备重传相同 message_id 时命中唯一键,只补发 ACK,不重复推进节点。
CREATE TABLE `h_robot_task_event` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '事件账本主键',
`robot_id` BIGINT NOT NULL COMMENT '上报机器人ID',
`device_code` VARCHAR(64) NOT NULL COMMENT '上报连接设备编码快照',
`message_id` VARCHAR(64) NOT NULL COMMENT '6000帧messageId;设备内幂等键',
`event_name` VARCHAR(64) NOT NULL COMMENT '事件名,如 nav_status/node_finished',
`task_id` BIGINT DEFAULT NULL COMMENT '解析并验证后的任务ID',
`task_revision` INT DEFAULT NULL COMMENT '事件声明的任务修订号;任务事件必须有值',
`node_id` BIGINT DEFAULT NULL COMMENT '解析并验证后的节点ID',
`lease_epoch` BIGINT NOT NULL COMMENT '设备执行该节点时接受的队列围栏代次',
`event_seq` BIGINT NOT NULL COMMENT '设备会话内单调序号,供乱序检测',
`device_session_id` VARCHAR(64) NOT NULL COMMENT '设备本次注册会话ID',
`device_time` DATETIME(3) DEFAULT NULL COMMENT '设备产生事件时间',
`received_at` DATETIME(3) NOT NULL COMMENT '服务端接收时间',
`payload` JSON NOT NULL COMMENT '原始事件数据,审计与重放使用',
`process_status` VARCHAR(20) NOT NULL DEFAULT 'RECEIVED' COMMENT 'RECEIVED/APPLIED/IGNORED/REJECTED/ERROR',
`process_note` VARCHAR(512) DEFAULT NULL COMMENT '忽略、拒绝或处理错误说明',
`applied_at` DATETIME(3) DEFAULT NULL COMMENT '成功投影时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_device_message` (`device_code`,`message_id`),
UNIQUE KEY `uk_device_event_seq` (`device_code`,`device_session_id`,`event_seq`),
KEY `idx_task_event` (`task_id`,`id`),
KEY `idx_node_event` (`node_id`,`id`),
KEY `idx_event_replay` (`process_status`,`received_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='机器人任务事实事件Inbox与审计账本';
表 5-7 h_robot_task_outbox:事务后可靠发送表
用途:任务事务只负责写“应该发送什么”,独立发送器在提交后发送。服务进程崩溃不会造成“数据库已提交但命令永远没发”。
CREATE TABLE `h_robot_task_outbox` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'Outbox主键',
`aggregate_type` VARCHAR(24) NOT NULL COMMENT 'TASK/NODE/COMMAND/EVENT',
`aggregate_id` BIGINT NOT NULL COMMENT '关联聚合主键',
`outbox_type` VARCHAR(32) NOT NULL COMMENT 'DEVICE_COMMAND/FRONTEND_PUSH/SCHEDULER_WAKEUP',
`dedup_key` VARCHAR(96) NOT NULL COMMENT '发送幂等键',
`payload` JSON NOT NULL COMMENT '待发送内容',
`status` VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/SENDING/SENT/FAILED/DEAD',
`retry_count` INT NOT NULL DEFAULT 0 COMMENT '已重试次数',
`next_retry_at` DATETIME(3) DEFAULT NULL COMMENT '下一次可重试时间',
`locked_by` VARCHAR(64) DEFAULT NULL COMMENT '发送实例租约持有者',
`locked_until` DATETIME(3) DEFAULT NULL COMMENT '发送租约到期时间',
`sent_at` DATETIME(3) DEFAULT NULL COMMENT '成功交付时间',
`last_error` VARCHAR(512) DEFAULT NULL COMMENT '最近错误',
`create_time` DATETIME(3) NOT NULL COMMENT '创建时间',
`update_time` DATETIME(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_outbox_dedup` (`dedup_key`),
KEY `idx_outbox_pick` (`status`,`next_retry_at`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务事务Outbox可靠发送表';
表 5-8 h_robot_task_template:组合任务模板版本表
用途:保存经过审核的组合任务定义。template_code + version 唯一;发布版本不可覆盖,只能新增版本。
CREATE TABLE `h_robot_task_template` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '模板版本主键',
`template_code` VARCHAR(64) NOT NULL COMMENT '稳定模板编码,如 MEDICINE_DELIVERY',
`version` INT NOT NULL COMMENT '模板版本,从1递增',
`name` VARCHAR(128) NOT NULL COMMENT '模板名称',
`task_type` VARCHAR(32) NOT NULL COMMENT '生成的组合任务类型',
`definition` JSON NOT NULL COMMENT '按node_seq排列的节点定义与参数schema引用',
`status` VARCHAR(16) NOT NULL COMMENT 'DRAFT/PUBLISHED/RETIRED',
`checksum` VARCHAR(64) NOT NULL COMMENT 'definition规范化后的SHA-256',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人',
`create_time` DATETIME(3) NOT NULL COMMENT '创建时间',
`published_at` DATETIME(3) DEFAULT NULL COMMENT '发布时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_template_version` (`template_code`,`version`),
KEY `idx_template_status` (`template_code`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='机器人组合任务模板版本表';
5.4 数据保留与归档
- 任务主表和节点表长期在线保留;大体量事件按月分区或按时间归档到历史表,但不物理丢失。
- 任务列表只查询投影列,不扫描 JSON;详情页再按 task_id 拉节点、投递项和事件。
- JSON 只保存不可频繁检索的快照,不把状态、排序、关联主键塞进 JSON。
- 患者敏感信息只保存完成任务所需最小集,日志和错误文本禁止打印腕带原文、身份证和完整答案。
6. 送药任务完整数据链
6.1 多患者、多格子的节点展开
一期以稳定性优先:按后台确认的投递顺序生成“患者投递段”。每段内部固定为导航、问候、核验、开格、提示、取药确认、关格;最后只有一个 RETURN_HOME。多个格子属于同一患者且同一目的地点时,可在同一投递段内依次开格,但每格仍有独立 DeliveryItem。
6.2 送达与清格事务
创建送药任务时,锁定箱体、格子和药包当前行,写入 DeliveryItem 活动互斥键,并把 h_medicine_pack.status 从待派送转为配送中。现有 h_medicine_pack.task_id 仍属于旧任务表,迁移评审前不得写入新版任务 ID;新关联只查询 DeliveryItem。
- 患者身份核验成功事件落库,DeliveryItem 进入 VERIFYING/可开格状态。
- 开格命令使用
box_code + slot_no + idempotency_key;5000 只记 ACCEPTED。 - 边缘端上报门已开、患者确认取药、门已关三个事实;缺任一事实均不判定已送达。
- 在同一数据库事务中把 DeliveryItem 标记 DELIVERED、更新药包状态、清空格子当前占用、补齐 fill_log.emptied_at,并写事件/Outbox。
- 若只完成部分格子,任务可终态为 PARTIAL_SUCCESS;失败格创建引用原任务的补偿任务,不重写原记录。
7. 状态机与运行中控制
7.1 任务状态机
7.2 暂停、继续、返航、取消、变更
| 操作 | 允许条件 | 执行语义 | 不能做的事 |
|---|---|---|---|
| 暂停 | RUNNING 且当前节点声明可暂停 | 先写 PAUSE_REQUESTED,再命令边缘安全停稳,事实确认后 PAUSED | 不能收到 HTTP 请求就直接写 PAUSED |
| 继续 | PAUSED、上下文仍有效、地图/设备/箱格复核通过 | 从当前节点幂等恢复;无法恢复则重新执行该节点 | 不能跳到下一个节点假装当前节点完成 |
| 返航 | RUNNING 或 PAUSED,操作员二次确认 | 当前节点安全收口,任务转 RETURNING,导航到本任务 map_id 的 HP | 不是把 RETURN_HOME 插入原队列队尾 |
| 取消 | QUEUED 可直接取消;运行中需安全停止 | 排队任务终态 CANCELED;运行任务先停止再确认终态 | 不删除任务,不释放未核实的药包占用 |
| 任务变更 | QUEUED 可编辑;RUNNING 必须先安全暂停为 PAUSED,且只改同机器人/area/map 下尚未执行的白名单字段 | 旧修订尾部标记 CANCELED,递增 revision 后插入新 Node/DeliveryItem,写 TASK_REVISED 事件 | 已完成证据永不改写;机器人、area_id 或 map_id 变化必须取消后重新入队 |
7.3 节点失败策略
- 可安全重试:语音播放、表情、只读自检、URL 打开失败,可按有限次数指数退避。
- 先对账再重试:导航命令超时、开格命令超时、互动提交超时。先查询/等待设备当前事实,不能盲目补发。
- 禁止自动重试:可能导致重复开仓、重复提交评测、重复清空格子的副作用操作。
- 允许人工跳过:仅模板明确标为可选节点;身份核验和关格等安全节点不可跳过。
8. WebSocket、同步异步与一致性
8.1 现有通道保持不变
| 通道 | 现有编号 | 新版用途 |
|---|---|---|
| 服务端 → 设备 | 4000 Function Call | 下发单节点命令,携带 command_id、task_no、node_seq、idempotency_key |
| 设备 → 服务端 | 5000 Function Response | 仅表示受理/拒绝,不作为节点最终成功 |
| 设备 → 服务端 | 6000 Event | 节点开始、进度、到达、核验、开关格、完成、失败等事实 |
| 服务端 → 设备 | 6001 Event ACK | 事件已被持久化或已幂等存在后回复 |
| 设备 → 服务端 | 7000 Property | 电量、底盘、药箱、机器人状态的实时快照 |
| 设备生命周期 | 8001~8004 | 注册鉴权和心跳,不承载业务任务 |
| 服务端 → 操作端 | 1010~1024 + 新任务消息 | 保留现有设备状态;建议新增独立 1030~1034 任务投影编号 |
兼容原则:不改变 4000/5000/6000/6001 的外层信封。新增的是 data 字段、任务事件种类和前端任务投影编号。lease_epoch 是队列租约围栏:边缘端记录已接受的最大代次并拒绝更旧命令,服务端投影事件时也必须匹配当前代次。协议变更需服务端与边缘端契约测试同时通过。
8.2 推荐命令信封
{
"msgType": 4000,
"messageId": "本次发送唯一UUID",
"deviceId": "CR101-01",
"function": "go_target_point",
"params": {
"command_id": "CMD-20260915-000081",
"idempotency_key": "TASK-810:NODE-3:MOVE_A2B:v1",
"lease_epoch": 12,
"task_no": "RT202609150081",
"task_revision": 1,
"node_seq": 3,
"map_id": 5,
"point_id": "32",
"goal_pose": { "...": "由h_map_point.coordinate快照转换" }
}
}
8.3 推荐事实事件
{
"msgType": 6000,
"messageId": "设备生成并在重传时保持不变",
"deviceId": "CR101-01",
"event": "task_node_status",
"data": {
"device_session_id": "注册会话ID",
"event_seq": 1842,
"lease_epoch": 12,
"task_no": "RT202609150081",
"task_revision": 1,
"node_seq": 3,
"command_id": "CMD-20260915-000081",
"status": "SUCCEEDED",
"progress": 100,
"result_code": "ARRIVED",
"device_time": "2026-09-15T10:22:31.442+08:00"
}
}
8.4 同步与异步边界
| 动作 | 同步部分 | 异步部分 | 客户端看到什么 |
|---|---|---|---|
| 创建任务 | 校验和数据库入队事务 | 调度、WS 下发和执行 | HTTP 202 + task_no + queue_seq |
| 节点下发 | 4000 与 5000 受理关联,最多短时等待 | 真正执行和结果事件 | 先 DISPATCHING,再 RUNNING/终态 |
| 暂停/返航 | 校验并记录操作意图 | 机器人安全停止或到达 HP | 先 REQUESTED,不假装已完成 |
| 进度查询 | REST 读取数据库投影 | WebSocket 推送变化 | 刷新不丢状态,在线时实时更新 |
8.5 ACK 顺序必须调整
当前服务端 6000 事件处理是先 ACK、后业务处理,新任务域必须改为:校验连接身份 → 插入 Event Inbox(或命中唯一键)→ 提交 → 回复 6001 → 异步投影。否则服务端在 ACK 后、落库前崩溃会永久丢事件。
8.6 hb_edge 最小执行账本
新增,不是现状:在边缘端使用 Python 标准库 SQLite 建立有界 edge_command_journal。它只负责设备侧幂等和重启恢复,不保存 FIFO 队列,不取代服务端 MySQL。
| 字段 | 用途 | 安全规则 |
|---|---|---|
| idempotency_key(PK)/ command_id | 识别服务端重发的同一副作用命令 | 命中 SUCCEEDED/FAILED 时返回既有结果,不再次驱动硬件 |
| lease_epoch / task_no / task_revision / node_seq | 绑定当前队列所有权和任务上下文 | 小于设备已接受最大 epoch 的命令直接拒绝 |
| action_code / input_digest | 确认相同幂等键没有携带不同参数 | 键相同但摘要不同视为协议冲突并告警 |
| state / result / updated_at | RECEIVED/RUNNING/SUCCEEDED/FAILED/UNKNOWN 及结果摘要 | 驱动硬件前先事务写 RECEIVED;断电留下 RUNNING 时先查硬件事实,不盲目重做 |
账本按容量和时间双边界清理,但未收到服务端完成确认、仍属活动任务或涉及开/关格的记录不得淘汰。SQLite 文件采用最小权限,患者身份原文和互动答案不得写入。
9. 电量告警、安全阻断与返航
低电量不作为普通“高优先级任务”插队,因为那会破坏已确定的 FIFO 和药物交付安全。它是设备级安全状态,控制调度门禁。
9.1 当前任务如何结算
- 尚未开格:当前任务标记 BLOCKED/RETURNING,保留未完成 DeliveryItem,返航后由人工决定恢复或生成补偿任务。
- 格门已开:先完成关格和药品状态对账,再返航;任何情况下不能带着未知门态直接移动。
- 正在展示内容:安全结束会话并保存阶段性结果,再返航;不把中断评测误标为完成。
- 无法返航:任务 FAILED,设备进入故障态并告警;后续队列不自动推进。
10. 操作端交互与后台界面原型
FIFO 任务队列
当前节点与事件
任务类型
● 送药任务
○ 移动 A2B
○ 宣教任务
○ 心理评测
○ 设备自检
本次送药摘要
机器人:CR101-01
区域:心脏大血管外科一病区(82)
地图:东区病房图(5)
药箱:BOX-001 · 15格
已选:3个药包 / 2位患者
提交前硬校验
请扫描患者腕带
仅显示必要信息:患者姓名脱敏、床号、待取格数。
核验通过前,开格按钮不可用
发车校验
✓ 箱体在位且锁定
✓ 所有占用格门已关闭
✓ 药包、患者、床位已解析
✓ 机器人、区域和地图已确认
10.1 后台操作完整链
| 用户动作 | 界面即时反馈 | 后台事实变化 | 成功/失败展示 |
|---|---|---|---|
| 点击新建任务 | 选择机器人后锁定其 area;只展示该 area 的地图和业务对象 | 无写入,直到保存草稿或提交 | 缺地图、HP、能力或权限立即阻止下一步 |
| 选择送药 | 列出目标机器人所挂药箱及已装药格 | 只读现有 h_* 表 | 异常格显示原因,不允许勾选 |
| 确认投递顺序 | 显示患者段和将生成的 node_seq | 仍未入队 | 跨 area/map 或无床旁点时标红阻止 |
| 提交 | 返回 task_no、queue_seq、前方任务数 | 一个事务创建任务、节点、快照、事件、Outbox | 成功显示 QUEUED;事务失败不产生残缺任务 |
| 机器人执行 | 任务、节点、每格结果和原始事件时间线实时更新 | 6000 事件驱动投影 | 离线不清空页面,显示“等待恢复/需对账” |
| 中途召回 | 二次确认未送达对象和影响 | 记录 RETURN_REQUESTED,安全收口后 RETURNING | 到 HP 后显示“已召回,N项未完成” |
| 全部完成 | 100%,显示每位患者/每格结果 | 任务 SUCCEEDED,原子清空 QueueCursor 活动指针并唤醒调度 | 下一任务自动进入 DISPATCHING |
11. API 与实时消息建议
11.1 REST
| 方法 | 路径 | 用途 | 返回要点 |
|---|---|---|---|
| POST | /api/robot-tasks/validate | 零写入预校验和节点预览 | errors、warnings、compiledNodes |
| POST | /api/robot-tasks | 幂等创建并加入队尾 | 202、taskNo、queueSeq、aheadCount |
| GET | /api/robots/:id/task-queue | 设备当前队列 | 运行项 + 等待项 + 阻断原因 |
| GET | /api/robot-tasks/:id | 任务详情投影 | Task、Nodes、DeliveryItems、controls |
| GET | /api/robot-tasks/:id/events | 分页事件审计 | 按 event id 顺序,不用设备时间排序推进 |
| POST | /api/robot-tasks/:id/actions | 暂停/继续/召回/取消/重试节点 | 202、operationId、requestedState |
| PATCH | /api/robot-tasks/:id | 受控修改 QUEUED/PAUSED 任务 | 新 revision、旧尾部取消、新编译尾部 |
| POST | /api/robot-task-templates/:code/publish | 发布不可变模板版本 | version、checksum |
11.2 前端实时消息
| 建议编号 | 名称 | 最小内容 |
|---|---|---|
| 1030 | TASK_QUEUE_CHANGED | robotId、队列摘要、变更 taskNo |
| 1031 | TASK_STATUS_CHANGED | taskNo、status、progress、currentNodeSeq、version |
| 1032 | TASK_NODE_CHANGED | taskNo、nodeSeq、actionCode、status、progress |
| 1033 | DELIVERY_ITEM_CHANGED | taskNo、itemSeq、slotNo、status、resultCode |
| 1034 | TASK_OPERATION_CHANGED | operationId、action、requested/result、message |
编号需在正式协议评审中锁定。任务详情仍以 REST 数据库投影为基准,WS 消息只通知变化;断线重连后重新拉详情即可收敛。
12. 恢复、对账、安全与可观测
12.1 服务重启后的恢复算法
- 扫描
h_robot_task_queue中租约过期且active_task_id非空的游标,再关联 DISPATCHING/RUNNING/PAUSE_REQUESTED/RETURNING 任务。 - 若机器人离线:保持原状态,标记 WAITING_DEVICE,不推进队列。
- 新实例取得游标租约并递增
lease_epoch;机器人重连后要求上报device_session_id + current task_no/node_seq/command_id/state/lease_epoch。 - 数据库与机器人一致:续租并继续等待事件;机器人报告已完成:补录事实并推进;状态不一致:游标和任务进入 BLOCKED,等待人工处置。
- 对账完成前绝不自动重发有副作用命令,也不领取下一任务。
12.2 关键异常矩阵
| 异常 | 系统反应 | 队列反应 | 人工可见信息 |
|---|---|---|---|
| 4000 发送失败 | Command 保持 CREATED/UNKNOWN,Outbox 重试 | 队头不释放 | 发送次数、最后错误、下次重试 |
| 5000 超时 | 先查边缘事实,不能直接判失败 | 当前节点保持 DISPATCHING | “受理状态未知,正在对账” |
| 6000 重复 | 唯一键命中,回复 ACK,不重复投影 | 无变化 | 事件标记 duplicate/ignored |
| 6000 乱序 | 按任务 revision、node_seq、合法迁移校验 | 禁止状态回退 | 原事件保留并标记 IGNORED |
| 机器人断线 | 进入 grace,超时后 OFFLINE;任务不自动失败 | 冻结该设备队列 | 断线时间、当前节点、最后事实 |
| 服务进程崩溃 | 数据库租约过期后新实例对账接管 | 不依赖内存 Map 恢复 | 恢复批次和对账结论 |
| 患者已转床 | 排队任务阻断并提示修订;在途任务按策略人工确认 | 不静默更换目的地 | 原快照与当前数据差异 |
| 格门状态未知 | 禁止移动和再次开格,进入 BLOCKED | 后续任务不推进 | 箱体、格号、最后门态时间 |
12.3 权限与审计
- 创建、暂停、继续、召回、取消、跳过、补偿分别配置权限;急停保持独立最高安全通道。
- 服务端从登录身份计算可访问 area/device,忽略客户端自报角色和 area。
- 所有人工操作写操作审计:操作者、原因、旧状态、新状态、请求 ID、来源 IP。
- 展示 URL 采用 HTTPS、域名白名单、内容版本和校验值;禁止任务参数注入任意脚本地址。
- 命令、事件和日志中的患者信息最小化;后台按区域和岗位做数据范围授权。
- 腕带原文、身份凭据和互动答案不进入通用任务 JSON;只保存业务表引用、不可逆摘要或专属加密存储引用,数据库备份同样加密并审计访问。
12.4 最小监控指标
每机器人排队数、队头等待时长、任务/节点耗时、BLOCKED 数。
在线率、5000 延迟、6000 重传率、Outbox 堆积、未匹配回执。
送达率、身份核验失败、格门异常、部分成功、补偿闭环时长。
13. 分阶段落地与验收
| 阶段 | 范围 | 上线门禁 | 回滚方式 |
|---|---|---|---|
| P0 契约固化 | 状态机、能力码、WS data、错误码、地图多图策略 | 三端契约测试和真机能力盘点通过 | 纯文档/测试,无生产行为变化 |
| P1 数据与队列 | 新表、模板编译、FIFO、Outbox/Event Inbox、只读页面 | 并发入队无重复 seq;重启可恢复;旧任务仍只读 | 关闭新系统开关,新表保留审计 |
| P2 A2B 与自检 | 先接无药物副作用任务,验证节点执行器和边缘 SQLite 执行账本 | 1000 次 Mock 队列、断线、重启、乱序测试;真机封闭区验证 | 停止领取新任务,安全结束当前节点 |
| P3 送药闭环 | 药箱/格子快照、身份核验、开关格、每格结果 | 真机门态、幂等、患者转床、部分失败、召回全覆盖 | 入口关闭,已在途任务人工收口 |
| P4 宣教/评测 | Pad URL、语音互动、内容结果和隐私 | 域名白名单、断网恢复、会话取消、结果幂等 | 模板退役,不影响送药与 A2B |
| P5 多实例/多机 | 外部连接路由、共享租约、设备选择器 | 故障切换和双实例竞争测试 | 保持单活部署,不改变数据模型 |
13.1 必测场景
- 20 个并发创建请求仍为同机器人生成唯一、递增、无空洞要求之外的 queue_seq。
- 运行任务期间新增任务只进入队尾,不触发任何 stop/cancel 命令。
- 服务端在“事务提交后、WS 发送前”崩溃,Outbox 恢复后仍能发送。
- 服务端在“事件落库后、6001 ACK 前”崩溃,重传事件只应用一次。
- 开格 5000 超时但物理门已开时,系统通过对账收敛,不重复开格。
- 任务运行中机器人断线、重连、服务端重启,队列顺序和当前节点不丢失。
- 患者转床、地图归档、点位删除、药箱更换、药包被清空均能在正确阶段被阻断。
- 低电量去抖、返航、无法返航、门态未知四种路径均不造成下一任务误启动。
- 身份核验失败时任何开格 function 都不会产生。
- 任务终态后迟到/重复事件不能把状态从终态回退。
- 任务修订后收到旧 revision 的迟到事件,只记 IGNORED,不能推进新节点或打开新格子。
- 边缘端在副作用命令 RECEIVED/RUNNING 后断电,重启必须先对账,不能重复开格或重复提交。
13.2 建议开发顺序
数据库迁移与领域状态机 → 纯 Mock 调度器 → A2B 基础节点 → Event Inbox/Outbox → 后台驾驶舱 → 真机任务能力验收 → 送药节点 → 机器人/药站 Pad → 宣教与评测。
start_task/stop_task 当前仍是受理即拒桩。在决定使用“大任务下发”之前必须完成边缘端装配、ROS2 Action Server 联调、取消语义和恢复测试。若未通过,一期应由云端 Node Executor 使用已经接通的细粒度 function 逐节点编排。13.3 最终不动点
QueueCursor / Task / Node / DeliveryItem / Command / Event / Outbox 七类运行对象,Template 作为配置对象;queue_seq + node_seq 两级顺序;单机器人 FIFO 不抢占;area_id 必填、移动任务 map_id 冻结;WebSocket 受理与事实完成分离;任务和事件长期留存。未来扩展多机时只替换“选择哪台机器人”和“如何取得设备租约”,不推翻任务模型。