This page documents the additive cpp_logic interface in ecmc.
Use it when:
ecmc data itemscpp_logic does not replace the existing plugin ABI in ecmcPluginDefs.h.
It is a second interface in ecmc, intended for user-defined cyclic logic
modules rather than full standalone plugins.
The normal IOC-level entry point is:
${SCRIPTEXEC} ${ecmccfg_DIR}scripts/loadCppLogic.cmd, "FILE=/path/to/main.so,REPORT=1"
Important defaults in loadCppLogic.cmd:
FILE: defaults to libmain.soDIR: defaults to ./bin/LOGIC_ID: defaults to the next free idASYN_PORT: defaults to CPP.LOGIC<LOGIC_ID>MACROS: optional free-form text passed into user code through ecmcCpp::getMacrosString()LOAD_DEFAULT_PVS: defaults to 1EPICS_SUBST: optional custom substitutions file for exported epics.* PVs, default cfg/<FILE>_cpp_logic.subsDB_PREFIX: defaults to $(IOC):The wrapper:
epics.* substitutions by default, unless EPICS_SUBST=EMPTYThe underlying parser commands are:
Cfg.LoadCppLogic(<id>,<file>)
Cfg.LoadCppLogic(<id>,<file>,<config>)
Cfg.ReportCppLogic(<id>)
Cfg.AppendCppLogicMacros(<id>)=<text>
The IOC wrapper script mainly fills in defaults and handles the EPICS record loading around those commands.
For long startup macro strings, use the companion helper script:
${SCRIPTEXEC} ${ecmccfg_DIR}scripts/loadCppLogic.cmd, "MACROS='A=1,B=2'"
${SCRIPTEXEC} ${ecmccfg_DIR}scripts/appendCppLogicMacros.cmd, "MACROS='C=3,D=4'"
If LOGIC_ID is omitted, appendCppLogicMacros.cmd targets the current
ECMC_CPP_LOGIC_ID set by loadCppLogic.cmd.
The MACROS argument is passed as plain text to the C++ logic instance. It is
not applied by msi to the C++ source code. User code reads it at runtime with
the helper functions in ecmcCppLogic.hpp.
Example startup:
${SCRIPTEXEC} ${ecmccfg_DIR}scripts/loadCppLogic.cmd, \
"FILE=libmain.so,MACROS='S_ID=14,AXIS_ID=1,DBG=1,GAIN=2.5'"
Example C++ use:
const std::string macros = ecmcCpp::getMacrosString();
const int slave_id = ecmcCpp::getMacroValueInt(macros, "S_ID", 14);
const int axis_id = ecmcCpp::getMacroValueInt(macros, "AXIS_ID", 1);
const bool dbg = ecmcCpp::getMacroValueInt(macros, "DBG", 0) != 0;
const double gain = ecmcCpp::getMacroValueDouble(macros, "GAIN", 1.0);
const std::string mode = ecmcCpp::getMacroValueString(macros, "MODE", "normal");
ecmcCpp::setEnableDbg(dbg);
Available macro helper functions:
ecmcCpp::getMacrosString()ecmcCpp::getMacroValue(macros, key)ecmcCpp::getMacroValueString(macros, key, defaultValue)ecmcCpp::getMacroValueInt(macros, key, defaultValue)ecmcCpp::getMacroValueDouble(macros, key, defaultValue)getMacroValue(...) returns an empty string when a key is missing.
getMacroValueString(...) returns the caller-provided default when a key is
missing. The numeric helpers return the caller-provided default when the key is
missing or cannot be parsed. Macro text is split on commas outside quotes, and
optional single or double quotes around values are stripped.
Use appendCppLogicMacros.cmd when a startup file should add more macro text
after the initial load. The appended text is visible through the same
ecmcCpp::getMacrosString() helper.
The public C++ headers are:
ecmcCppLogic.hppecmcCppMotion.hppecmcCppControl.hppecmcCppUtils.hppecmcCppTrace.hppecmcCppPersist.hppFor a helper/header-oriented summary, see C++ Logic Helpers.
Typical user code looks like:
#include "ecmcCppLogic.hpp"
struct MyLogic : public ecmcCpp::LogicBase {
int32_t actual_position {0};
int16_t drive_control {0};
int16_t velocity_setpoint {1000};
MyLogic() {
ecmc.input("ec.s14.positionActual01", actual_position)
.output("ec.s14.driveControl01", drive_control)
.output("ec.s14.velocitySetpoint01", velocity_setpoint);
epics.readOnly("main.actual_position", actual_position)
.writable("main.velocity_setpoint", velocity_setpoint);
}
void run() override {
drive_control = 1;
}
};
ECMC_CPP_LOGIC_REGISTER_DEFAULT(MyLogic)
The main split is:
ecmc...: live realtime bindings to ecmc item namesepics...: values exported on the C++ logic instance’s dedicated asyn portFor normal scalars, the C++ value type is inferred from the bound variable type.
Supported patterns include:
ecmc.input(...)ecmc.output(...)ecmc.inputArray(...)ecmc.outputArray(...)ecmc.inputBytes(...)ecmc.outputBytes(...)ecmc.inputAutoArray(...)ecmc.outputAutoArray(...)On the exported EPICS side, corresponding helpers exist for:
epics.readOnly(...)epics.writable(...)epics.readOnlyArray(...)epics.writableArray(...)epics.readOnlyBytes(...)epics.writableBytes(...)Each loaded cpp_logic instance gets:
epics.* variables on that same portThe built-in core substitutions are loaded from:
$(ecmccfg_DIR)db/generic/ecmcCppLogicCore.substitutions
The generic caQtDM runtime panel is:
$(ecmccfg_DIR)qt/ecmcCppLogic.ui
Open it with macros such as:
caqtdm -macro "IOC=<ioc-name>,CPP_ID=0" $(ecmccfg_DIR)qt/ecmcCppLogic.ui
There is also a compact overview panel for several logic instances:
caqtdm -macro "IOC=<ioc-name>" $(ecmccfg_DIR)qt/ecmcCppLogicOverview.ui
That overview shows logic ids 0..7 and opens one instance in
ecmcCppLogic.ui.
The built-in runtime names currently include:
logic.ctrl.wordlogic.stat.wordlogic.ctrl.rate_mslogic.stat.rate_mslogic.ctrl.update_rate_mslogic.stat.update_rate_mslogic.stat.exec_mslogic.stat.input_mslogic.stat.output_mslogic.stat.total_mslogic.stat.divlogic.stat.countlogic.stat.dbg_txtCurrent control word bits are:
User-defined epics.* exports can be turned into substitutions offline with:
python3 examples/PSI/plugins/cpp_logic/utils/ecmcCppLogicSourceSubstGen.py \
--source src/main.cpp \
--output cpp_logic.subs
The loadCppLogic.cmd wrapper then loads:
cfg/<FILE>_cpp_logic.subs by defaultEPICS_SUBST=EMPTYIn the IOC-style cpp_logic examples, the custom substitutions are normally
generated as:
cfg/libmain.so_cpp_logic.subs
The IOC-style examples generate a simple local caQtDM panel with:
qt/<IOC>_cpp_logic.ui
Typical example flow:
make
make pvs
make ui
make install
For new IOC projects there is also a scaffold helper in the cpp_logic utils
area:
python3 examples/PSI/plugins/cpp_logic/utils/cpp_logic_new_ioc.py <new-dir>
Generated and checked-in startup scripts also include exact caqtdm commands
for:
ecmcCppLogicOverview.uiecmcCppLogic.uiIf you override FILE, keep it as a basename and move the directory into DIR so the default substitutions path stays predictable. Example:
${SCRIPTEXEC} ${ecmccfg_DIR}scripts/loadCppLogic.cmd, \
"DIR=bin/,FILE=libmain.so,REPORT=1"
The cpp_logic execution point is before the safety plugin.
That means user logic can produce values that the safety plugin may still override afterwards, which is the intended order.
IOC-style examples are available in:
examples/PSI/plugins/cpp_logic/
Current example families include:
MC_* style)