feat: add on_field_change() for reacting to CA/PVA field writes#205
Open
BoredDadDev wants to merge 1 commit intoDiamondLightSource:masterfrom
Open
feat: add on_field_change() for reacting to CA/PVA field writes#205BoredDadDev wants to merge 1 commit intoDiamondLightSource:masterfrom
BoredDadDev wants to merge 1 commit intoDiamondLightSource:masterfrom
Conversation
Add per-record on_field_change(field, callback) API that fires a Python callback whenever any record field (not just VAL) is written via Channel Access or PVAccess. Implementation: - extension.c: FieldWriteHook via asTrapWrite (coexists with existing EpicsPvPutHook), register_field_write_listener() C function - field_monitor.py: bridges C hook to per-record Python callbacks, parses channel names, dispatches by record + field - device_core.py: on_field_change() with wildcard '*' support, field_callbacks read-only property, _get_field_callbacks() helper - imports.py: register_field_write_listener() wrapper - softioc.py: installs field monitor after iocInit() Tests: - 5 integration tests covering CA, PVA, alarm fields (DBF_DOUBLE), string fields (DBF_STRING), and PVA non-VAL writes - sim_field_callbacks_ioc.py subprocess IOC with counter PVs
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds the ability to register Python callbacks that fire whenever an
external client (Channel Access or PV Access) writes to any field of a
soft-IOC record. This enables reactive logic for metadata fields like
SCAN,DISA,DESC,EGU, alarm limits, and any other writeable field-- without modifying the EPICS core libraries.
Motivation
The upstream
pythonSoftIOCprovideson_updatecallbacks that fire whenthe
VALfield is written, but there is no mechanism to react to writes onnon-VAL fields (SCAN, DISA, DESC, HIHI, etc.). At CLS we need to:
What changed
New files
softioc/field_monitor.pytests/sim_field_callbacks_ioc.pysim_*_ioc.pypattern)tests/test_field_callbacks.pyModified files
softioc/extension.cFieldWriteHookC function +register_field_write_listenerPython wrapper -- newasTrapWritelistener, coexists with the existingEpicsPvPutHookprint-loggersoftioc/device_core.pyon_field_change(),field_callbacksproperty,_get_field_callbacks()onDeviceSupportCoresoftioc/imports.pyregister_field_write_listener()ctypes wrappersoftioc/softioc.pyiocInit; callinstall_field_monitor()aftertests/conftest.pyfield_callbacks_iocpytest fixturesetup.cfgHow it works
Preserving original behavior
Print logging (pvlog): The original
EpicsPvPutHookremains completelyuntouched. Both hooks coexist because
asTrapWriteRegisterListenersupports multiple listeners. We ensure
pvlogis imported beforeiocInit(which it was already in all upstream test IOCs), so theaccess.acffile is loaded and the print-logging hook is registered.No existing API changes:
on_updatestill works as before. The newon_field_changeis purely additive.No record behavior changes: Records process identically. The hook
fires after the write completes, with the GIL acquired safely.
API
De-registration (
remove_field_callback,clear_field_callbacks) andmulti-field subscription (passing a list of fields) are included in PR2
along with the
auto_reset_scanandlog_putsextensions.What fires callbacks
caput)pvput,p4p)record.set()dbpfDemonstration
A runnable demo (
docs/demo_field_coverage.py, in the CLS workspace repo)exercises 47 writeable fields of an
aOutrecord via both CA and PVA,capturing each
on_field_changecallback and printing a latency table.Measured results on localhost (after channel warm-up):
Key observations:
HIHI,HIGH,LOW,LOLO,HHSV, ...)use the identical code path as every other field -- no special casing.
cagetbut not writeable, so no callback firesfor it (expected).
once channels are warm the latency matches CA.
Test results
The 16 skips are
@requires_cothreadtests on non-cothread platforms -- sameon both branches.
Design review
SOLID compliance
field_monitor.pydispatches;device_core.pystores callbacks;
extension.cbridges C/Python. Each module does one thing.on_field_change()-- no source modifications needed. The"*"wildcardmakes the system extensible to fields we haven't thought of yet.
interface, not on concrete Python classes.
Security review
MAX_STRING_SIZE + 1(41 bytes, theEPICS standard) with
memsetzeroing. No overflow possible becausedbGetFieldwithDBR_STRINGrespectsMAX_STRING_SIZE.PyGILState_Ensure/Releasebracket all Python calls fromthe CA/PVA thread. No Python objects accessed outside the GIL.
Py_XDECREFon result;Py_INCREF/XDECREFoncallback replacement. No leaks.
printf/printuse explicitformat specifiers, never user-controlled format strings.
callback cannot crash the IOC or prevent other callbacks from running.
it does not open sockets, listen on ports, or accept new connections.
Code metrics checklist
on_field_change,_dispatch_field_write)