Skip to content

Add PVA structure PV that provides mapping of plugin types to their instances (port names and PV prefixes) - #607

Open
jwlodek wants to merge 9 commits into
areaDetector:masterfrom
jwlodek:add-self-describing-plugins-pv
Open

jwlodek wants to merge 9 commits into
areaDetector:masterfrom
jwlodek:add-self-describing-plugins-pv

Conversation

@jwlodek

@jwlodek jwlodek commented Sep 16, 2026 •

Copy link
Copy Markdown
Member

Currently, there is no way for a client to know the available plugins configured for a particular detector, unless it is explicitly told by the user. It would be nice if a client could query the IOC and ask for a list of plugins available, sorted by type. That is the intention of this PR. Without any changes to downstream plugin code, and with only minor changes to the superclass constructor/destructor, we can create a registry of created NDPlugins. From there, we can construct a PVA structure keyed on plugin type that maps to lists of plugin port name / pv prefix tuples.

Clients can then use this to dynamically adjust behavior based on available plugins. For example:

  • A viewer application can provide access to the correct number of ROI plugin settings / visualizations.
  • A DAQ system can provide the user with access to all the detector plugins dynamically, without needing to be told about each one (specifically thinking of ophyd objects for AD detectors here - the main motivation of the PR).

and more.

Limitations:

  • Requires either PVA or PVXS. It can probably be expanded to include CA with some kind of waveform of encoded JSON, but I'm not sure that's worth doing since in most cases at least one of the PVA plugins will be available.
  • Currently, there isn't a way to nest plugins - for example, the PROC plugin creates a child TIFF plugin. It would be nice if the two were associated, aside from just checking the PV prefixes. Instead, the current implementation adds the PROC's tiff plugin in the overall list of tiff plugins.

Some questions for reviewers:

  • I think I can further re-work the logic here to not even need an additional port driver for the inventory record. I did it this way to more closely follow the pattern of other AD features. Does the architecture here look reasonable, or should I go back to no port driver (and by extension no changes to startup sequence) required?
  • Is there any other information a client may want to know about the plugins? I assumed that PV prefix and type would be enough, since presumably the client must know the plugin structure / available records already to do anything with it.

Here is an example pvget of the output:

jwlodek@alma10:~/Workspace/ADCore$ pvget DEV:SIM1:Plugins_RBV
DEV:SIM1:Plugins_RBV structure 
    structure[] NDFileHDF5
        structure 
            string portName FileHDF1
            string pvPrefix DEV:SIM1:HDF1:
    structure[] NDFileTIFF
        structure 
            string portName FileTIFF1
            string pvPrefix DEV:SIM1:TIFF1:
    structure[] NDPluginCodec
        structure 
            string portName CODEC1
            string pvPrefix DEV:SIM1:Codec1:
        structure 
            string portName CODEC2
            string pvPrefix DEV:SIM1:Codec2:
    structure[] NDPluginPva
        structure 
            string portName PVA1
            string pvPrefix DEV:SIM1:Pva1:
    structure[] NDPluginROI
        structure 
            string portName ROI1
            string pvPrefix DEV:SIM1:ROI1:
        structure 
            string portName ROI2
            string pvPrefix DEV:SIM1:ROI2:
        structure 
            string portName ROI3
            string pvPrefix DEV:SIM1:ROI3:
        structure 
            string portName ROI4
            string pvPrefix DEV:SIM1:ROI4:
    structure[] NDPluginStats
        structure 
            string portName STATS1
            string pvPrefix DEV:SIM1:Stats1:
    structure[] NDPluginStdArrays
        structure 
            string portName Image1
            string pvPrefix DEV:SIM1:image1:

For the following startup script:

#!../../bin/linux-x86_64/simDetectorApp

< envPaths
< /epics/common/localhost-netsetup.cmd

errlogInit(20000)

dbLoadDatabase("$(TOP)/dbd/simDetectorApp.dbd")
simDetectorApp_registerRecordDeviceDriver(pdbbase)

epicsEnvSet("PREFIX", "DEV:SIM1:")
epicsEnvSet("PORT",   "SIM1")
epicsEnvSet("QSIZE",  "20")
epicsEnvSet("XSIZE",  "1024")
epicsEnvSet("YSIZE",  "1024")
epicsEnvSet("NCHANS", "2048")
epicsEnvSet("MAX_THREADS", "8")
epicsEnvSet("EPICS_DB_INCLUDE_PATH", "$(ADCORE)/db")

# simDetector driver
simDetectorConfig("SIM1", 1024, 1024, 1, 0, 0)
dbLoadRecords("$(ADSIMDETECTOR)/db/simDetector.template","P=$(PREFIX),R=cam1:,PORT=$(PORT),ADDR=0,TIMEOUT=1")

