一条已经投入数据采集的光束线,内部至少藏着两套系统。一套负责保护并驱动物理设备,包括 EPICS IOC、控制器逻辑、限位开关、快门、联锁、硬件触发器和工程界面。另一套描述实验本身:哪些电机需要定位,哪个探测器需要触发,应记录哪些元数据,扫描如何重复,中断后又该怎样处理。
Bluesky 很适合承担第二套系统的工作。它把实验流程表示为一种称为 plan(方案) 的 Python 生成器,再交由 RunEngine 执行。硬件在方案中呈现为 Python 对象,通常由 Ophyd 建模;数据与元数据以实时流的形式离开 RunEngine,回调程序可据此绘图、转换、存储或转发。[1][3] 这层分工让实验流程可以复用,也便于检查,同时保留底层原有的控制系统。
最后这一点划定了迁移范围。采用 Bluesky 时,起点应放在 EPICS 之上。每个过程变量、已经验证的联锁以及确定性同步职责都留在原有层级,先保留那套熟悉设备保护方式的系统,再逐项把实验流程移入明确的设备模型与方案。
现有设施的实践支持这种收窄范围的做法。BESSY II 保留了异构的 EPICS 控制系统,在这些控制的外层编写 Ophyd 类,把 SPEC 宏转换成 Bluesky 方案,并先用仿真硬件测试,随后让同一套接口连接真实设备。[4] Diamond Light Source 后来将基于 Bluesky 的 Athena 服务部署在既有 GDA 平台旁边,在用户实验中验证新技术栈时,可将选定操作导向任一路径。[5] 两个案例都把“迁移”处理成分阶段推进的过程。
图片背景:封面照片俯瞰 Bluesky 的诞生地 NSLS-II 实验大厅。光束线外罩与各类管线让不同层级清晰可见:编排软件可以迅速变化,它所指挥的物理系统却不能当作随时可替换的部分。[6]
把一项正常运行的扫描固化为约定
起步时应避开最长的宏和最难驾驭的探测器。选择一项风险低、有代表性的步进扫描,其当前行为应同时为光束线科学家和控制工程师所熟悉。先完整记录它做了什么,再着手转换它的书写方式。
基线应包括指令位置与回读值、稳定时间规则、探测器曝光和触发顺序、电机与探测器超时、扫描前后状态、文件名、操作员提示、元数据字段、输出格式,以及停止、中止和硬件故障时的行为。保存一组已知输入及其生成的数据,同时记录产生这些结果的 IOC 数据库、电机控制器、探测器固件、旧宏和分析工具版本。
验收关注实际行为与科学结果,源代码外形无关紧要。新方案需要抵达相同的预期位置,取得科学工作所需的读数,留下足以解释数据的上下文,使设备停留在约定状态,并以操作员能够恢复的方式处理故障。一张小型数值矩阵能让比较保持客观,其中可列出点数、端点容差、最大回读误差、预期曝光次数、丢帧数、时间戳偏差、清理后状态,以及数据集达到可用状态所需的时间。
建立这份约定期间,旧路径应保持可运行。Diamond 的 I22 部署可作为范例:Athena 与既有 GDA 系统并行运行,在测试新编排路径时,请求可以导向其中任一系统。[5] 当回退目标是一套已知可靠的科学流程时,并行运行体现的是明确的恢复安排。
先建立设备行为,再转换宏
如果把一组松散的 EPICS PV 直接暴露给每个方案,Bluesky 部署很快就会难以维护。Ophyd 的职责是把相关信号组合成具有统一名称和行为接口的设备。例如,一个只读的光束位置对象可以收纳两个 PV,同时避免其前缀渗入扫描逻辑:
from ophyd import Component, Device, EpicsSignalRO
class BeamPosition(Device):
x = Component(EpicsSignalRO, "X")
y = Component(EpicsSignalRO, "Y")
这里的后缀仅作示例;生产环境中的类必须反映设施实际使用的记录和命名。语法之外,周围的行为约定才是重点。可移动设备需要明确的完成条件、超时行为、切实有效的 stop() 逻辑、回读、限位和配置信号。探测器则需要进入预备状态、触发、完成确认、数据描述和资产处理,并与真实采集流程一致。
BESSY II 的经验说明了为何这项工作要放在前面。其光束线电机没有采用标准 EPICS Motor Record,同类设备之间的接口也各有差异。团队编写了专用 Ophyd 类,随后运行覆盖每台电机实际移动的集成测试;这些测试发现了常规抽象层测试遗漏的缺陷。[4] 如果设备对象在运动结束前就宣告成功,或向错误的 PV 发送停止指令,再精致的扫描方案也无法补救。
测试分为三层。第一层使用仿真设备检验方案组织方式,完全断开光束线。第二层使用独立 PV 前缀,把设备类连接到模拟器或数字孪生。第三层在控制工程师在场时,让每个类针对真实硬件完成范围受限的移动、独立回读与调试。BESSY II 的 Ophyd 接口从仿真设备切换到真实设备,有时只需更改 EPICS 前缀,因此有限的现场调试时间得以更充分地用于验证。[4]
这层设备依赖也应像其他控制依赖一样审慎锁定版本。当前 Ophyd 文档将 v2 API 标为 provisional(暂定),并指出 v1 与 v2 设备可以共用一个 RunEngine。[2] 渐进式评估由此成为现实,同时也提醒团队避免把光束线切换与一场范围未定的 API 代际重写合在一起。为试点选定 API 代际与版本并做好记录,今后的变更统一通过同一套验收测试。
流程应写成方案,RunEngine 调用留在辅助函数之外
设备对象的行为可信后,便可按照扫描意图转换流程。Bluesky 已提供 count、scan、rel_scan 和 grid_scan 等方案。设施专用流程可以用 yield from 组合这些方案,补充元数据并包裹清理步骤,让 RunEngine 从头到尾掌握执行权。[1]
这个细节关系重大。Bluesky 教程明确警告,不要把 RE(...) 藏在循环或辅助函数中;重复嵌套调用 RunEngine 会让中断与错误处理变得不可预测。辅助函数本身应写成方案,把消息交给唯一的 RunEngine,由它全程管理流程。[1] 同理,清理工作应采用为此设计的方案预处理工具,例如 finalize_wrapper 或目标更具体的 contingency_wrapper。普通 Python finally 块预设硬件命令总能按计划执行,并不适合承担这项职责。[1]
先转换看得见的顺序:配置、进入预备状态、移动、触发、读取、退出预备状态、恢复。随后把旧宏隐含的信息逐项显式记录,包括样品身份、课题编号、探测器设置、校准参照、扫描目的,以及方案和设备配置版本。Bluesky 可以在每次运行前后采集基线读数,也能把数据流发送给多个回调程序,因此每个方案可直接沿用统一的元数据采集与实时显示功能。[1]
BESSY II 报告称,ascan 等常用 SPEC 宏在 Bluesky 中有接近的对应方案,即使专用设置宏也相对容易转换。不过在首次部署中,系统仍将数据导出为 .spec 文件,供 PyMca 使用,从而在采集方式变化期间保留熟悉的分析路径。[4] 这是一种合理的迁移策略:先转移编排工作,同时为下游用户保留兼容通道,直到新的文档流与存储流程经受住实际检验。
设备保护与确定性时序留在 RunEngine 下层
Bluesky 的中断模型很有用,其能力范围也需要准确界定。暂停时,RunEngine 会通知本次运行调用过的设备停止;恢复时,它回退至一个检查点,并按需要重复指令。教程说明,这种停止行为只是一项便利功能,不能替代正规的设备保护;对于随时间演化的实验,暂停后也未必能安全恢复。[1]
因此,快门、碰撞规避、硬行程限位、真空保护、过温跳闸和其他设备保护逻辑,都应继续留在 IOC、PLC、控制器或硬接线层中。即使 Python 进程失去健康状态,这些层级仍能动作。方案可以检查许可条件并拒绝启动,也可以请求进入安全状态;阻止危险状态转换的职责不能只存在于方案中。
时序也适用同一条分界原则。在步进扫描中,“运动完成后触发并读取”就可以成为科学约定。飞行扫描中的探测器帧、编码器、快门和样品环境事件,常常需要共享硬件时钟或触发线。BESSY II 的部署论文指出,把 PV 组合进 Ophyd 设备并不会让各数值同步更新,这项保证必须来自 IOC 下层。当单色器移动速度足够慢、读取时差小到可以忽略时,其连续扫描可以使用 RunEngine 轮询;一旦轮询精度不足,论文明确提出转向硬件触发。[4]
这应列为严格的验收门槛。对于每条连续扫描路径,都要记录触发源、时钟所有者、预期帧数、时间戳权威来源、最大容许偏差、溢出行为,以及探测器或写入器落后时的处理方式。在具有代表性的速度和数据量下,逐帧比对编码器位置。一次运行即使画面流畅,只要无法证明某一帧对应哪个位置,就还没有通过验收。
Diamond 的分阶段部署展示了与之呼应的模式。Athena 服务通过 Bluesky 方案和 Ophyd 设备安排定时采集,专用硬件负责同步触发;后续工作再扩展到更复杂的外部触发情形。[5] Bluesky 表达实验顺序,Python 调度器并未承担时序源的角色。
把文档流视为公共接口
迁移后的系统即使准确移动了每台电机,也仍有丢失科学记录的风险。在用户数据依赖新路径之前,团队需要界定一份完整运行由哪些元数据和数据文档组成,大型探测器资产存放在哪里、如何关联,以及哪个下游程序是持久化存储的权威记录方。
RunEngine 向回调程序发布实时流,供可视化、处理、消息队列、文件和数据库使用。[1] 这种扇出方式有助于隔离故障,但回调程序若与采集运行在同一进程中,仅凭“回调”这个名称并不能隔开两者。把非必要的下游程序迁至经过测试的队列或传输通道之后,电子日志导出器或实时绘图程序发生故障时才不会牵连采集。无论采用何种拓扑,显示绿色状态的图表都不足以证明持久化写入器已经提交本次运行。
测试文档流时应主动制造故障。在扫描期间重启可视化客户端,让一个非必要回调程序抛出错误,在实验环境中填满暂存文件系统,并中断资产元数据与探测器文件之间的连接。随后确认运行的开始与完成状态、唯一标识符、基线读数、配置及外部资产引用仍保持内部一致;若一致性已经破坏,采集应停止并明确标记记录尚未完成。BESSY II 将图像文件与可搜索元数据分开存放,再用已保存的运行标识符通过 Databroker 取回图像;同时,它还把次要下游程序移出关键测量路径。[4]
逐条路径以证据推进
一套实际可用的发布流程分为四个阶段:
- 盘点: 一份扫描约定,每个设备类各有一名负责人,锁定依赖,并完成旧系统回退测试。
- 仿真: 检查方案,在数字孪生中执行,注入超时,测试中断,并在无光束条件下验证发出的元数据。
- 调试: 在受保护的机器时间内完成范围受限的真实移动、探测器进入预备状态、停止行为、资产写入与时序测量。
- 用户试点: 选定一种约定好的技术或一个班次走新路径,旧系统保持可用,并事先写明切回触发条件。
对于一台实验室仪器,一名科学家和一名控制工程师可以共同负责全部四个阶段。共享设施则要分别指定设备抽象、方案、控制系统安全、存储、身份认证、部署和束流时间支持的负责人。把 RunEngine 放入队列并作为服务运行,可以将采集过程与崩溃的 notebook 或显示程序隔离;与此同时,权限、网络、部署和恢复工作也随之增加,运营团队能够负责这些工作时再采用这种方式。[4][5]
出现以下任一情况时,应暂停推进:必需设备缺少可信的完成或停止语义;关键联锁只存在于 Python 方案中;飞行扫描的对齐误差无法测量;已经完成的运行仍会丢失持久化资产;操作员无法在约定的恢复时限内返回旧路径。即使演示展示了漂亮的实时图,这些情况仍属于迁移失败。
Bluesky 第一次取得成功时,范围应当有意收窄:原有那项安全扫描被表达为可测试的方案,经由行为可靠的设备对象执行,并记录更丰富的上下文。当这条路径能够稳定重复,便可逐步向上迁移更多流程。联锁与时钟继续留在其保证能力最强的层级。
来源
- Bluesky Project,“Tutorial”,v1.15.1——RunEngine 与方案模型、回调、仿真、基线流、中断语义和清理模式。
- Bluesky Project,Ophyd“Using existing Devices”教程——由 EPICS 支持的设备构造、仿真模式、v1/v2 混合使用,以及 v2 API 尚处 provisional 阶段的说明。
- Brookhaven National Laboratory,NSLS-II Data Science and Systems Integration,“Software”——设施背景,以及 Bluesky 在实时数据、元数据、流程复用、中断恢复和可插拔 I/O 方面的能力。
- Stuart Campbell、Andrii Prodius,“Status of Bluesky Deployment at BESSY II”,ICALEPCS 2021——异构 EPICS 集成、SPEC 方案转换、数字孪生、轮询限制、文档存储和发布经验。
- Diamond Light Source,“Scientific Software, Controls and Computation”,Annual Review 2023–2024——Athena/GDA 并行部署、采用 Bluesky/Ophyd,以及通过硬件同步的 I22 用户实验。
- Henrysz,“National Synchrotron Light Source II interior overview”(2023 年 4 月 5 日),Wikimedia Commons——封面所用 NSLS-II 实验大厅照片的来源记录。