本节摘要:执行监控照亮"发出与完成之间"的黑暗地带。本节定义执行状态机(已下发、执行中、校验中、完成、失败),讲过程监控的三道盯守——进度、偏差阈值、期限看门狗,兑现 3.1 节验收谓词的完成判定契约,并给出视觉伺服一类的在线反馈校正——偏差在放大之前被拉回,而不是事后追溯。
指令发出去了,然后呢?如果答案是"等回执",那这段等待就是监控盲区:夹爪在半途卡住、导航在绕弯、下单请求被对端悄悄丢弃——偏差在黑暗里安静地累积,直到某刻集中爆发。上一节把单条指令做得可靠,本节把整段执行做得可见:执行走到哪一步、偏差有没有越线、期限还剩多久、做没做成由谁判定。这四问都有明确的机制答案,合起来就是执行监控。
每个动作从下达到销账,走固定的状态机:已下发(接口受理、唯一号登记)、执行中(伺服层推进、进度可查)、校验中(动作报完成,验收谓词正在复核)、完成(谓词转真、回执入状态板)或失败(偏差越限、超时、被中止)。状态机不是形式主义——它是监控的挂钩点:每条盯守规则都挂在特定状态的转移边上,看门狗挂在"执行中"超期,验收谓词挂在"校验中",降级触发挂在"失败"。没有状态机的执行是一团日志,有状态机的执行才可断言、可告警、可审计。
状态机纪律里最重要的一条:"执行中"到"完成"必须经过"校验中"。执行器自报完成只算候选,验收谓词(读状态板信念、按 3.1 节任务单的判据复核)说了才算。这条纪律终结了"假完成"的温床——回执说到位而货物还悬着的尴尬,在复核这道闸前现形。
盯进度:执行器回报进度比例或阶段号,长时间零进度即告警——卡死不必等超时才发现。盯偏差:执行过程中的实时偏差(位姿误差、力误差)对照阈值带,越限即预警,连续越限即触发校正或中止——阈值带分黄红两档,黄档触发在线校正,红档直接上抛 4.3 节复飞。盯期限:每步动作的期限看门狗独立于执行器计时——执行器自己挂了,看门狗照样敲钟。三道盯守的产出全部写进状态板与航行日志,监控不是后台私语,是全机可见的仪表。
from enum import Enum import time class Phase(Enum): ISSUED = "已下发" RUNNING = "执行中" VERIFYING = "校验中" DONE = "完成" FAILED = "失败" class ExecutionMonitor: """单步执行的监控器:状态机 + 三道盯守。""" def __init__(self, step, board, executor): self.step = step # 含 accept 验收谓词与 params self.board = board # 单一事实源 self.executor = executor self.phase = Phase.ISSUED self.t0 = time.monotonic() self.warned = False def tick(self, belief): if self.phase is Phase.ISSUED: self.executor.submit(self.step) self.phase = Phase.RUNNING elif self.phase is Phase.RUNNING: prog = self.executor.progress() err = self.executor.deviation(belief) if time.monotonic() - self.t0 > self.step.timeout_s: return self.fail("看门狗超时") if err > RED_BAND: # 红档:上抛复飞 return self.fail(f"偏差越红档:{err:.3f}") if err > YELLOW_BAND and not self.warned: self.warned = True self.executor.nudge(belief) # 黄档:在线校正 if prog == self._last_prog: self.stall += 1 if self.stall > STALL_LIMIT: return self.fail("进度停滞") self._last_prog = prog if self.executor.self_reported_done(): self.phase = Phase.VERIFYING # 自报完成只算候选 elif self.phase is Phase.VERIFYING: if self.step.accept(belief): # 验收谓词说了才算 self.phase = Phase.DONE self.board.write("behavior", f"done:{self.step.action}", writer="executor") else: return self.fail("验收谓词未转真") return self.phase def fail(self, reason): self.phase = Phase.FAILED self.board.write("failure", reason, writer="monitor") return self.phase
反馈校正藏在黄档逻辑里:偏差越黄档时不等失败,先用小步修正指令在线拉回。视觉伺服是它的典型形态——执行抓取时相机持续回报目标偏移,修正指令跟着偏移走,偏差被压在萌芽状态。校正的纪律是小步与限次:每次修正量设上限、整个动作的校正次数设上限,超限升级为失败交给复飞——校正的职责是救小偏差,不是拖大偏差。
阈值带与校正器的配置同样长在代码里:
YELLOW_BAND, RED_BAND = 0.05, 0.12 # 黄档校正,红档上抛 STALL_LIMIT, MAX_NUDGES = 5, 3 # 停滞拍数与校正次数上限 class Nudger: """在线校正器:小步限幅限次,救小偏差不拖大偏差。""" def __init__(self, executor): self.executor = executor self.nudges = 0 def correct(self, belief, err): if self.nudges >= MAX_NUDGES: return False # 校正次数用尽,升级失败 step = clamp(err * 0.5, max=STEP_LIMIT) # 小步限幅 self.executor.nudge(step) self.nudges += 1 return True

监控的联调主打剧情注入:在台架上按剧本制造偏差——渐进漂移看黄档校正是否拉回、阶跃越限看红档是否果断上抛、静默卡死看看门狗与停滞检测谁先响、执行器谎报完成看验收谓词是否拦住。每种剧情都要在日志里留下正确的状态机轨迹,轨迹与剧本对不上就是监控自身的缺陷。
按症状排查:监控误报(偏差没多大却频繁告警)查阈值带是否忽略了正常波动幅度——阈值要按回执统计的分位数定,不拍脑袋;漏报(事后才发现早就卡死)查盯守频率与数据新鲜度——监控吃的读数过期,等于隔着毛玻璃站岗;校正震荡(修正指令来回拉锯)查校正步长与执行延迟的匹配——步长大于延迟期内的位移就会过冲;状态机卡死(停在"校验中")查验收谓词是否引用了永不更新的字段——谓词也要有自身的超时。
💡 关键直觉:监控的价值不在发现失败,在把失败的发现提前到代价最小的时刻。黄档拉回花的是毫秒与小步,红档中止花的是重试,等到事故现场才知道失败,花的就是停机与复盘。盯守的密度,跟着代价的梯度走。
监控能发现失败,但发现之后怎么办——重试几回、降到什么程度、何时认输进安全态,需要一套排好的阶梯。下一节请出复飞程序与整机鲁棒性设计。