# Standard arrays plugin
NDStdArraysConfigure("Image1", 20, 0, "SIM1", 0, 0, 0, 0, 0, 5)
dbLoadRecords("NDStdArrays.template", "P=$(PREFIX),R=image1:,PORT=Image1,ADDR=0,TIMEOUT=1,NDARRAY_PORT=$(PORT),TYPE=Int8,FTVL=UCHAR,NELEMENTS=12000000")

# 4 ROI plugins
NDROIConfigure("ROI1", $(QSIZE), 0, "$(PORT)", 0, 0, 0, 0, 0, $(MAX_THREADS=5))
dbLoadRecords("NDROI.template", "P=$(PREFIX),R=ROI1:,PORT=ROI1,ADDR=0,TIMEOUT=1,NDARRAY_PORT=$(PORT)")
NDROIConfigure("ROI2", $(QSIZE), 0, "$(PORT)", 0, 0, 0, 0, 0, $(MAX_THREADS=5))
dbLoadRecords("NDROI.template", "P=$(PREFIX),R=ROI2:,PORT=ROI2,ADDR=0,TIMEOUT=1,NDARRAY_PORT=$(PORT)")
NDROIConfigure("ROI3", $(QSIZE), 0, "$(PORT)", 0, 0, 0, 0, 0, $(MAX_THREADS=5))
dbLoadRecords("NDROI.template", "P=$(PREFIX),R=ROI3:,PORT=ROI3,ADDR=0,TIMEOUT=1,NDARRAY_PORT=$(PORT)")
NDROIConfigure("ROI4", $(QSIZE), 0, "$(PORT)", 0, 0, 0, 0, 0, $(MAX_THREADS=5))
dbLoadRecords("NDROI.template", "P=$(PREFIX),R=ROI4:,PORT=ROI4,ADDR=0,TIMEOUT=1,NDARRAY_PORT=$(PORT)")

# 2 Codec plugins
NDCodecConfigure("CODEC1", $(QSIZE), 0, "$(PORT)", 0, 0, 0, 0, 0, 5)
dbLoadRecords("NDCodec.template", "P=$(PREFIX),R=Codec1:,PORT=CODEC1,ADDR=0,TIMEOUT=1,NDARRAY_PORT=$(PORT)")
NDCodecConfigure("CODEC2", $(QSIZE), 0, "$(PORT)", 0, 0, 0, 0, 0, 5)
dbLoadRecords("NDCodec.template", "P=$(PREFIX),R=Codec2:,PORT=CODEC2,ADDR=0,TIMEOUT=1,NDARRAY_PORT=$(PORT)")

# HDF5 file plugin
NDFileHDF5Configure("FileHDF1", $(QSIZE), 0, "$(PORT)", 0)
dbLoadRecords("NDFileHDF5.template", "P=$(PREFIX),R=HDF1:,PORT=FileHDF1,ADDR=0,TIMEOUT=1,XMLSIZE=2048,NDARRAY_PORT=$(PORT)")

# TIFF file plugin
NDFileTIFFConfigure("FileTIFF1", $(QSIZE), 0, "$(PORT)", 0)
dbLoadRecords("NDFileTIFF.template", "P=$(PREFIX),R=TIFF1:,PORT=FileTIFF1,ADDR=0,TIMEOUT=1,NDARRAY_PORT=$(PORT)")

# 5 statistics plugins
NDStatsConfigure("STATS1", $(QSIZE), 0, "$(PORT)", 0, 0, 0, 0, 0, $(MAX_THREADS=5))
dbLoadRecords("NDStats.template", "P=$(PREFIX),R=Stats1:,PORT=STATS1,ADDR=0,TIMEOUT=1,HIST_SIZE=256,XSIZE=$(XSIZE),YSIZE=$(YSIZE),NCHANS=$(NCHANS),NDARRAY_PORT=$(PORT)")

# Start our inventory port driver that will publish the PVA structure mapping plugin types to instances.
NDPluginInventoryConfigure("PluginInventory", "$(PREFIX)Plugins_RBV")
dbLoadRecords("NDPluginInventory.template", "P=$(PREFIX),PORT=PluginInventory,ADDR=0,TIMEOUT=1")

iocInit()

@jwlodek

jwlodek commented Sep 16, 2026 •

Copy link
Copy Markdown
Member Author

Note: For now I've only tested with pvAccessCpp, not PVXS. I will want to test with PVXS (and have someone else try it out with both pvAccessCpp and PVXS) before this is merged.

On another note, if this is merged, a follow up to the areaDetector repo adding the option to enable this at build time should be added to the example config files. For now I've made it a separate config option - would making it just conditional on WITH_PVA or WITH_PVXS being true make sense? There are no other dependencies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant