连接Bluesky和EPICS

目标: 连接Bluesky和EPICS过程变量

基础

在Bluesky中,ophyd包用于连接底层控制系统(此处以EPICS为例)。其基本结构是EpicsSignal,它将单个EPICS过程变量(PV)与单个Python对象相连接。在此场景后面, 该连接在底层通过PyEpics包及EPICS通道访问协议实现。其他信息(如单位、限值、显示精度、数据类型、枚举标签等)在可用时,也会从EPICS获取。

|---------------|--------------------------------------|
| ophyd类 | 描述 |
| EpicsSignal | 与一个EPICS PV建立一个读取/写入连接, 在后台监视和更新这个对象 |
| EpicsSignalRO | EpicsSignal的只读版本 |

注意: EpicsSignal和EpicsSignalRO有超出此处描写外的很多其它配置特性. 更多细节见ophyd文档.

EpicsSignal

从ophyd导入支持开始

python 复制代码
In [7]: from ophyd import EpicsSignal

有一个EPICS过程变量(PV)MotorVM:m1_able可以与之连接。该PV代表单个比特位。我们用一个名为bit1的Python对象与之连接(为了方便起见,我们设置了一个ioc前缀变量,以防有人使用的EPICS IOC具有不同的前缀)。

除了EPICS PV外,还必须添加一个name关键字,以便构造具有对象名称的Python对象。按照惯例,其名称应与等号左侧的名称保持一致。

python 复制代码
In [1]: from ophyd import EpicsSignal

In [2]: ioc = "MotorVM:"

In [3]: bit1= EpicsSignal(f"{ioc}m1_able", name="bit1")

在创建连接后,我们立即测试了 Python 对象是否已与 EPICS 完全连接(通常这需要短暂时间,并非瞬时完成)。

python 复制代码
In [4]: print(f"{bit1.connected = }")
bit1.connected = True

当人工在 Jupyter notebook 中进行交互时,EPICS PV 的连接通常会在移动到下一个单元格并执行它的时间内完成。但是,当这些 PV 连接由单个程序执行且需要立即使用该对象时,则可能需要等待连接完成才能继续执行后续操作。

python 复制代码
In [5]: bit1.wait_for_connection()

In [6]: print(f"{bit1.connected = }")
bit1.connected = True

使用这个Python对象的get()方法打印其值:

python 复制代码
In [7]: print(f"{bit1.get() = }")
bit1.get() = 0

该过程变量可能关联着其两种不同取值所对应的标签。可通过对象的 enum_strs 属性查看这些标签:

python 复制代码
In [8]: print(f"{bit1.enum_strs = }")
bit1.enum_strs = ('Enable', 'Disable')

我们用数值(0或1)或用文本(Enable, Disable)设置这个对象, 并且我们可以用数值或文本显示这个对象:

python 复制代码
In [9]: bit1.put(1)

In [10]: print(f"{bit1.get() = }")
bit1.get() = 1

In [11]: print(f"{bit1.get(as_string=False) = }")
bit1.get(as_string=False) = 1

In [12]: print(f"{bit1.get(as_string=True) = }")
bit1.get(as_string=True) = 'Disable'

In [13]: bit1.put("Enable")

In [14]: print(f"{bit1.get() = }")
bit1.get() = 0

In [15]: print(f"{bit1.get(as_string=False) = }")
bit1.get(as_string=False) = 0

In [16]: print(f"{bit1.get(as_string=True) = }")
bit1.get(as_string=True) = 'Enable'

通过在创建这个连接时添加string=True关键字,我们也可以使得文本表示成为默认:

python 复制代码
In [17]: bit1 = EpicsSignal(f"{ioc}m1_able", name="bit1", string=True)

In [18]: bit1.get()
Out[18]: 'Enable'

read()方法(由数据采集使用)显示了从EPICS接收到的值和时间戳. 这个时间戳被Python记录并且是从1970年1月1日GMT以来的绝对秒数.

python 复制代码
In [19]: bit1.read()
Out[19]: {'bit1': {'value': 'Enable', 'timestamp': 1766728047.551385}}

EpicsSignalRO

gp:UPTIME PV告诉我们IOC已经运行了多少时间. 这个PV包含我们不能更改的信息, 因为它报告了仅IOC知道的值. 用来自ophyd的EpicsSignalRO创建一个uptime对象.

python 复制代码
from ophyd import EpicsSignalRO
uptime =  EpicsSignalRO(f"{ioc}UPTIME", name="uptime")
uptime.wait_for_connection()
print(f"{uptime.get() = }")

Device和Component

我们可能拥有一组在某种程度上有关联的过程变量。可以将这些 Python 对象组织成一个更大的结构,称为 ophyd.Device,其中每个 EpicsSignal 对象都是一个 ophyd.Component。接下来导入相应的 ophyd 结构:

python 复制代码
In [2]: from ophyd import Component, Device

构建一个结构体, 它关联这些PVs并且使用:

|------------------|----------|----------------|
| PV | 如何使用它 | 属性 |
| gp:gp:bit1 | on/off控制 | enable |
| gp:gp:float1 | 目标温度 | setpoint |
| gp:gp:float1.EGU | 温度单位 | setpoint_units |
| gp:gp:text1 | 短标签 | label |

要将这些PV组合在一起,必须创建一个自定义的Python类,该类以Device作为基类,并将每个PV定义为Component

为了使该类更有用,我们从我们的类省略IOC前缀(即第一个gp:)。当我们为这个结构体创建Pythno对象时, 我们将使用这个IOC前缀。

python 复制代码
In [5]: class MyGroup(Device):
   ...:     setPoint = Component(EpicsSignal, "gp:float1")
   ...:     units = Component(EpicsSignal, "gp:float1.EGU")
   ...:     label = Component(EpicsSignal, "gp:text1")
   ...:     enable  = Component(EpicsSignal, "gp:bit1")
   ...:

使用这个结构体(一次性)连接所有PVs, 使用与以上EpicsSignal相同的命令类型.

python 复制代码
In [11]: thing = MyGroup(prefix=ioc, name="thing")

In [12]: thing.wait_for_connection()

使用read()方法一次性显示所有值.

python 复制代码
In [10]: thing.read()
Out[10]:
OrderedDict([('thing_setPoint', {'value': 0.0, 'timestamp': 631152000.0}),
             ('thing_units', {'value': 'Degree', 'timestamp': 631152000.0}),
             ('thing_label',
              {'value': 'Hello World', 'timestamp': 631152000.0}),
             ('thing_enable', {'value': 0, 'timestamp': 631152000.0})])

设置温度单位和设置点:

python 复制代码
In [13]: thing.setPoint.put(123.45)

In [14]: thing.units.put("Rankine")

In [15]: thing.read()
Out[15]:
OrderedDict([('thing_setPoint',
              {'value': 123.45, 'timestamp': 1766760933.393842}),
             ('thing_units',
              {'value': 'Rankine', 'timestamp': 1766760933.393842}),
             ('thing_label',
              {'value': 'Hello World', 'timestamp': 631152000.0}),
             ('thing_enable', {'value': 0, 'timestamp': 631152000.0})])
python 复制代码
In [17]: print(f"set point: {thing.setPoint.get():.2f} {thing.units.get()}")
set point: 123.45 Rankine

Device结构体可以被嵌套, 产生更复杂的结构体. 例如:

python 复制代码
    ...:     basic = Component(MyGroup, "")
    ...:     reading = Component(EpicsSignal, "gp:float2")
    ...:     abstract = Component(EpicsSignal, "gp:text2",  string=True)
    ...:

In [19]: stack = Stack(ioc, name="stack")

In [20]: stack.wait_for_connection()

In [21]: stack.read()
Out[21]:
OrderedDict([('stack_basic_setPoint',
              {'value': 123.45, 'timestamp': 1766760933.393842}),
             ('stack_basic_units',
              {'value': 'Rankine', 'timestamp': 1766760933.393842}),
             ('stack_basic_label',
              {'value': 'Hello World', 'timestamp': 631152000.0}),
             ('stack_basic_enable', {'value': 0, 'timestamp': 631152000.0}),
             ('stack_reading', {'value': 0.0, 'timestamp': 631152000.0}),
             ('stack_abstract',
              {'value': 'Hello World', 'timestamp': 631152000.0})])

以上教程使用了一个软IOC db, 如下:

python 复制代码
record(bo, "gp:gp:bit1") {
  field(ZNAM, "OFF")
  field(ONAM, "ON")
}

record(bo, "gp:gp:bit2") {
  field(ZNAM, "OFF")
  field(ONAM, "ON")
}

record(ai, "gp:gp:float1") {
        field(EGU, "Degree")
}
record(ao, "gp:gp:float2") {
        field(EGU, "Degree")
}

record(stringin, "gp:gp:text1") {
        field(VAL, "Hello World")
}

record(stringout, "gp:gp:text2") {
        field(VAL, "Hello World")
}

创建虚拟环境:

python 复制代码
conda create -n XXXX python=3.11 -c conda-forge
相关推荐
yuyuyuliang0010 天前
pydm教程2
epics
BF062415 天前
EPICS ARCHIVER APPLIANCE
编辑器·epics
翟天保Steven5 个月前
Ubuntu-安装Epics教程
linux·ubuntu·epics
yuyuyuliang006 个月前
EPICS IP模块
epics
yuyuyuliang001 年前
Qt5中使用EPICS通道访问读写EPICS PV
linux·开发语言·qt·epics
woshigaowei51461 年前
自定义EPICS在LabVIEW中的测试
labview·epics
woshigaowei51461 年前
LabVIEW中EPICS客户端/服务端的测试
labview·epics
yuyuyuliang002 年前
Webmin在EPICS IOC启动中的应用
epics·hexapod c-887
yuyuyuliang002 年前
EPICS modbus 模块数字量读写练习
epics