For the integration of existing developments into docker/kubernates we use part of the Diamond flow for a beamline under kubernates.
In particular we have realised our customisation where we put all the normalisable support we have for our INFN's beamlines.
The project is INFN EPICS IOC by cloning it:
git clone https://baltig.infn.it/epics-containers/infn-epics-ioc.git --recurse-submodules
The aim of this project:
- create a docker image with the device support needed for INFN beamlines that can be run in container.
- standardize and greatly simplify the way to instantiate iocs via simple yaml file
- have a common database (shared among INFN and Diamond)
- standardize the build system
The layout of this project is:
. ├── Dockerfile <-- docker file production ├── LICENSE ├── README.md ├── build ├── ibek-support <-- submodule with ibek device support │ ├── ADAravis │ ├── ADCore │ ├── ADGenICam │ ├── ADSimDetector │ ├── IOCInfo │ ├── LICENSE │ ├── README.md │ ├── StreamDevice │ ├── _global │ ├── asyn │ ├── autosave │ ├── build_support.sh │ ├── busy │ ├── calc │ ├── easy-driver-epics │ ├── iocStats │ ├── lakeshore340 │ ├── make_global_schemas.sh │ ├── modbus │ ├── motor │ ├── motorMotorSim │ ├── motorNewport │ ├── opcua │ ├── pmac │ ├── pvi-generate.sh │ ├── quadEM │ ├── schemas │ ├── screen-epics-ioc │ ├── sequencer │ ├── sscan │ ├── tests │ ├── todo │ └── zebra ├── ioc │ ├── Makefile │ ├── configure │ ├── install.sh │ ├── iocApp │ ├── liveness.sh │ ├── start.sh │ └── stop.sh ├── menlo │ ├── install.sh │ └── menlo.ibek.support.yaml ├── requirements.txt ├── requirements_ec.txt ├── tests │ ├── example-config │ ├── example-ibek-config │ └── run-tests.sh └── user_examples └── README.md
When we have to put an IOC in production we may have the following possibility:
- support already present in <ibek-support>
- support present as git project
- support not present at all → put in the condition 2
Use an existing ibek support
If a support is present in <ibek-support>, it's very simple to use it just create a yaml that instantiates your device(s).
An example could be the following the following yaml newport.yaml. It instatiate a motor with 1 axis. This support implicitly assumes that this serial control is connected to a Ethernet2Serial server like a moxa that responds on address 192.168.190.56 and port 4001
ioc_name: rfmotors description: RF motors entities: - type: motorNewport.SMC100CreateController controllerName: NEWPORT001 P: "SPARC:RF:" IP: 192.168.190.56 TCPPORT: 4001 numAxes: 1 - type: motorNewport.motorAxis controller: NEWPORT001 M: "m0" DESC: "Axis" ADDR: 0 DLLM: -25 DHLM: 25 home: 1 start: 10 VELO: 1
Then we can test if simply having the docker image: baltig.infn.it:4567/epics-containers/infn-epics-ioc
docker run -p 5064:5064/udp -p 5064:5064/tcp -p 5065:5065/udp -p 5065:5065/tcp -it -v .:/epics/ioc/config baltig.infn.it:4567/epics-containers/infn-epics-ioc:devel
A generic asyn motor OPI interface can be used to drive the motor. Motor OPIs Remember to add localhost to your phoebus
org.phoebus.pv.ca/addr_list settings:
org.phoebus.pv.ca/addr_list=localhost org.phoebus.pv.ca/auto_addr_list=false org.csstudio.trends.databrowser3/urls=pbraw://sparc-archiver.apps.okd-datest.lnf.infn.it/retrieval org.csstudio.trends.databrowser3/archives=pbraw://sparc-archiver.apps.okd-datest.lnf.infn.it/retrieval org.phoebus.olog.es.api/olog_url=http://sparc-olog.apps.okd-datest.lnf.infn.it/Olog org.phoebus.olog.api/olog_url=http://sparc-olog.apps.okd-datest.lnf.infn.it/Olog org.phoebus.logbook/logbook_factory=olog-es org.phoebus.olog.api/username=epics org.phoebus.olog.api/password=epics org.phoebus.channelfinder/channelfinder.serviceURL=http://sparc-channelfinder.apps.okd-datest.lnf.infn.it/ChannelFinder org.phoebus.applications.saveandrestore.client/jmasar.service.url=http://sparc-saveandrestore.apps.okd-datest.lnf.infn.it/save-restore org.csstudio.scan.client/host=http://sparc-scanserver.apps.okd-datest.lnf.infn.it org.csstudio.scan.client/port=4810
Once the IOC runs correctly as docker can be inserted in the EPIK8S configuration
Build a support from existing git support
This is path is more complex, but gives much more satisfaction, in fact each time you add a new support as much generic possible you greatly simplify the life of your self and in principle many other people will use the your support.
Step 1 create a directory inside ibek-support
Create by copy and renaming one of the existing support. Suppose you want to add the support for <mydevmodule>, you should have:
. ├── Dockerfile ├── LICENSE ├── README.md ├── build ├── ibek-support │ ├── ADAravis │ ├── mydevmodule │ │ ├── install.sh │ │ └── mydevmodule.ibek.support.yaml │ ├── opcua │ │ └── install.sh │ ├── pmac │ │ ├── install.sh │ │ ├── make_pvi.sh │ │ ├── pmac.ibek.support.todo │ │ ├── pmac.ibek.support.yaml │ │ ├── pmacAxis.pvi.device.yaml │ │ ├── pmacCSController.pvi.device.yaml │ │ ├── pmacController.pvi.device.yaml │ │ └── pmacTrajectory.pvi.device.yaml │
So in general just two file must be added: install.sh and mydevmodule.ibek.support.yaml.
install.sh
This file contains instructions on howto retrieve, compile and link dependencies of your module mydevmodule. In general if the mydevmodule is well structured from a build epics point of view needs only to be patched like that:
#!/bin/bash # ARGUMENTS: # $1 VERSION to install (must match repo tag) VERSION=${1} NAME=<mydevmodule> ## as compare in the git repository FOLDER=$(dirname $(readlink -f $0)) # log output and abort on failure set -xe # get the source and fix up the configure/RELEASE files ibek support git-clone ${NAME} ${VERSION} --org http://gitrepo ## repository git root ibek support register ${NAME} # declare the libs and DBDs that are required in ioc/iocApp/src/Makefile ibek support add-libs <mydevmodulelib> <support1lib> <support2lib> asyn ## optional libraries that must be linked ibek support add-dbds <mydevmodule>.dbd <optsupport1.dbd> <optsupport2>.dbd ### optional dbd supports # global config settings ${FOLDER}/../_global/install.sh ${NAME} # compile the support module ibek support compile ${NAME} # prepare *.bob, *.pvi, *.ibek.support.yaml for access outside the container. ibek support generate-links ${FOLDER}
mydevmodule.ibek.support.yaml
This also has a very simple syntax and gives instructions on how to expand a future yaml configuration.
The simplest way to proceed is to understand how the st.cmd of your ioc should be constructed and having this in mind build a set of rules that wraps that code.
The best way is look other supports to understand, here below I put a recently support that I added for newport motors. The comments at the beginning is more a general st.cmd of a SMC100 newport motor.
In the example yaml I instruct ibek to correcty replace a file configuration like the previous newport.yaml.
# yaml-language-server: $schema=https://github.com/epics-containers/ibek/releases/download/1.5.0/ibek.support.schema.json # #errlogInit(5000) # < envPaths # # Tell EPICS all about the record types, device-support modules, drivers, # # etc. # dbLoadDatabase("../../dbd/newport.dbd") # newport_registerRecordDeviceDriver(pdbbase) # ### Motors # dbLoadTemplate "motor.substitutions.SMC100" # ### Serial port setup # drvAsynSerialPortConfigure("serial1", "/dev/ttyS0", 0, 0, 0) # asynSetOption(serial1,0,baud,57600) # asynOctetSetInputEos("serial1",0,"\r\n") # asynOctetSetOutputEos("serial1",0,"\r\n") # ### Newport SMC100 support # # (driver port, serial port, axis num, ms mov poll, ms idle poll, egu per step) # SMC100CreateController("SMC100_1", "serial1",1, 100, 0, "0.00005") # file "$(TOP)/db/basic_asyn_motor.db" # { # pattern # {P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DHLM, DLLM, INIT, RTRY} # {IOC:, 1, "m$(N)", "asynMotor", "SMC100_1", 0, "GTS30V", mm, Pos, 1, 0, .2, 0, .5, .2, 0.00001, 6, 25, -5, ""} # } # iocInit module: motorNewport defs: - name: SMC100CreateController description: |- Creates a SMC100 motion controller connected to an ethernetToSerialServer args: - type: id name: controllerName description: |- The name of the controller and its Asyn Port Name - type: str name: P description: |- Device PV Prefix - type: str name: IP description: |- IP address of the ethernet2serial default: 127.0.0.1 ## localhost - type: int name: TCPPORT description: |- Port of the ethernet2serial default: 4001 - type: int name: POLL description: |- Movement poll ms default: 100 - type: float name: EGUXSTEP description: |- EGU PER STEP default: 0.00005 - type: int name: ASYNPRIO description: |- ASYN PRIORITY, Default : 0 default: 0 - type: int name: AUTOCONNECT description: |- Asyn auto connect 0: Auto connection 1: no Auto connection default: 0 - type: int name: NOPRECESSESOS description: |- ASYN noProcessEos, Default : 0 https://epics.anl.gov/tech-talk/2020/msg01705.php default: 0 - type: int name: numAxes description: |- The number of axes to create pre_init: - value: | # epicsEnvSet "STREAM_PROTOCOL_PATH", "$(MOTORNEWPORT)/protocol/" # Create Asyn Port drvAsynIPPortConfigure("{{controllerName}}_ASYN", "{{IP}}:{{TCPPORT}}", {{ASYNPRIO}}, {{AUTOCONNECT}}, {{NOPRECESSESOS}}) # asynInterposeEosConfig("{{controllerName}}_ASYN",0,2000,0) SMC100CreateController("SMC100_{{controllerName}}", "{{controllerName}}_ASYN","{{numAxes}}", "{{POLL}}", 0, "{{EGUXSTEP}}") asynOctetSetInputEos({{controllerName}}_ASYN,0,"\r\n") asynOctetSetOutputEos({{controllerName}}_ASYN,0,"\r\n") asynReport 10 - name: motorAxis description: |- Creates a motor axis args: - type: object name: controller description: |- a reference to the motion controller - type: str name: M description: |- PV suffix for the motor record - type: int name: ADDR description: |- The axis number (allowed to be from 0 to controller.numAxes-1) - type: str name: DESC description: |- The description of the axis - type: int name: DLLM description: |- The low limit of the axis default: -5 - type: int name: DHLM description: |- The high limit of the axis default: 25 - type: int name: VELO description: |- Velocity default: 1 - type: int name: home description: |- The home position of the axis (in counts) - type: int name: start description: |- The starting position of the axis (in counts) default: 0 - type: enum name: DIR description: |- The direction of the axis default: 0 values: Pos: 0 Neg: 1 - type: str name: EGU description: |- Engineering Units default: "mm" - type: float name: VBAS description: |- Base Velocity (EGU/s) default: 0.2 - type: float name: ACCL description: |- Seconds to Velocity default: 0.2 - type: int name: BDST description: |- BL Distance (EGU) default: 0 - type: float name: BVEL description: |- BL Velocity (EGU/s) default: 0.5 - type: float name: BACC description: |- BL Seconds to Veloc. default: 0.2 - type: float name: MRES description: |- Motor Step Size (EGU) default: 0.00001 - type: int name: PREC description: |- Display precision (EGU) default: 6 databases: # TODO as this is a simulation I have hard coded some of the DB fields, # but these could easily be made into arguments above # # Note: supplying no value means that the argument of the same name is used # (the most common case - if you contrive to make args and db fields the same. # Which is good idea for ease of transition from traditional IOCs) - file: basic_asyn_motor.db args: P: "{{controller.P}}" N: "{{ADDR +1 }}" M: DTYP: "asynMotor" PORT: "SMC100_{{controller}}" ADDR: DESC: EGU: DIR: VELO: VBAS: ACCL: BDST: BVEL: BACC: MRES: PREC: DHLM: DLLM: INIT: "" post_init: - value: | dbl
Step 2 add an entry into Dockerfile
Modify the Dockerfile in the project:
##### build stage ############################################################## ARG TARGET_ARCHITECTURE ARG BASE=7.0.8ec1b3 ARG REGISTRY=ghcr.io/epics-containers FROM ${REGISTRY}/epics-base-${TARGET_ARCHITECTURE}-developer:${BASE} AS developer # The devcontainer mounts the project root to /epics/generic-source # Using the same location here makes devcontainer/runtime differences transparent. ENV SOURCE_FOLDER=/epics/generic-source # connect ioc source folder to its know location RUN ln -s ${SOURCE_FOLDER}/ioc ${IOC} # Get latest ibek while in development. Will come from epics-base when stable COPY requirements.txt requirements.txt RUN pip install --upgrade -r requirements.txt WORKDIR ${SOURCE_FOLDER}/ibek-support # copy the global ibek files COPY ibek-support/_global/ _global COPY ibek-support/iocStats/ iocStats RUN iocStats/install.sh 3.2.0 ################################################################################ # TODO - Add further support module installations here ################################################################################ COPY ibek-support/asyn/ asyn/ RUN asyn/install.sh R4-44 COPY ibek-support/autosave/ autosave/ RUN autosave/install.sh R5-11 COPY ibek-support/busy/ busy/ RUN busy/install.sh R1-7-3 COPY ibek-support/StreamDevice/ StreamDevice/ RUN StreamDevice/install.sh 2.8.24 COPY ibek-support/sscan/ sscan/ RUN sscan/install.sh R2-11-6 COPY ibek-support/calc/ calc/ RUN calc/install.sh R3-7-5 COPY ibek-support/motor/ motor/ RUN motor/install.sh R7-3 COPY ibek-support/motorMotorSim/ motorMotorSim/ RUN motorMotorSim/install.sh R1-2 COPY ibek-support/ADCore/ ADCore/ RUN ADCore/install.sh R3-13 COPY ibek-support/ADGenICam ADGenICam/ RUN ADGenICam/install.sh R1-9 COPY ibek-support/ADSimDetector ADSimDetector/ RUN ADSimDetector/install.sh R2-10 COPY ibek-support/modbus/ modbus/ RUN modbus/install.sh R3-3 COPY ibek-support/screen-epics-ioc screen-epics-ioc/ RUN screen-epics-ioc/install.sh v1.3.1 COPY ibek-support/motorNewport motorNewport/ RUN motorNewport/install.sh R1-2-1 # COPY ibek-support/easy-driver-epics/ easy-driver-epics/ # RUN easy-driver-epics/install.sh master COPY ibek-support/mydevmodule mydevmodule ### HERE RUN mydevmodule/install.sh master ## HERE # get the ioc source and build it COPY ioc/ ${SOURCE_FOLDER}/ioc RUN cd ${IOC} && ./install.sh && make ##### runtime preparation stage ################################################ FROM developer AS runtime_prep # get the products from the build stage and reduce to runtime assets only RUN ibek ioc extract-runtime-assets /assets ${SOURCE_FOLDER}/ibek* ##### runtime stage ############################################################ FROM ${REGISTRY}/epics-base-${TARGET_ARCHITECTURE}-runtime:${BASE} AS runtime # get runtime assets from the preparation stage COPY --from=runtime_prep /assets / # install runtime system dependencies, collected from install.sh scripts RUN ibek support apt-install-runtime-packages --skip-non-native ENV TARGET_ARCHITECTURE ${TARGET_ARCHITECTURE} CMD ["/bin/bash", "-c", "${IOC}/start.sh"]
Step 3 build docker image
building:
git clone https://baltig.infn.it/epics-containers/infn-epics-ioc.git --recurse-submodules cd infn-epics-ioc docker build --build-arg TARGET_ARCHITECTURE="linux" --build-arg TARGETARCH="amd64" -t baltig.infn.it:4567/epics-containers/infn-epics-ioc:local .
building development:
git clone https://baltig.infn.it/epics-containers/infn-epics-ioc.git --recurse-submodules cd infn-epics-ioc docker build -f Dockerfile.devel --build-arg TARGET_ARCHITECTURE="linux" --build-arg TARGETARCH="amd64" -t baltig.infn.it:4567/epics-containers/infn-epics-ioc:devel .
Step 4 motor yaml instance
Write a mymotor.yaml ibek instance of the support just created and built:
gioc_name: rfmotors description: RF motors entities: - type: motorNewport.SMC100CreateController controllerName: NEWPORT001 P: "SPARC:RF:" IP: 192.168.190.56 TCPPORT: 4001 numAxes: 1 - type: motorNewport.motorAxis controller: NEWPORT001 M: "m0" DESC: "Axis" ADDR: 0 DLLM: -25 DHLM: 25 home: 1 start: 10 VELO: 1
test instance
the following lines will mount the current directory that contains mymotor.yaml in the container /epics/ioc/config and will expose CA ports, then run the support just created:
docker run -p 5064:5064/udp -p 5064:5064/tcp -p 5065:5065/udp -p 5065:5065/tcp -p 5075:5075/tcp -p 5076:5076/udp -it -v .:/epics/ioc/config -v .:/epics/ioc/config baltig.infn.it:4567/epics-containers/infn-epics-ioc:local root@3870c1890419:/epics/generic-source/ioc/config# /epics/ioc/start.sh ... io log .... ....
the following lines use the development image:
cd <myibek yaml instance> docker run -p 5064:5064/udp -p 5064:5064/tcp -p 5065:5065/udp -p 5065:5065/tcp -p 5075:5075/tcp -p 5076:5076/udp -it -v .:/epics/ioc/config baltig.infn.it:4567/epics-containers/infn-epics-ioc:devel root@3870c1890419:/epics/generic-source/ioc/config# /epics/ioc/start.sh ... log ...
For test refer to existing ibek support