From a41e8c69b2e0ffda57a088c1f33779d3b5d0d063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Sandstr=C3=B6m?= Date: Fri, 1 Mar 2024 15:07:38 +0100 Subject: [PATCH] Start from scope plugin --- .gitignore | 16 + GNUmakefile | 47 ++ README.md | 387 ++++++++++++ iocsh/.keep | 0 iocsh/test_plugin_scope.script | 63 ++ qt/.keep | 0 src/Db/.keep | 0 src/Db/ecmcPluginScope.template | 90 +++ src/src/.keep | 0 src/src/ecmcPluginScope.c | 158 +++++ src/src/ecmcScope.cpp | 1002 +++++++++++++++++++++++++++++++ src/src/ecmcScope.h | 131 ++++ src/src/ecmcScopeDefs.h | 28 + src/src/ecmcScopeWrap.cpp | 121 ++++ src/src/ecmcScopeWrap.h | 54 ++ 15 files changed, 2097 insertions(+) create mode 100644 .gitignore create mode 100644 GNUmakefile create mode 100644 README.md create mode 100644 iocsh/.keep create mode 100644 iocsh/test_plugin_scope.script create mode 100644 qt/.keep create mode 100644 src/Db/.keep create mode 100644 src/Db/ecmcPluginScope.template create mode 100644 src/src/.keep create mode 100644 src/src/ecmcPluginScope.c create mode 100644 src/src/ecmcScope.cpp create mode 100644 src/src/ecmcScope.h create mode 100644 src/src/ecmcScopeDefs.h create mode 100644 src/src/ecmcScopeWrap.cpp create mode 100644 src/src/ecmcScopeWrap.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8221a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*~ +*-dev +modules.order +Module.symvers +Mkfile.old +core.* +#* +.#* +\#* +*.local +\#* +.cvsignore +*_old/ +*PVs.list +*-loc/*.Makefile +ecmc_plugin_scope/*.Makefile diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..91a9a9d --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,47 @@ +include /ioc/tools/driver.makefile + +MODULE = ecmc_plugin_daq + +BUILDCLASSES = Linux +ARCH_FILTER = deb10% + +# Run 7.0.6 for now +EXCLUDE_VERSIONS+=3 7.0.5 7.0.6 + +IGNORE_MODULES += asynMotor +IGNORE_MODULES += motorBase + +USR_CXXFLAGS += -std=c++17 +OPT_CXXFLAGS_YES = -O3 + +# dependencies +ECmasterECMC_VERSION = v1.1.0 +# motorECMC_VERSION = 7.0.7-ESS +#ecmc_VERSION = v9.0.1_RC4 +ecmc_VERSION = 9.1.0 + +################################################################################ +# THIS RELATES TO THE EtherCAT MASTER LIBRARY +# IT IS OF PARAMOUNT IMPORTANCE TO LOAD THE PROPER KERNEL MODULE +# ################################################################################ +#USR_LDFLAGS += -lethercat +# +#EC_MASTER_LIB = ${EPICS_MODULES}/ECmasterECMC/${ECmasterECMC_VERSION}/R${EPICSVERSION}/lib/${T_A} +#USR_LDFLAGS += -Wl,-rpath=${EC_MASTER_LIB} +#USR_LDFLAGS += -L ${EC_MASTER_LIB} +# +#BASE_DIR = . +#SRC_DIR = $(BASE_DIR)/src +#DB_DIR = $(BASE_DIR)/Db +# +#SOURCES += $(SRC_DIR)/ecmcPluginSafety.c +#SOURCES += $(SRC_DIR)/ecmcSafetyPlgWrap.cpp +#SOURCES += $(SRC_DIR)/ecmcSS1SafetyGroup.cpp +# +##SOURCES += $(foreach d,${SRC_DIR}, $(wildcard $d/*.c) $(wildcard $d/*.cpp)) +#HEADERS += $(foreach d,${SRC_DIR}, $(wildcard $d/*.h)) +#DBDS += $(foreach d,${SRC_DIR}, $(wildcard $d/*.dbd)) +#SCRIPTS += $(BASE_DIR)/startup.cmd +#SCRIPTS += $(BASE_DIR)/scripts/addSS1Group.cmd +#SCRIPTS += $(BASE_DIR)/scripts/addAxisToSafetyGroup.cmd +#TEMPLATES += $(wildcard $(DB_DIR)/*.template) diff --git a/README.md b/README.md new file mode 100644 index 0000000..05ca7b1 --- /dev/null +++ b/README.md @@ -0,0 +1,387 @@ +e3-ecmcPlugin_Scope +====== +ESS Site-specific EPICS module : ecmcPlugin_Scope + +A shared library with Scope functionalities loadable into ecmc: + +https://github.com/epics-modules/ecmc (or local ess fork https://github.com/icshwi/ecmc). + +Configuration is made through ecmccfg: + +https://github.com/paulscherrerinstitute/ecmccfg (ot local ess fork https://github.com/icshwi/ecmccfg) + + +# Introduction + +The main functionality of this plugin is triggerd sampling of ethercat data from oversampled and timestamped ethercat slaves, like: + +Data: +* EL3702 +* EL3742 +* EL5101-0011 +* ELM3604 +* .... + +Trigger: +* EL1252 +* EL1252-0050 + +## Loading of Scope plugin in ecmc: + +NOTE: "require" can be used to get access to the template file. However, the plugin also needs to be loaded like below. + +A plugin is loaded by the ecmccfg command loadPlugin: +https://github.com/icshwi/ecmccfg/blob/master/scripts/loadPlugin.cmd + +Example: +``` +epicsEnvSet(RESULT_NELM, 1024) +epicsEnvSet(ECMC_PLUGIN_FILNAME,"/home/pi/sources/e3-ecmcPlugin_Scope/ecmcPlugin_Scope-loc/O.7.0.4_linux-arm/libecmcPlugin_Scope.so") +epicsEnvSet(ECMC_PLUGIN_CONFIG,"SOURCE=ec0.s${SLAVE_NUM_AI}.mm.CH1_ARRAY;DBG_PRINT=1;TRIGG=ec0.s${SLAVE_NUM_TRIGG}.CH1_LATCH_POS;SOURCE_NEXTTIME=ec0.s${SLAVE_NUM_AI}.NEXT_TIME;RESULT_ELEMENTS=${RESULT_NELM};") +${SCRIPTEXEC} ${ecmccfg_DIR}loadPlugin.cmd, "PLUGIN_ID=1,FILE=${ECMC_PLUGIN_FILNAME},CONFIG='${ECMC_PLUGIN_CONFIG}', REPORT=1" +epicsEnvUnset(ECMC_PLUGIN_FILNAME) +epicsEnvUnset(ECMC_PLUGIN_CONFIG) +dbLoadRecords("../template/ecmcPluginScope.template","P=$(IOC):,PORT=${ECMC_ASYN_PORT},INDEX=0,RESULT_NELM=${RESULT_NELM},RESULT_DTYP=asynInt16ArrayIn,RESULT_FTVL=SHORT") +epicsEnvUnset(RESULT_NELM, 1024) +``` + +This plugin supports multiple loading. For each load of the plugin a new Scope object will be created. In order to access these plugins, from plc:s or EPICS records, they can be accessed by an index. The first Scope plugin will have index 0. The next loaded Scope plugin will have index 1... + +Note: If another plugin is loaded in between the loading of Scope plugins, it will have no affect on these Scope indexes (so the Scope index is _not_ the same as plugin index). + + +## Configuration: + +Three links to ethercat data needs to be defined: +* Source data +* Source data timestamp +* Trigger timestamp + +All these three links needs to be defined in the plugin startup configuration string. + +Other configurations that can be made: +* Data elements to collect +* Debug printouts +* Enable + + +### Source data (mandatory) + +The source data should normally be a ecmc memmap. + +The source is defined by the "SOURCE" configuration string: +``` +SOURCE=ec0.s2.mm.CH1_ARRAY; +``` + +### Source data timestamp (mandatory) + +In order to know which source data elements that correspond to the trigger value, the oversampled ethercat slaves normally have a pdo that contains the value of the next dc sync time which is the dc timestamp of the next acquired data element. + +The source timestamp is defined by the "SOURCE_NEXTTIME" configuration string: +``` +SOURCE_NEXTTIME=ec0.s2.NEXT_TIME; +``` +This timestamp can be either in 32bit or 64bit format. The "NEXT_TIME" is always considered to be later than the trigger timestamp. + +### Trigger (mandatory) + +The trigger should normally be a timestamped digital input, like EL1252. + +The trigger timestamp is defined by the "TRIGG" configuration string: +``` +TRIGG=ec0.s5.CH1_LATCH_POS; +``` +This timestamp can be either in 32bit or 64bit format. If 32 bits then "NEXT_TIME" is always considered to be later than the trigger timestamp. + +### Data elements to collect (optional) + +The number of values to be collected after the trigger is defined by setting the option "RESULT_ELEMENTS" in the configurations string. The default value is 1024 data elements of the same type as the choosen source. +``` +RESULT_ELEMENTS=2048; +``` + +### Debug printouts (optional) + +Debug printouts can be enbaled/disabled by the option DBG_PRINT (defaults to 0) +``` +DBG_PRINT=1; +``` + +### Enable (optional) + +The acuiring of data can be enabled/disabled by the "ENABLE" option (defaults to 1) +``` +ENABLE=0; +``` + +### Example of complete configuration string +``` +epicsEnvSet(ECMC_PLUGIN_CONFIG,"SOURCE=ec0.s${SLAVE_NUM_AI}.mm.CH1_ARRAY;DBG_PRINT=1;TRIGG=ec0.s${SLAVE_NUM_TRIGG}.CH1_LATCH_POS;SOURCE_NEXTTIME=ec0.s${SLAVE_NUM_AI}.NEXT_TIME;RESULT_ELEMENTS=${RESULT_NELM};") +``` + +## EPICS records +Each Scope plugin object will create a new asyn parameters. +The reason for a dedicated asynport is to disturb ecmc as little as possible. +The plugin contains a template file, "ecmcPluginScope.template", that will make most information availbe from records: + +* Enable (rw) +* Data Source (ro) +* Trigger source (ro) +* Resultdata (ro) + +The available records from this template file can be listed by the cmd: +``` +raspberrypi-15269 > dbgrep *Scope* +IOC_TEST:Plugin-Scope0-MissTriggCntAct +IOC_TEST:Plugin-Scope0-ScanToTriggSamples +IOC_TEST:Plugin-Scope0-TriggCntAct +IOC_TEST:Plugin-Scope0-Enable +IOC_TEST:Plugin-Scope0-DataSource +IOC_TEST:Plugin-Scope0-TriggSource +IOC_TEST:Plugin-Scope0-NextTimeSource +IOC_TEST:Plugin-Scope0-Data-Act +``` + +## GUI + +### ecmcScopeMainGui + +A simple pyqt gui is provided to visualize and control the scope ([GUI](tools/ecmcScopeMainGui.py)). +The gui are included in the ecmccomgui repo: +https://github.com/anderssandstrom/ecmccomgui + +The tool recives data from the EPICS records by pyepics framework. + +Help screen of ecmcScopeMainGui.py +``` +$ python ecmcScopeMainGui.py +ecmcScopeMainGui: Plots waveforms of FFT data (updates on Y data callback). +python ecmcScopeMainGui.py + : Ioc prefix ('IOC_TEST:') + : Id of scope plugin ('0') +example : python ecmcScopeMainGui.py 'IOC_TEST:' '0' +Will connect to Pvs: Plugin-Scope-* + +``` +Example: Start ecmcFFMainTGui.py for: +* predix="IOC_TEST:" +* scopePluginId=0 (the first loaded Scope plugin in the ioc) + +``` +python ecmcScopeMainGui.py 'IOC_TEST:' '0' +``` +![gui.py](docs/gui.png) + +### Needed packages: +* python 3.5 +* epics +* PyQt5 +* numpy +* matplotlib + +## Example script +An example script can be found in the iocsh directory of this repo. + +The example is based on: +* EL3702 for acquiring analog data with a oversampling factor of 100 (100kHz sampling rate) +* EL1252 for timestamped trigger + +The below image shows triggered acquisition in 10Hz of a 100Hz sinus 1Vpp. For each trigger, 500 samples are acquired in 100kHz sample rate and pushed to epics as waveforms (in 10Hz with NELM 500). In total the image shows data from 17 triggers after each other. + +![Example](docs/example.png) + +## Plugin info + +``` +Plugin info: + Index = 1 + Name = ecmcPlugin_Scope + Description = Scope plugin for use with ecmc. + Option description = + DBG_PRINT=<1/0> : Enables/disables printouts from plugin, default = disabled. + SOURCE= : Ec source variable (example: ec0.s1.mm.CH1_ARRAY). + RESULT_ELEMENTS= : Data points to collect, default = 4096. + SOURCE_NEXTTIME= : Ec next sync time for source (example: ec0.s1.NEXTTIME) + TRIGG= : Ec trigg time (example: ec0.s2.LATCH_POS). + ENABLE=<1/0> : Enable data acq, defaults to enabled. + + Filename = /home/dev/projects/e3-ecmcPlugin_Scope/ecmcPlugin_Scope-loc/O.7.0.4_linux-x86_64/libecmcPlugin_Scope.so + Config string = SOURCE=ec0.s35.mm.CH1_ARRAY;DBG_PRINT=0;TRIGG=ec0.s1.CH1_LATCH_POS;SOURCE_NEXTTIME=ec0.s35.NEXT_TIME;RESULT_ELEMENTS=500; + Version = 2 + Interface version = 65536 (ecmc = 65536) + max plc funcs = 64 + max plc func args = 10 + max plc consts = 64 + Construct func = @0x7f28a4eca2a0 + Enter realtime func = @0x7f28a4eca300 + Exit realtime func = @0x7f28a4eca290 + Realtime func = @0x7f28a4eca2f0 + Destruct func = @0x7f28a4eca2c0 + dlhandle = @0x1883050 + Plc functions: + funcs[00]: + Name = "scope_enable(arg0, arg1);" + Desc = scope_enable(index,enable) : Enable/disaable scope[index]. + Arg count = 2 + func = @0x7f28a4eca310 + Plc constants: +``` + +# Troubleshooting + +## Missed triggers +The plugin can not handle new triggers before the acquistion from the previous trigger is completed. Therefore the maximum triggering rate depends on the amount of data that should be acquired. +Therfore, in order to handle higher triggering rates, the result element count might need lowering (see above options). + +Missed triggers can also be a result of bad syncronized dc clocks. If the dc-clocks are syncing properly the value of "NEXT_TIME" should always be after the trigger value (since NEXT_TIME should occur in the future). +The time between NEXT_TIME and trigger can be diagnosed by the "ScanToTriggSamples" pv like in this example (NELM=100): +``` +camonitor IOC_TEST:Plugin-Scope0-ScanToTriggSamples +IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:20.680888 91 +IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:20.703867 118 +IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:20.906862 115 +IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:20.944867 87 +IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:21.019865 102 +IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:21.282872 93 +IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:21.362859 99 +IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:21.470863 102 +``` +The value should always be 0 < value < 2*NELM (NELM = Oversamplefactor or samples per ethercat cycle) which means that the trigger occured up to 2*NELM ago. +If the value is outside these limts the trigger will be rejected. The reason could be badly syncrobized dc-clocks (see below). + +## Slave time syncing + +If the dc time syncronization of the slaves is not working properly then the timestamps from both trigger and analog i/o will drift apart resulting in lost triggers and currupted data. +This could be related to that an old version of the etherlab master is installed and needs to be upgraded. + +NOTE: The dc clocks in the slaves start to syncronize to the reference clocks as soon as the ioc enter runtime. This procedure can take normally a few seconds but can also take minutes depending on how many slaves in the network. During this startup syncronization phase the dc clock of the trigger and the analog input slaves may be out of sync resulting in acquisition of wrong data. + +### Verify dc diagnostics + +#### ethercat master command + +Check ethercat master dc diagnostics by issueing the "etehrcat master" command (when ecmc-ioc is running): + +``` +dev@mcag-epics4 ~ $ ethercat master +Master0 + Phase: Operation + Active: yes + Slaves: 49 + Ethernet devices: + Main: c0:3f:d5:66:24:13 (attached) + Link: UP + Tx frames: 1419695 + Tx bytes: 553305060 + Rx frames: 1419694 + Rx bytes: 553304557 + Tx errors: 0 + Tx frame rate [1/s]: 1000 1000 1000 + Tx rate [KByte/s]: 498.0 498.0 498.0 + Rx frame rate [1/s]: 1000 1000 1000 + Rx rate [KByte/s]: 498.0 498.0 498.0 + Common: + Tx frames: 1419695 + Tx bytes: 553305060 + Rx frames: 1419694 + Rx bytes: 553304557 + Lost frames: 0 + Tx frame rate [1/s]: 1000 1000 1000 + Tx rate [KByte/s]: 498.0 498.0 498.0 + Rx frame rate [1/s]: 1000 1000 1000 + Rx rate [KByte/s]: 498.0 498.0 498.0 + Loss rate [1/s]: 0 -0 0 + Frame loss [%]: 0.0 -0.0 0.0 + Distributed clocks: + Reference clock: Slave 0 + DC reference time: 654699808777655501 + Application time: 654700465153733449 + 2020-09-29 13:14:25.153733449 +``` +The last section of the printout lists some dc information of the master. Check teh following: + +* The "Reference clock" should be assigned to a slave. + +* The "DC reference time" should have a resonable value. + +* The "Application time" should have a resonable value. + +#### ethercat slaves command + +Check the dc diagnostics for both the triggering and analog input slave bu the ethercat slaves command (when ecmc-ioc is running). + +Example: Trigger slave id = 1 + +``` +ev@mcag-epics4 ~ $ ethercat slaves -p1 -v +=== Master 0, Slave 1 === +Device: Main +State: OP +Flag: + +Identity: + Vendor Id: 0x00000002 + Product code: 0x04e43052 + Revision number: 0x00150000 + Serial number: 0x00000000 +DL information: + FMMU bit operation: no + Distributed clocks: yes, 64 bit + DC system time transmission delay: 140 ns +Port Type Link Loop Signal NextSlave RxTime [ns] Diff [ns] NextDc [ns] + 0 EBUS up open yes 0 1224884182 0 140 + 1 EBUS up open yes 2 1224891012 6830 154 + 2 N/A down closed no - - - - + 3 N/C down closed no - - - - +General: + Group: DigIn + Image name: TERM_DI + Order number: EL1252 + Device name: EL1252 2K. Fast Dig. Eingang 24V, 1�s, DC Latch + Flags: + Enable SafeOp: no + Enable notLRW: no + Current consumption: 110 mA +``` + +Example: analog input slave id = 35 + +``` +dev@mcag-epics4 ~ $ ethercat slaves -p35 -v +=== Master 0, Slave 35 === +Device: Main +State: OP +Flag: + +Identity: + Vendor Id: 0x00000002 + Product code: 0x0e763052 + Revision number: 0x00030000 + Serial number: 0x00000000 +DL information: + FMMU bit operation: no + Distributed clocks: yes, 32 bit + DC system time transmission delay: 9088 ns +Port Type Link Loop Signal NextSlave RxTime [ns] Diff [ns] NextDc [ns] + 0 EBUS up open yes 34 1765139745 0 170 + 1 EBUS down closed no - - - - + 2 N/A down closed no - - - - + 3 N/A down closed no - - - - +General: + Group: AnaInFast + Image name: TERM_AI + Order number: EL3702 + Device name: EL3702 2K. Ana. Eingang +/-10V, DIFF, Oversample + Flags: + Enable SafeOp: no + Enable notLRW: no + Current consumption: 200 mA +``` + +Check the following for both slaves: + +* "DC system time transmission delay" is a non zero value (like above) + +* "Distributed clocks" is "yes" and 32 or 64 bit + +If status of the above commands are not according to the examples then the etherlab master probbaly needs to be upgraded/reinstalled. Use the etherlab master repo: https://github.com/icshwi/etherlabmaster/ (or for ESS install via cs-entry.) + diff --git a/iocsh/.keep b/iocsh/.keep new file mode 100644 index 0000000..e69de29 diff --git a/iocsh/test_plugin_scope.script b/iocsh/test_plugin_scope.script new file mode 100644 index 0000000..e98ffaa --- /dev/null +++ b/iocsh/test_plugin_scope.script @@ -0,0 +1,63 @@ +############################################################################## +# Test to make a triggered scope + +############################################################################## +## Initiation: +epicsEnvSet("IOC" ,"$(IOC="IOC_TEST")") +epicsEnvSet("ECMCCFG_INIT" ,"") #Only run startup once (auto at PSI, need call at ESS), variable set to "#" in startup.cmd +epicsEnvSet("SCRIPTEXEC" ,"$(SCRIPTEXEC="iocshLoad")") + +require ecmccfg develop + +# run module startup.cmd (only needed at ESS PSI auto call at require) +$(ECMCCFG_INIT)$(SCRIPTEXEC) ${ecmccfg_DIR}startup.cmd, "IOC=$(IOC),ECMC_VER=develop,stream_VER=2.8.10, EC_RATE=1000" + +############################################################################## +## Config hardware: + +## Sampel rate in milliseconds +epicsEnvSet("ECMC_SAMPLE_RATE_MS", "${RATE="1"}") + +# Choose sample rate (2, 10 or 100) +epicsEnvSet("ECMC_OVER_SMP", "${OVERSAMP="100"}") + +# EL1252: trigger on pos edge timestamp +epicsEnvSet("SLAVE_NUM_TRIGG", "6") +${SCRIPTEXEC} ${ecmccfg_DIR}addSlave.cmd, "SLAVE_ID=$(SLAVE_NUM_TRIGG), HW_DESC=EL1252" + +# EL3702: Note: Set NELM to define oversampling rate +epicsEnvSet("SLAVE_NUM_AI", "11") +${SCRIPTEXEC} ${ecmccfg_DIR}addSlave.cmd, "SLAVE_ID=$(SLAVE_NUM_AI), HW_DESC=EL3702, NELM=${ECMC_OVER_SMP}" + +#ecmcConfigOrDie "Cfg.EcSelectReferenceDC(0,$(SLAVE_NUM_TRIGG))" + +#Apply hardware configuration +ecmcConfigOrDie "Cfg.EcApplyConfig(1)" + +########################################################################s###### +## Load plugin: Scope + +require ecmc_plugin_scope master # to get access to db file.. + +epicsEnvSet(RESULT_NELM, 500) +epicsEnvSet(ECMC_PLUGIN_FILNAME,"${HOME}/epics/base-7.0.4/require/3.3.0/siteMods/ecmc_plugin_scope/master/lib/${EPICS_HOST_ARCH=linux-x86_64}/libecmcPlugin_Scope.so") +epicsEnvSet(ECMC_PLUGIN_CONFIG,"SOURCE=ec0.s${SLAVE_NUM_AI}.mm.CH1_ARRAY;DBG_PRINT=0;TRIGG=ec0.s${SLAVE_NUM_TRIGG}.CH1_LATCH_POS;SOURCE_NEXTTIME=ec0.s${SLAVE_NUM_AI}.NEXT_TIME;RESULT_ELEMENTS=${RESULT_NELM};") +${SCRIPTEXEC} ${ecmccfg_DIR}loadPlugin.cmd, "PLUGIN_ID=1,FILE=${ECMC_PLUGIN_FILNAME},CONFIG='${ECMC_PLUGIN_CONFIG}', REPORT=1" +epicsEnvUnset(ECMC_PLUGIN_FILNAME) +epicsEnvUnset(ECMC_PLUGIN_CONFIG) + +dbLoadRecords("ecmcPluginScope.template","P=$(IOC):,PORT=${ECMC_ASYN_PORT},INDEX=0,RESULT_NELM=${RESULT_NELM},RESULT_DTYP=asynInt16ArrayIn,RESULT_FTVL=SHORT") +epicsEnvUnset(RESULT_NELM, 1024) + +############################################################################## +############# Configure diagnostics: + +ecmcConfigOrDie "Cfg.EcSetDiagnostics(1)" +ecmcConfigOrDie "Cfg.EcEnablePrintouts(0)" +ecmcConfigOrDie "Cfg.EcSetDomainFailedCyclesLimit(100)" + +# go active +$(SCRIPTEXEC) ($(ecmccfg_DIR)setAppMode.cmd) + +iocInit() +dbl > pvs.log diff --git a/qt/.keep b/qt/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/Db/.keep b/src/Db/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/Db/ecmcPluginScope.template b/src/Db/ecmcPluginScope.template new file mode 100644 index 0000000..096487e --- /dev/null +++ b/src/Db/ecmcPluginScope.template @@ -0,0 +1,90 @@ +# Data source +record(waveform,"$(P)Plugin-Scope${INDEX}-DataSource"){ + field(DESC, "Data source name") + field(PINI, "1") + field(DTYP, "asynInt8ArrayIn") + field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt8ArrayIn/plugin.scope${INDEX}.source?") + field(FTVL, "CHAR") + field(NELM, "1024") + field(SCAN, "I/O Intr") + field(TSE, "0") +} + +record(waveform,"$(P)Plugin-Scope${INDEX}-TriggSource"){ + field(DESC, "Trigger source name") + field(PINI, "1") + field(DTYP, "asynInt8ArrayIn") + field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt8ArrayIn/plugin.scope${INDEX}.trigg?") + field(FTVL, "CHAR") + field(NELM, "1024") + field(SCAN, "I/O Intr") + field(TSE, "0") +} + +record(waveform,"$(P)Plugin-Scope${INDEX}-NextTimeSource"){ + field(DESC, "Trigger source name") + field(PINI, "1") + field(DTYP, "asynInt8ArrayIn") + field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt8ArrayIn/plugin.scope${INDEX}.nexttime?") + field(FTVL, "CHAR") + field(NELM, "1024") + field(SCAN, "I/O Intr") + field(TSE, "0") +} + +# result +record(waveform,"$(P)Plugin-Scope${INDEX}-Data-Act"){ + info(asyn:FIFO, "1000") + field(DESC, "Result data") + field(PINI, "1") + field(DTYP, "${RESULT_DTYP}") + field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=-1)/TYPE=${RESULT_DTYP}/plugin.scope${INDEX}.resultdata?") + field(FTVL, "${RESULT_FTVL}") + field(NELM, "${RESULT_NELM}") + field(SCAN, "I/O Intr") + field(TSE, "0") +} + +record(bo,"$(P)Plugin-Scope${INDEX}-Enable"){ + field(DESC, "FFT Enable") + field(DTYP,"asynInt32") + field(OUT, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.scope${INDEX}.enable=") + field(ZNAM,"FALSE") + field(ONAM,"TRUE") + field(DOL, "0") + field(VAL, "0") +} + +record(ai,"$(P)Plugin-Scope${INDEX}-MissTriggCntAct"){ + field(PINI, "1") + field(DESC, "Missed trigger counter") + field(DTYP,"asynInt32") + field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.scope${INDEX}.missed?") + field(SCAN, "I/O Intr") +} + +record(ai,"$(P)Plugin-Scope${INDEX}-ScanToTriggSamples"){ + field(PINI, "1") + field(DESC, "Samples between now and trigger []") + field(DTYP,"asynFloat64") + field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynFloat64/plugin.scope${INDEX}.scantotrigg?") + field(SCAN, "I/O Intr") +} + +record(ai,"$(P)Plugin-Scope${INDEX}-TriggCntAct"){ + field(PINI, "1") + field(DESC, "Trigger counter") + field(DTYP,"asynInt32") + field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.scope${INDEX}.count?") + field(SCAN, "I/O Intr") +} + +#record(bo,"$(P)Plugin-Scope${INDEX}-Trigg"){ +# field(DESC, "FFT Trigg measurement") +# field(DTYP,"asynInt32") +# field(OUT, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.scope${INDEX}.trigg") +# field(ZNAM,"FALSE") +# field(ONAM,"TRUE") +# field(DOL, "0") +# field(VAL, "0") +#} diff --git a/src/src/.keep b/src/src/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/src/ecmcPluginScope.c b/src/src/ecmcPluginScope.c new file mode 100644 index 0000000..c08793c --- /dev/null +++ b/src/src/ecmcPluginScope.c @@ -0,0 +1,158 @@ +/*************************************************************************\ +* Copyright (c) 2019 European Spallation Source ERIC +* ecmc is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +* +* ecmcPluginScope.cpp +* +* Created on: Sept 21, 2020 +* Author: anderssandstrom +* +\*************************************************************************/ + +// Needed to get headers in ecmc right... +#define ECMC_IS_PLUGIN +#define ECMC_EXAMPLE_PLUGIN_VERSION 2 + +#ifdef __cplusplus +extern "C" { +#endif // ifdef __cplusplus + +#include +#include +#include + +#include "ecmcPluginDefs.h" +#include "ecmcScopeDefs.h" +#include "ecmcScopeWrap.h" + +static int lastEcmcError = 0; +static char* lastConfStr = NULL; + +/** Optional. + * Will be called once after successfull load into ecmc. + * Return value other than 0 will be considered error. + * configStr can be used for configuration parameters. + **/ +int scopeConstruct(char *configStr) +{ + //This module is allowed to load several times so no need to check if loaded + + // create FFT object and register data callback + lastConfStr = strdup(configStr); + return createScope(configStr); +} + +/** Optional function. + * Will be called once at unload. + **/ +void scopeDestruct(void) +{ + deleteAllScopes(); + if(lastConfStr){ + free(lastConfStr); + } +} + +/** Optional function. + * Will be called each realtime cycle if definded + * ecmcError: Error code of ecmc. Makes it posible for + * this plugin to react on ecmc errors + * Return value other than 0 will be considered to be an error code in ecmc. + **/ +int scopeRealtime(int ecmcError) +{ + lastEcmcError = ecmcError; + return executeScopes(); +} + +/** Link to data source here since all sources should be availabe at this stage + * (for example ecmc PLC variables are defined only at enter of realtime) + **/ +int scopeEnterRT(){ + return linkDataToScopes(); +} + +/** Optional function. + * Will be called once just before leaving realtime mode + * Return value other than 0 will be considered error. + **/ +int scopeExitRT(void){ + return 0; +} + +// // Plc function for clear of buffers +// double scope_clear(double index) { +// return (double)clearScope((int)index); +// } + +// Plc function for enable +double scope_enable(double index, double enable) { + return (double)enableScope((int)index, (int)enable); +} + +// // Plc function for trigg new measurement (will clear buffers) +// double scope_trigg(double index) { +// return (double)triggScope((int)index); +// } + +// Register data for plugin so ecmc know what to use +struct ecmcPluginData pluginDataDef = { + // Allways use ECMC_PLUG_VERSION_MAGIC + .ifVersion = ECMC_PLUG_VERSION_MAGIC, + // Name + .name = "ecmcPlugin_Scope", + // Description + .desc = "Scope plugin for use with ecmc.", + // Option description + .optionDesc = "\n "ECMC_PLUGIN_DBG_PRINT_OPTION_CMD"<1/0> : Enables/disables printouts from plugin, default = disabled.\n" + " "ECMC_PLUGIN_SOURCE_OPTION_CMD" : Ec source variable (example: ec0.s1.mm.CH1_ARRAY).\n" + " "ECMC_PLUGIN_RESULT_ELEMENTS_OPTION_CMD" : Data points to collect, default = 4096.\n" + " "ECMC_PLUGIN_SOURCE_NEXTTIME_OPTION_CMD" : Ec next sync time for source (example: ec0.s1.NEXTTIME)\n" + " "ECMC_PLUGIN_TRIGG_OPTION_CMD" : Ec trigg time (example: ec0.s2.LATCH_POS).\n" + " "ECMC_PLUGIN_ENABLE_OPTION_CMD"<1/0> : Enable data acq, defaults to enabled.\n" + , + // Plugin version + .version = ECMC_EXAMPLE_PLUGIN_VERSION, + // Optional construct func, called once at load. NULL if not definded. + .constructFnc = scopeConstruct, + // Optional destruct func, called once at unload. NULL if not definded. + .destructFnc = scopeDestruct, + // Optional func that will be called each rt cycle. NULL if not definded. + .realtimeFnc = scopeRealtime, + // Optional func that will be called once just before enter realtime mode + .realtimeEnterFnc = scopeEnterRT, + // Optional func that will be called once just before exit realtime mode + .realtimeExitFnc = scopeExitRT, + // PLC funcs + .funcs[0] = + { /*----fft_clear----*/ + // Function name (this is the name you use in ecmc plc-code) + .funcName = "scope_enable", + // Function description + .funcDesc = "scope_enable(index,enable) : Enable/disaable scope[index].", + /** + * 12 different prototypes allowed (only doubles since reg in plc). + * Only funcArg${argCount} func shall be assigned the rest set to NULL. + **/ + .funcArg0 = NULL, + .funcArg1 = NULL, + .funcArg2 = scope_enable, + .funcArg3 = NULL, + .funcArg4 = NULL, + .funcArg5 = NULL, + .funcArg6 = NULL, + .funcArg7 = NULL, + .funcArg8 = NULL, + .funcArg9 = NULL, + .funcArg10 = NULL, + .funcGenericObj = NULL, + }, + .consts[0] = {0}, // last element set all to zero.. +}; + +ecmc_plugin_register(pluginDataDef); + +# ifdef __cplusplus +} +# endif // ifdef __cplusplus diff --git a/src/src/ecmcScope.cpp b/src/src/ecmcScope.cpp new file mode 100644 index 0000000..fd1cefc --- /dev/null +++ b/src/src/ecmcScope.cpp @@ -0,0 +1,1002 @@ +/*************************************************************************\ +* Copyright (c) 2019 European Spallation Source ERIC +* ecmc is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +* +* ecmcScope.cpp +* +* Created on: Sept 22, 2020 +* Author: anderssandstrom +* Credits to https://github.com/sgreg/dynamic-loading +* +\*************************************************************************/ + +// Needed to get headers in ecmc right... +#define ECMC_IS_PLUGIN + +#define ECMC_PLUGIN_ASYN_PREFIX "plugin.scope" +#define ECMC_PLUGIN_ASYN_ENABLE "enable" +#define ECMC_PLUGIN_ASYN_RESULTDATA "resultdata" +#define ECMC_PLUGIN_ASYN_SCOPE_SOURCE "source" +#define ECMC_PLUGIN_ASYN_SCOPE_TRIGG "trigg" +#define ECMC_PLUGIN_ASYN_SCOPE_NEXT_SYNC "nexttime" +#define ECMC_PLUGIN_ASYN_MISSED "missed" +#define ECMC_PLUGIN_ASYN_TRIGG_COUNT "count" +#define ECMC_PLUGIN_ASYN_SCAN_TO_TRIGG_OFFSET "scantotrigg" + + +#define SCOPE_DBG_PRINT(str) \ +if(cfgDbgMode_) { \ + printf(str); \ +} \ + +#define ECMC_MAX_32BIT 0xFFFFFFFF + +#include +#include "ecmcScope.h" +#include "ecmcPluginClient.h" +#include + +/** ecmc Scope class + * This object can throw: + * - bad_alloc + * - invalid_argument + * - runtime_error +*/ +ecmcScope::ecmcScope(int scopeIndex, // index of this object (if several is created) + char* configStr){ + SCOPE_DBG_PRINT("ecmcScope::ecmcScope()"); + cfgDataSourceStr_ = NULL; + cfgDataNexttimeStr_ = NULL; + cfgTriggStr_ = NULL; + resultDataBuffer_ = NULL; + lastScanSourceDataBuffer_ = NULL; + missedTriggs_ = 0; + triggerCounter_ = 0; + objectId_ = scopeIndex; + triggOnce_ = 0; + dataSourceLinked_ = 0; + resultDataBufferBytes_ = 0; + bytesInResultBuffer_ = 0; + oldTriggTime_ = 0; + triggTime_ = 0; + sourceNexttime_ = 0; + sourceSampleRateNS_ = 0; + sourceElementsPerSample_ = 0; + firstTrigg_ = 1; // Avoid first trigger (0 timestamp..) + scopeState_ = ECMC_SCOPE_STATE_INVALID; + ecmcSmapleTimeNS_ = (uint64_t)getEcmcSampleTimeMS()*1E6; + samplesSinceLastTrigg_ = 0; + + // Asyn + sourceStrParam_ = NULL; + sourceNexttimeStrParam_ = NULL; + triggStrParam_ = NULL; + enbaleParam_ = NULL; + resultParam_ = NULL; + asynMissedTriggs_ = NULL; + asynTriggerCounter_ = NULL; + asynTimeTrigg2Sample_ = NULL; + + // ecmcDataItems + sourceDataItem_ = NULL; + sourceDataNexttimeItem_ = NULL; + sourceTriggItem_ = NULL; + + sourceDataItemInfo_ = NULL; + sourceDataNexttimeItemInfo_ = NULL; + sourceTriggItemInfo_ = NULL; + + // Config defaults + cfgDbgMode_ = 0; + cfgBufferElementCount_ = ECMC_PLUGIN_DEFAULT_BUFFER_SIZE; + cfgEnable_ = 1; // start enabled (enable over asyn) + + parseConfigStr(configStr); // Assigns all configs + + // Check valid buffer size + if(cfgBufferElementCount_ <= 0) { + SCOPE_DBG_PRINT("ERROR: Configuration buffer size must be > 0."); + throw std::out_of_range("ERROR: Configuration Buffer Size must be > 0."); + } + + // Allocate buffers first at enter RT (since datatype is unknown here) + resultDataBuffer_ = NULL; + resultDataBufferBytes_ = 0; +} + +ecmcScope::~ecmcScope() { + + if(resultDataBuffer_) { + delete[] resultDataBuffer_; + } + + if(lastScanSourceDataBuffer_) { + delete[] lastScanSourceDataBuffer_; + } + + if(cfgDataSourceStr_) { + free(cfgDataSourceStr_); + } + if(cfgTriggStr_) { + free(cfgTriggStr_); + } + if(cfgDataNexttimeStr_) { + free(cfgDataNexttimeStr_); + } + +} + +void ecmcScope::parseConfigStr(char *configStr) { + SCOPE_DBG_PRINT("ecmcScope::parseConfigStr()"); + // check config parameters + if (configStr && configStr[0]) { + char *pOptions = strdup(configStr); + char *pThisOption = pOptions; + char *pNextOption = pOptions; + + while (pNextOption && pNextOption[0]) { + pNextOption = strchr(pNextOption, ';'); + if (pNextOption) { + *pNextOption = '\0'; /* Terminate */ + pNextOption++; /* Jump to (possible) next */ + } + + // ECMC_PLUGIN_DBG_PRINT_OPTION_CMD (1/0) + if (!strncmp(pThisOption, ECMC_PLUGIN_DBG_PRINT_OPTION_CMD, strlen(ECMC_PLUGIN_DBG_PRINT_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_DBG_PRINT_OPTION_CMD); + cfgDbgMode_ = atoi(pThisOption); + } + + // ECMC_PLUGIN_SOURCE_OPTION_CMD (Source string) + else if (!strncmp(pThisOption, ECMC_PLUGIN_SOURCE_OPTION_CMD, strlen(ECMC_PLUGIN_SOURCE_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_SOURCE_OPTION_CMD); + cfgDataSourceStr_=strdup(pThisOption); + } + + // ECMC_PLUGIN_RESULT_ELEMENTS_OPTION_CMD (1/0) + else if (!strncmp(pThisOption, ECMC_PLUGIN_RESULT_ELEMENTS_OPTION_CMD, strlen(ECMC_PLUGIN_RESULT_ELEMENTS_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_RESULT_ELEMENTS_OPTION_CMD); + cfgBufferElementCount_ = atoi(pThisOption); + } + + // ECMC_PLUGIN_ENABLE_OPTION_CMD (1/0) + else if (!strncmp(pThisOption, ECMC_PLUGIN_ENABLE_OPTION_CMD, strlen(ECMC_PLUGIN_ENABLE_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_ENABLE_OPTION_CMD); + cfgEnable_ = atoi(pThisOption); + } + + // ECMC_PLUGIN_TRIGG_OPTION_CMD (string) + else if (!strncmp(pThisOption, ECMC_PLUGIN_TRIGG_OPTION_CMD, strlen(ECMC_PLUGIN_TRIGG_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_TRIGG_OPTION_CMD); + cfgTriggStr_ = strdup(pThisOption); + } + + // ECMC_PLUGIN_SOURCE_NEXTTIME_OPTION_CMD (string) + else if (!strncmp(pThisOption, ECMC_PLUGIN_SOURCE_NEXTTIME_OPTION_CMD, strlen(ECMC_PLUGIN_SOURCE_NEXTTIME_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_SOURCE_NEXTTIME_OPTION_CMD); + cfgDataNexttimeStr_ = strdup(pThisOption); + } + + + // ECMC_PLUGIN_MODE_OPTION_CMD CONT/TRIGG + // else if (!strncmp(pThisOption, ECMC_PLUGIN_MODE_OPTION_CMD, strlen(ECMC_PLUGIN_MODE_OPTION_CMD))) { + // pThisOption += strlen(ECMC_PLUGIN_MODE_OPTION_CMD); + // if(!strncmp(pThisOption, ECMC_PLUGIN_MODE_CONT_OPTION,strlen(ECMC_PLUGIN_MODE_CONT_OPTION))){ + // cfgMode_ = CONT; + // } + // if(!strncmp(pThisOption, ECMC_PLUGIN_MODE_TRIGG_OPTION,strlen(ECMC_PLUGIN_MODE_TRIGG_OPTION))){ + // cfgMode_ = TRIGG; + // } + // } + + pThisOption = pNextOption; + } + free(pOptions); + } + + // Data source must be defined... + if(!cfgDataSourceStr_) { + SCOPE_DBG_PRINT("ERROR: Configuration Data source not defined.\n"); + throw std::invalid_argument( "ERROR: Data source not defined."); + } + if(!cfgTriggStr_) { + SCOPE_DBG_PRINT("ERROR: Configuration Trigger not defined.\n"); + throw std::invalid_argument( "ERROR: Configuration Trigger not defined."); + } + if(!cfgDataNexttimeStr_) { + SCOPE_DBG_PRINT("ERROR: Configuration Nexttime not defined.\n"); + throw std::invalid_argument( "ERROR: Configuration Nexttime not defined."); + } +} + +void ecmcScope::connectToDataSources() { + SCOPE_DBG_PRINT("ecmcScope::connectToDataSources()"); + /* Check if already linked (one call to connectToDataSources (enterRT) per loaded Scope lib (Scope object)) + But link should only happen once!!*/ + if( dataSourceLinked_ ) { + return; + } + + // Get source dataItem + sourceDataItem_ = (ecmcDataItem*) getEcmcDataItem(cfgDataSourceStr_); + if(!sourceDataItem_) { + SCOPE_DBG_PRINT("ERROR: Source dataitem NULL.\n"); + throw std::runtime_error( "ERROR: Source dataitem NULL." ); + } + sourceDataItemInfo_ = sourceDataItem_->getDataItemInfo(); + + if(!sourceDataItemInfo_) { + SCOPE_DBG_PRINT("ERROR: Source dataitem info NULL.\n"); + throw std::runtime_error( "ERROR: Source dataitem info NULL." ); + } + + // Allocate buffer for result + resultDataBuffer_ = new uint8_t[cfgBufferElementCount_ * sourceDataItemInfo_->dataElementSize]; + memset(&resultDataBuffer_[0],0,cfgBufferElementCount_ * sourceDataItemInfo_->dataElementSize); + resultDataBufferBytes_ = cfgBufferElementCount_ * sourceDataItemInfo_->dataElementSize; + // Data for last scan cycle + lastScanSourceDataBuffer_ = new uint8_t[sourceDataItemInfo_->dataSize]; + memset(&lastScanSourceDataBuffer_[0],0,sourceDataItemInfo_->dataSize); + sourceElementsPerSample_ = sourceDataItemInfo_->dataSize / sourceDataItemInfo_->dataElementSize; + sourceSampleRateNS_ = ecmcSmapleTimeNS_ / sourceElementsPerSample_; + + // Get source nexttime dataItem + sourceDataNexttimeItem_ = (ecmcDataItem*) getEcmcDataItem(cfgDataNexttimeStr_); + if(!sourceDataNexttimeItem_) { + SCOPE_DBG_PRINT("ERROR: Source nexttime dataitem NULL.\n"); + throw std::runtime_error( "ERROR: Source nexttime dataitem NULL." ); + } + sourceDataNexttimeItemInfo_ = sourceDataNexttimeItem_->getDataItemInfo(); + + if(!sourceDataNexttimeItemInfo_) { + SCOPE_DBG_PRINT("ERROR: Source nexttime dataitem info NULL.\n"); + throw std::runtime_error( "ERROR: Source nexttime dataitem info NULL." ); + } + + // Get trigg dataItem + sourceTriggItem_ = (ecmcDataItem*) getEcmcDataItem(cfgTriggStr_); + if(!sourceTriggItem_) { + SCOPE_DBG_PRINT("ERROR: Trigg dataitem NULL.\n"); + throw std::runtime_error( "ERROR: Trigg dataitem NULL." ); + } + + sourceTriggItemInfo_ = sourceTriggItem_->getDataItemInfo(); + if(!sourceTriggItemInfo_) { + SCOPE_DBG_PRINT("ERROR: Trigg dataitem info NULL.\n"); + throw std::runtime_error( "ERROR: Trigg dataitem info NULL." ); + } + + if( sourceTriggItem_->read((uint8_t*)(&oldTriggTime_),sourceTriggItemInfo_->dataElementSize)){ + SCOPE_DBG_PRINT("ERROR: Failed read trigg time.\n"); + throw std::runtime_error( "ERROR: Failed read trigg time." ); + } + + if(!sourceDataTypeSupported(sourceDataItem_->getEcmcDataType())) { + SCOPE_DBG_PRINT("ERROR: Source data type not suppported.\n"); + throw std::runtime_error( "ERROR: Source data type not suppported."); + } + + // Register asyn parameters + initAsyn(); + + dataSourceLinked_ = 1; + scopeState_ = ECMC_SCOPE_STATE_WAIT_TRIGG; +} + +bool ecmcScope::sourceDataTypeSupported(ecmcEcDataType dt) { + + SCOPE_DBG_PRINT("ecmcScope::sourceDataTypeSupported()"); + + switch(dt) { + case ECMC_EC_NONE: + return 0; + break; + case ECMC_EC_B1: + return 0; + break; + case ECMC_EC_B2: + return 0; + break; + case ECMC_EC_B3: + return 0; + break; + case ECMC_EC_B4: + return 0; + break; + default: + return 1; + break; + } + return 1; +} + +/** + * Note: The code needs to handle triggers in the current and the past ethercat scan. + * If the trigger is newer than "NEXT_TIME" then the dc clocks must be out of sync (see readme) + * The analog samples from the prev cycles is always buffered to be able to also handle older timestamps (up to 2*NELM back in time) +*/ +void ecmcScope::execute() { + + size_t bytesToCp = 0; + + // Ensure ethercat bus is started + if(getEcmcEpicsIOCState() < 15) { + bytesInResultBuffer_ = 0; + scopeState_ = ECMC_SCOPE_STATE_WAIT_TRIGG; + // Wait for new trigg + setWaitForNextTrigg(); + return; + } + + // Read trigg data + if( sourceTriggItem_->read((uint8_t*)&triggTime_,sourceTriggItemInfo_->dataElementSize)){ + SCOPE_DBG_PRINT("ERROR: Failed read trigg time.\n"); + throw std::runtime_error( "ERROR: Failed read trigg time." ); + } + + // Read next sync timestamp + if( sourceDataNexttimeItem_->read((uint8_t*)&sourceNexttime_,sourceDataNexttimeItemInfo_->dataElementSize)){ + SCOPE_DBG_PRINT("ERROR: Failed read ai nexttime.\n"); + throw std::runtime_error( "ERROR: Failed read nexttime." ); + } + + // Ensure enabled + if(!cfgEnable_) { + bytesInResultBuffer_ = 0; + scopeState_ = ECMC_SCOPE_STATE_WAIT_TRIGG; + // Wait for new trigg + setWaitForNextTrigg(); + return; + } + + switch(scopeState_) { + case ECMC_SCOPE_STATE_INVALID: + SCOPE_DBG_PRINT("ERROR: Invalid state (state = ECMC_SCOPE_STATE_INVALID)."); + SCOPE_DBG_PRINT("INFO: Change state to ECMC_SCOPE_STATE_WAIT_TRIGG.\n"); + bytesInResultBuffer_ = 0; + scopeState_ = ECMC_SCOPE_STATE_WAIT_TRIGG; + // Wait for new trigg + setWaitForNextTrigg(); + return; + break; + + case ECMC_SCOPE_STATE_WAIT_TRIGG: + + // New trigger then collect data (or wait ) + if(oldTriggTime_ != triggTime_ && !firstTrigg_) { + //printf("sourceNexttime_=%" PRIu64 " ,sourceDataNexttimeItemInfo_->dataSize = %zu\n",sourceNexttime_,sourceDataNexttimeItemInfo_->dataSize); + + // calculate how many samples ago trigger occured + samplesSinceLastTrigg_ = timeDiff() / sourceSampleRateNS_; + asynTimeTrigg2Sample_->refreshParam(1); + + if( samplesSinceLastTrigg_ > sourceElementsPerSample_ * 2 || samplesSinceLastTrigg_ < 0) { + SCOPE_DBG_PRINT("WARNING: Invalid trigger (occured more than two ethercat cycles ago or in future).."); + missedTriggs_++; + asynMissedTriggs_->refreshParam(1); + // Wait for new trigg (skip this trigger) + setWaitForNextTrigg(); + return; + } + + // printf("samplesSinceLastTrigg_=%lf\n",samplesSinceLastTrigg_); + + SCOPE_DBG_PRINT("INFO: New trigger detected.\n"); + + // Copy from last scan buffer if needed (if trigger occured during last scan) + if(samplesSinceLastTrigg_ > sourceElementsPerSample_) { + bytesToCp = (samplesSinceLastTrigg_ - sourceElementsPerSample_) * sourceDataItemInfo_->dataElementSize; + if(resultDataBufferBytes_ < bytesToCp) { + bytesToCp = resultDataBufferBytes_; + } + size_t startByte = (sourceElementsPerSample_*2-samplesSinceLastTrigg_) * sourceDataItemInfo_->dataElementSize; + + + memcpy( &resultDataBuffer_[0], &lastScanSourceDataBuffer_[startByte], bytesToCp); + bytesInResultBuffer_ = bytesToCp; + } + + // Copy from current scan if needed + if(bytesInResultBuffer_ < resultDataBufferBytes_) { + bytesToCp = sourceDataItemInfo_->dataSize; + // Ensure not to much data is copied + if(bytesToCp > (resultDataBufferBytes_ - bytesInResultBuffer_)) { + bytesToCp = resultDataBufferBytes_ - bytesInResultBuffer_; + } + + // Write directtly into results buffer + if( sourceDataItem_->read((uint8_t*)&resultDataBuffer_[bytesInResultBuffer_],bytesToCp)){ + SCOPE_DBG_PRINT("ERROR: Failed read data source.\n"); + throw std::runtime_error( "ERROR: Failed read data source." ); + } + bytesInResultBuffer_ += bytesToCp; + } + + // If more data is needed the go to collect state. + if(bytesInResultBuffer_ < resultDataBufferBytes_) { + // Fill more data from next scan + scopeState_ = ECMC_SCOPE_STATE_COLLECT; + } + else { // The data from current scan was enough. send over asyn and then start over (wait for next trigger) + resultParam_->refreshParam(1); + bytesInResultBuffer_ = 0; + + triggerCounter_++; + asynTriggerCounter_->refreshParam(1); + + SCOPE_DBG_PRINT("INFO: Result Buffer full. Data push over asyn..\n"); + + if(cfgDbgMode_) { + printEcDataArray(resultDataBuffer_,resultDataBufferBytes_,sourceDataItemInfo_->dataType,objectId_); + } + } + } + + // Avoid first rubbish trigger timestamp (when first value is read from bus it will differ from "0" and therefor trigger) + if(oldTriggTime_ != triggTime_) { + firstTrigg_ = 0; + } + + // This trigg is handled. Wait for next trigger + setWaitForNextTrigg(); + + break; + + case ECMC_SCOPE_STATE_COLLECT: + + if (oldTriggTime_ != triggTime_) { + SCOPE_DBG_PRINT("WARNING: Latch during sampling of data. This trigger will be disregarded.\n"); + setWaitForNextTrigg(); + missedTriggs_++; + asynMissedTriggs_->refreshParam(1); + } + + // Ensure not to much data is copied + if(bytesInResultBuffer_ < resultDataBufferBytes_) { + bytesToCp = sourceDataItemInfo_->dataSize; + if(bytesToCp > (resultDataBufferBytes_ - bytesInResultBuffer_)) { + bytesToCp = resultDataBufferBytes_ - bytesInResultBuffer_; + } + + // Write directtly into results buffer + if( sourceDataItem_->read((uint8_t*)&resultDataBuffer_[bytesInResultBuffer_],bytesToCp)){ + SCOPE_DBG_PRINT("ERROR: Failed read data source..\n"); + throw std::runtime_error( "ERROR: Failed read data source." ); + } + bytesInResultBuffer_ += bytesToCp; + } + + if(bytesInResultBuffer_ >= resultDataBufferBytes_) { + resultParam_->refreshParam(1); + bytesInResultBuffer_ = 0; + triggerCounter_++; + asynTriggerCounter_->refreshParam(1); + scopeState_ = ECMC_SCOPE_STATE_WAIT_TRIGG; + // Wait for next trigger. + setWaitForNextTrigg(); + SCOPE_DBG_PRINT("INFO: Change state to ECMC_SCOPE_STATE_WAIT_TRIGG.\n"); + SCOPE_DBG_PRINT("INFO: Result Buffer full. Data push over asyn..\n"); + if(cfgDbgMode_) { + printEcDataArray(resultDataBuffer_,resultDataBufferBytes_,sourceDataItemInfo_->dataType,objectId_); + } + } + + // Wait for next trigger. + setWaitForNextTrigg(); + + break; + default: + SCOPE_DBG_PRINT("ERROR: Invalid state (state = default)."); + bytesInResultBuffer_ = 0; + scopeState_ = ECMC_SCOPE_STATE_WAIT_TRIGG; + // Wait for new trigg + setWaitForNextTrigg(); + return; + + return; + break; + } + + // Read source data to last scan buffer (only one "old" scan seems to be needed) + if( sourceDataItem_->read((uint8_t*)&lastScanSourceDataBuffer_[0],sourceDataItemInfo_->dataSize)){ + SCOPE_DBG_PRINT("ERROR: Failed read data source..\n"); + throw std::runtime_error( "ERROR: Failed source data." ); + } + +} + +/** Calculate depending on bits (32 or 64 bit dc) + * If one is 32 bit then only compare lower 32 bits + * sourceDataNexttimeItemInfo_ is always considered to happen in the future (after trigg) + * If 32 bit registers then it can max be 2^32 ns between trigg and nexttime (approx 4s). +*/ +int64_t ecmcScope::timeDiff() { + // retrun time from trigg to next + int64_t retVal = 0; + if(sourceTriggItemInfo_->dataBitCount < 64 || sourceDataNexttimeItemInfo_->dataBitCount < 64) { + // use only 32bit dc info + uint32_t trigg = getUint32((uint8_t*)&triggTime_); + uint32_t next = getUint32((uint8_t*)&sourceNexttime_); + + // Overflow... always report shortest timediff + if (std::abs( ((int64_t)next)-((int64_t)trigg)) > (int64_t)(ECMC_MAX_32BIT / 2)) { + if(next > trigg) { + retVal = -(((int64_t)trigg) + ECMC_MAX_32BIT - ((int64_t)next)); + //if(std::abs(retVal)>ECMC_MAX_32BIT/2) printf("Overflow 1! %" PRId64 "\n", retVal); + } + else { + retVal = ((int64_t)next) + ECMC_MAX_32BIT - ((int64_t)trigg); + //if(std::abs(retVal)>ECMC_MAX_32BIT/2) printf("Overflow 2! %" PRId64 "\n", retVal); + } + } + else { + retVal = ((int64_t)next)-((int64_t)trigg); + //if(std::abs(retVal)>ECMC_MAX_32BIT/2) printf("timediff 3! %" PRId64 "\n", retVal); + } + } + else { + // Both are 64 bit dc timestamps + retVal = sourceNexttime_ - triggTime_; + //if(std::abs(retVal)>ECMC_MAX_32BIT/2) printf("timediff 4! %" PRId64 "\n", retVal); + } + + return retVal; +} + +void ecmcScope::printEcDataArray(uint8_t* data, + size_t size, + ecmcEcDataType dt, + int objId) { + printf("INFO: Scope id: %d, data: ",objId); + + size_t dataElementSize = getEcDataTypeByteSize(dt); + + uint8_t *pData = data; + for(unsigned int i = 0; i < size / dataElementSize; ++i) { + if(i % 10 == 0) { + printf("\n"); + } else { + printf(", "); + } + switch(dt) { + case ECMC_EC_U8: + printf("%hhu",getUint8(pData)); + break; + case ECMC_EC_S8: + printf("%hhd",getInt8(pData)); + break; + case ECMC_EC_U16: + printf("%hu",getUint16(pData)); + break; + case ECMC_EC_S16: + printf("%hd",getInt16(pData)); + break; + case ECMC_EC_U32: + printf("%u",getUint32(pData)); + break; + case ECMC_EC_S32: + printf("%d",getInt32(pData)); + break; + case ECMC_EC_U64: + printf("%" PRIu64 "",getInt64(pData)); + break; + case ECMC_EC_S64: + printf("%" PRId64 "",getInt64(pData)); + break; + case ECMC_EC_F32: + printf("%f",getFloat32(pData)); + break; + case ECMC_EC_F64: + printf("%lf",getFloat64(pData)); + break; + default: + break; + } + + pData += dataElementSize; + } + printf("\n"); +} + +uint8_t ecmcScope::getUint8(uint8_t* data) { + return *data; +} + +int8_t ecmcScope::getInt8(uint8_t* data) { + int8_t* p=(int8_t*)data; + return *p; +} + +uint16_t ecmcScope::getUint16(uint8_t* data) { + uint16_t* p=(uint16_t*)data; + return *p; +} + +int16_t ecmcScope::getInt16(uint8_t* data) { + int16_t* p=(int16_t*)data; + return *p; +} + +uint32_t ecmcScope::getUint32(uint8_t* data) { + uint32_t* p=(uint32_t*)data; + return *p; +} + +int32_t ecmcScope::getInt32(uint8_t* data) { + int32_t* p=(int32_t*)data; + return *p; +} + +uint64_t ecmcScope::getUint64(uint8_t* data) { + uint64_t* p=(uint64_t*)data; + return *p; +} + +int64_t ecmcScope::getInt64(uint8_t* data) { + int64_t* p=(int64_t*)data; + return *p; +} + +float ecmcScope::getFloat32(uint8_t* data) { + float* p=(float*)data; + return *p; +} + +double ecmcScope::getFloat64(uint8_t* data) { + double* p=(double*)data; + return *p; +} + +size_t ecmcScope::getEcDataTypeByteSize(ecmcEcDataType dt){ + switch(dt) { + case ECMC_EC_NONE: + return 0; + break; + + case ECMC_EC_B1: + return 1; + break; + + case ECMC_EC_B2: + return 1; + break; + + case ECMC_EC_B3: + return 1; + break; + + case ECMC_EC_B4: + return 1; + break; + + case ECMC_EC_U8: + return 1; + break; + + case ECMC_EC_S8: + return 1; + break; + + case ECMC_EC_U16: + return 2; + break; + + case ECMC_EC_S16: + return 2; + break; + + case ECMC_EC_U32: + return 4; + break; + + case ECMC_EC_S32: + return 4; + break; + + case ECMC_EC_U64: + return 8; + break; + + case ECMC_EC_S64: + return 8; + break; + + case ECMC_EC_F32: + return 4; + break; + + case ECMC_EC_F64: + return 8; + break; + + default: + return 0; + break; + } + + return 0; +} + +void ecmcScope::initAsyn() { + + ecmcAsynPortDriver *ecmcAsynPort = (ecmcAsynPortDriver *)getEcmcAsynPortDriver(); + if(!ecmcAsynPort) { + SCOPE_DBG_PRINT("ERROR: ecmcAsynPort NULL."); + throw std::runtime_error( "ERROR: ecmcAsynPort NULL." ); + } + + // Add resultdata "plugin.scope%d.resultdata" + std::string paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_RESULTDATA; + asynParamType asynType = getResultAsynDTFromEcDT(sourceDataItemInfo_->dataType); + + if(asynType == asynParamNotDefined) { + SCOPE_DBG_PRINT("ERROR: ecmc data type not supported for param."); + throw std::runtime_error( "ERROR: ecmc data type not supported for param: " + paramName); + } + + resultParam_ = ecmcAsynPort->addNewAvailParam( + paramName.c_str(), // name + asynType, // asyn type + resultDataBuffer_, // pointer to data + resultDataBufferBytes_,// size of data + sourceDataItemInfo_->dataType, // ecmc data type + 0); // die if fail + + if(!resultParam_) { + SCOPE_DBG_PRINT("ERROR: Failed create asyn param for result."); + throw std::runtime_error( "ERROR: Failed create asyn param for result: " + paramName); + } + + resultParam_->setAllowWriteToEcmc(false); // read only + resultParam_->refreshParam(1); // read once into asyn param lib + ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR); + + // Add enable "plugin.scope%d.enable" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_ENABLE; + + enbaleParam_ = ecmcAsynPort->addNewAvailParam( + paramName.c_str(), // name + asynParamInt32, // asyn type + (uint8_t*)&cfgEnable_, // pointer to data + sizeof(cfgEnable_), // size of data + ECMC_EC_S32, // ecmc data type + 0); // die if fail + + if(!enbaleParam_) { + SCOPE_DBG_PRINT("ERROR: Failed create asyn param for enable."); + throw std::runtime_error( "ERROR: Failed create asyn param for enable: " + paramName); + } + + enbaleParam_->setAllowWriteToEcmc(true); + enbaleParam_->refreshParam(1); // read once into asyn param lib + ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR); + + // Add missed triggers "plugin.scope%d.missed" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_MISSED; + + asynMissedTriggs_ = ecmcAsynPort->addNewAvailParam( + paramName.c_str(), // name + asynParamInt32, // asyn type + (uint8_t*)&missedTriggs_, // pointer to data + sizeof(missedTriggs_), // size of data + ECMC_EC_S32, // ecmc data type + 0); // die if fail + + if(!asynMissedTriggs_) { + SCOPE_DBG_PRINT("ERROR: Failed create asyn param for missed trigg counter."); + throw std::runtime_error( "ERROR: Failed create asyn param for missed trigg counter: " + paramName); + } + + asynMissedTriggs_->setAllowWriteToEcmc(false); + asynMissedTriggs_->refreshParam(1); // read once into asyn param lib + ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR); + + // Add trigger counter "plugin.scope%d.count" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_TRIGG_COUNT; + + asynTriggerCounter_ = ecmcAsynPort->addNewAvailParam( + paramName.c_str(), // name + asynParamInt32, // asyn type + (uint8_t*)&triggerCounter_, // pointer to data + sizeof(triggerCounter_), // size of data + ECMC_EC_S32, // ecmc data type + 0); // die if fail + + if(!asynTriggerCounter_) { + SCOPE_DBG_PRINT("ERROR: Failed create asyn param for trigg counter."); + throw std::runtime_error( "ERROR: Failed create asyn param for trigg counter: " + paramName); + } + + asynTriggerCounter_->setAllowWriteToEcmc(false); + asynTriggerCounter_->refreshParam(1); // read once into asyn param lib + ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR); + + // Add trigger counter "plugin.scope%d.scantotrigg" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_SCAN_TO_TRIGG_OFFSET; + + asynTimeTrigg2Sample_ = ecmcAsynPort->addNewAvailParam( + paramName.c_str(), // name + asynParamFloat64, // asyn type + (uint8_t*)&samplesSinceLastTrigg_, // pointer to data + sizeof(samplesSinceLastTrigg_), // size of data + ECMC_EC_S64, // ecmc data type + 0); // die if fail + + if(!asynTimeTrigg2Sample_) { + SCOPE_DBG_PRINT("ERROR: Failed create asyn param for time trigg to sample."); + throw std::runtime_error( "ERROR: Failed create asyn param for time trigg to sample: " + paramName); + } + + asynTimeTrigg2Sample_->addSupportedAsynType(asynParamFloat64); + asynTimeTrigg2Sample_->setAllowWriteToEcmc(false); + asynTimeTrigg2Sample_->refreshParam(1); // read once into asyn param lib + ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR); + + // Add enable "plugin.scope%d.source" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_SCOPE_SOURCE; + + sourceStrParam_ = ecmcAsynPort->addNewAvailParam( + paramName.c_str(), // name + asynParamInt8Array, // asyn type + (uint8_t*)cfgDataSourceStr_,// pointer to data + strlen(cfgDataSourceStr_), // size of data + ECMC_EC_U8, // ecmc data type + 0); // die if fail + + if(!sourceStrParam_) { + SCOPE_DBG_PRINT("ERROR: Failed create asyn param for data source."); + throw std::runtime_error( "ERROR: Failed create asyn param for data source: " + paramName); + } + + sourceStrParam_->setAllowWriteToEcmc(false); // read only + sourceStrParam_->refreshParam(1); // read once into asyn param lib + ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR); + + // Add enable "plugin.scope%d.trigg" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_SCOPE_TRIGG; + + triggStrParam_ = ecmcAsynPort->addNewAvailParam( + paramName.c_str(), // name + asynParamInt8Array, // asyn type + (uint8_t*)cfgTriggStr_,// pointer to data + strlen(cfgTriggStr_), // size of data + ECMC_EC_U8, // ecmc data type + 0); // die if fail + + if(!triggStrParam_) { + SCOPE_DBG_PRINT("ERROR: Failed create asyn param for trigger."); + throw std::runtime_error( "ERROR: Failed create asyn param for trigger: " + paramName); + } + + triggStrParam_->setAllowWriteToEcmc(false); // read only + triggStrParam_->refreshParam(1); // read once into asyn param lib + ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR); + + // Add enable "plugin.scope%d.nexttime" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_SCOPE_NEXT_SYNC; + + sourceNexttimeStrParam_ = ecmcAsynPort->addNewAvailParam( + paramName.c_str(), // name + asynParamInt8Array, // asyn type + (uint8_t*)cfgDataNexttimeStr_,// pointer to data + strlen(cfgDataNexttimeStr_), // size of data + ECMC_EC_U8, // ecmc data type + 0); // die if fail + + if(!sourceNexttimeStrParam_) { + SCOPE_DBG_PRINT("ERROR: Failed create asyn param for nexttime."); + throw std::runtime_error( "ERROR: Failed create asyn param for nexttime: " + paramName); + } + + sourceNexttimeStrParam_->setAllowWriteToEcmc(false); // read only + sourceNexttimeStrParam_->refreshParam(1); // read once into asyn param lib + ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR); + +} + +asynParamType ecmcScope::getResultAsynDTFromEcDT(ecmcEcDataType ecDT) { + +/*typedef enum { + asynParamNotDefined, + asynParamInt32, + asynParamUInt32Digital, + asynParamFloat64, + asynParamOctet, + asynParamInt8Array, + asynParamInt16Array, + asynParamInt32Array, + asynParamFloat32Array, + asynParamFloat64Array, + asynParamGenericPointer +} asynParamType;*/ + + switch(ecDT) { + case ECMC_EC_NONE: + return asynParamNotDefined; + break; + case ECMC_EC_B1 : + return asynParamNotDefined; + break; + case ECMC_EC_B2 : + return asynParamNotDefined; + break; + case ECMC_EC_B3 : + return asynParamNotDefined; + break; + case ECMC_EC_B4 : + return asynParamNotDefined; + break; + case ECMC_EC_U8 : + return asynParamInt8Array; + break; + case ECMC_EC_S8 : + return asynParamInt8Array; + break; + case ECMC_EC_U16: + return asynParamInt16Array; + break; + case ECMC_EC_S16: + return asynParamInt16Array; + break; + case ECMC_EC_U32: + return asynParamInt32Array; + break; + case ECMC_EC_S32: + return asynParamInt32Array; + break; + case ECMC_EC_U64: + return asynParamNotDefined; + break; + case ECMC_EC_S64: + return asynParamNotDefined; + break; + case ECMC_EC_F32: + return asynParamFloat32Array; + break; + case ECMC_EC_F64: + return asynParamFloat64Array; + break; + default: + return asynParamNotDefined; + break; + } + return asynParamNotDefined; +} + +// Avoid issues with std:to_string() +std::string ecmcScope::to_string(int value) { + std::ostringstream os; + os << value; + return os.str(); +} + +void ecmcScope::setEnable(int enable) { + if(enable) { + SCOPE_DBG_PRINT("INFO: Scope enabled.\n"); + } + else { + SCOPE_DBG_PRINT("INFO: Scope disabled.\n"); + } + + cfgEnable_ = enable; + enbaleParam_->refreshParam(1); +} + +void ecmcScope::triggScope() { + triggOnce_ = 1; +} + +void ecmcScope::setWaitForNextTrigg() { + oldTriggTime_ = triggTime_; +} + + diff --git a/src/src/ecmcScope.h b/src/src/ecmcScope.h new file mode 100644 index 0000000..2cd1ee0 --- /dev/null +++ b/src/src/ecmcScope.h @@ -0,0 +1,131 @@ +/*************************************************************************\ +* Copyright (c) 2019 European Spallation Source ERIC +* ecmc is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +* +* ecmcScope.h +* +* Created on: Mar 22, 2020 +* Author: anderssandstrom +* +\*************************************************************************/ +#ifndef ECMC_SCOPE_H_ +#define ECMC_SCOPE_H_ + +#include +#include "ecmcDataItem.h" +#include "ecmcAsynPortDriver.h" +#include "ecmcScopeDefs.h" +#include "inttypes.h" +#include + +typedef enum { + ECMC_SCOPE_STATE_INVALID, /**Invalid. */ + ECMC_SCOPE_STATE_WAIT_TRIGG, /**Waiting for trigger. */ + ECMC_SCOPE_STATE_WAIT_NEXT, /**Waiting analog. (trigger newer than next ai time)*/ + ECMC_SCOPE_STATE_COLLECT, /**Filling buffer (waiting for data). */ +} ecmcScopeState; + +class ecmcScope { + public: + + /** ecmc Scope class + * This object can throw: + * - bad_alloc + * - invalid_argument + * - runtime_error + * - out_of_range + */ + ecmcScope(int scopeIndex, // index of this object + char* configStr); + ~ecmcScope(); + + // Add data to buffer (called from "external" callback) + // void dataUpdatedCallback(uint8_t* data, + // size_t size, + // ecmcEcDataType dt); + // Call just before realtime because then all data sources should be available + void connectToDataSources(); + void setEnable(int enable); + //void clearBuffers(); + void triggScope(); + void execute(); + + private: + void parseConfigStr(char *configStr); + void addDataToBuffer(double data); + bool sourceDataTypeSupported(ecmcEcDataType dt); + void initAsyn(); + int64_t timeDiff(); + asynParamType getResultAsynDTFromEcDT(ecmcEcDataType ecDT); + void setWaitForNextTrigg(); + + + uint8_t* resultDataBuffer_; + uint8_t* lastScanSourceDataBuffer_; + size_t resultDataBufferBytes_; + size_t bytesInResultBuffer_; + ecmcDataItem *sourceDataItem_; + ecmcDataItemInfo *sourceDataItemInfo_; + ecmcDataItem *sourceDataNexttimeItem_; + ecmcDataItemInfo *sourceDataNexttimeItemInfo_; + ecmcDataItem *sourceTriggItem_; + ecmcDataItemInfo *sourceTriggItemInfo_; + + int dataSourceLinked_; // To avoid link several times + int objectId_; // Unique object id + int triggOnce_; + int firstTrigg_; + + uint64_t triggTime_; + uint64_t oldTriggTime_; + uint64_t sourceNexttime_; + int64_t sourceSampleRateNS_; // nanoseconds + ecmcScopeState scopeState_; + uint64_t ecmcSmapleTimeNS_; + int64_t sourceElementsPerSample_; + size_t elementsInResultBuffer_; + double samplesSinceLastTrigg_; + + // Config options + char* cfgDataSourceStr_; // Config: data source string + char* cfgDataNexttimeStr_; // Config: data source string + char* cfgTriggStr_; // Config: trigg string + int cfgDbgMode_; // Config: allow dbg printouts + size_t cfgBufferElementCount_; // Config: Data set size + int cfgEnable_; // Config: Enable data acq./calc. + + int missedTriggs_; + int triggerCounter_; + + // Asyn + ecmcAsynDataItem *sourceStrParam_; + ecmcAsynDataItem *triggStrParam_; + ecmcAsynDataItem *enbaleParam_; + ecmcAsynDataItem *resultParam_; + ecmcAsynDataItem *sourceNexttimeStrParam_; + ecmcAsynDataItem *asynMissedTriggs_; + ecmcAsynDataItem *asynTriggerCounter_; + ecmcAsynDataItem *asynTimeTrigg2Sample_; + + + // Some generic utility functions + static uint8_t getUint8(uint8_t* data); + static int8_t getInt8(uint8_t* data); + static uint16_t getUint16(uint8_t* data); + static int16_t getInt16(uint8_t* data); + static uint32_t getUint32(uint8_t* data); + static int32_t getInt32(uint8_t* data); + static uint64_t getUint64(uint8_t* data); + static int64_t getInt64(uint8_t* data); + static float getFloat32(uint8_t* data); + static double getFloat64(uint8_t* data); + static size_t getEcDataTypeByteSize(ecmcEcDataType dt); + static void printEcDataArray(uint8_t* data, + size_t size, + ecmcEcDataType dt, + int objId); + static std::string to_string(int value); +}; + +#endif /* ECMC_SCOPE_H_ */ diff --git a/src/src/ecmcScopeDefs.h b/src/src/ecmcScopeDefs.h new file mode 100644 index 0000000..ebdd55b --- /dev/null +++ b/src/src/ecmcScopeDefs.h @@ -0,0 +1,28 @@ +/*************************************************************************\ +* Copyright (c) 2019 European Spallation Source ERIC +* ecmc is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +* +* ecmcScopeDefs.h +* +* Created on: Sept 21, 2020 +* Author: anderssandstrom +* Credits to https://github.com/sgreg/dynamic-loading +* +\*************************************************************************/ + +#ifndef ECMC_SCOPE_DEFS_H_ +#define ECMC_SCOPE_DEFS_H_ + +// Options +#define ECMC_PLUGIN_DBG_PRINT_OPTION_CMD "DBG_PRINT=" +#define ECMC_PLUGIN_SOURCE_OPTION_CMD "SOURCE=" +#define ECMC_PLUGIN_SOURCE_NEXTTIME_OPTION_CMD "SOURCE_NEXTTIME=" +#define ECMC_PLUGIN_TRIGG_OPTION_CMD "TRIGG=" +#define ECMC_PLUGIN_RESULT_ELEMENTS_OPTION_CMD "RESULT_ELEMENTS=" +#define ECMC_PLUGIN_ENABLE_OPTION_CMD "ENABLE=" + +// Default size (must be n²) +#define ECMC_PLUGIN_DEFAULT_BUFFER_SIZE 4096 + +#endif /* ECMC_SCOPE_DEFS_H_ */ diff --git a/src/src/ecmcScopeWrap.cpp b/src/src/ecmcScopeWrap.cpp new file mode 100644 index 0000000..628bfe5 --- /dev/null +++ b/src/src/ecmcScopeWrap.cpp @@ -0,0 +1,121 @@ +/*************************************************************************\ +* Copyright (c) 2019 European Spallation Source ERIC +* ecmc is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +* +* ecmcScopeWrap.cpp +* +* Created on: Sept 21, 2020 +* Author: anderssandstrom +* Credits to https://github.com/sgreg/dynamic-loading +* +\*************************************************************************/ + +// Needed to get headers in ecmc right... +#define ECMC_IS_PLUGIN + +#include +#include +#include +#include "ecmcScopeWrap.h" +#include "ecmcScope.h" +#include "ecmcScopeDefs.h" + +#define ECMC_PLUGIN_PORTNAME_PREFIX "PLUGIN.SCOPE" +#define ECMC_PLUGIN_SCOPE_ERROR_CODE 1 + +static std::vector scopes; +static int scopeObjCounter = 0; + +int createScope(char* configStr) { + + // create new ecmcFFT object + ecmcScope* scope = NULL; + + try { + scope = new ecmcScope(scopeObjCounter, configStr); + } + catch(std::exception& e) { + if(scope) { + delete scope; + } + printf("Exception: %s. Plugin will unload.\n",e.what()); + return ECMC_PLUGIN_SCOPE_ERROR_CODE; + } + + scopes.push_back(scope); + scopeObjCounter++; + + return 0; +} + +void deleteAllScopes() { + for(std::vector::iterator pscope = scopes.begin(); pscope != scopes.end(); ++pscope) { + if(*pscope) { + delete (*pscope); + } + } +} + +int linkDataToScopes() { + for(std::vector::iterator pscope = scopes.begin(); pscope != scopes.end(); ++pscope) { + if(*pscope) { + try { + (*pscope)->connectToDataSources(); + } + catch(std::exception& e) { + printf("Exception: %s. Plugin will unload.\n",e.what()); + return ECMC_PLUGIN_SCOPE_ERROR_CODE; + } + } + } + return 0; +} + +int enableScope(int scopeIndex, int enable) { + try { + scopes.at(scopeIndex)->setEnable(enable); + } + catch(std::exception& e) { + printf("Exception: %s. Scope index out of range.\n",e.what()); + return ECMC_PLUGIN_SCOPE_ERROR_CODE; + } + return 0; +} + +// int clearScope(int scopeIndex) { +// try { +// scopes.at(scopeIndex)->clearBuffers(); +// } +// catch(std::exception& e) { +// printf("Exception: %s. Scope index out of range.\n",e.what()); +// return ECMC_PLUGIN_SCOPE_ERROR_CODE; +// } +// return 0; +// } + +int triggScope(int scopeIndex) { + try { + scopes.at(scopeIndex)->triggScope(); + } + catch(std::exception& e) { + printf("Exception: %s. Scope index out of range.\n",e.what()); + return ECMC_PLUGIN_SCOPE_ERROR_CODE; + } + return 0; +} + +int executeScopes() { + try { + for(std::vector::iterator pscope = scopes.begin(); pscope != scopes.end(); ++pscope) { + if(*pscope) { + (*pscope)->execute(); + } + } + } + catch(std::exception& e) { + printf("Exception: %s.\n",e.what()); + return ECMC_PLUGIN_SCOPE_ERROR_CODE; + } + return 0; +} diff --git a/src/src/ecmcScopeWrap.h b/src/src/ecmcScopeWrap.h new file mode 100644 index 0000000..54f09f3 --- /dev/null +++ b/src/src/ecmcScopeWrap.h @@ -0,0 +1,54 @@ +/*************************************************************************\ +* Copyright (c) 2019 European Spallation Source ERIC +* ecmc is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +* +* ecmcScopeWrap.h +* +* Created on: Sept 21, 2020 +* Author: anderssandstrom +* +\*************************************************************************/ +#ifndef ECMC_SCOPE_WRAP_H_ +#define ECMC_SCOPE_WRAP_H_ +#include "ecmcScopeDefs.h" + +# ifdef __cplusplus +extern "C" { +# endif // ifdef __cplusplus + +int createScope(char *configStr); + +int enableScope(int scopeIndex, int enable); + +//int clearScope(int scopeIndex); + +int triggScope(int scopeIndex); + + +int executeScopes(); + +/** \brief Link data to _all_ scope objects + * + * This tells the Scope lib to connect to ecmc to find it's data source.\n + * This function should be called just before entering realtime since then all\n + * data sources in ecmc will be definded (plc sources are compiled just before runtime\n + * so are only fist accesible now).\n + * \return 0 if success or otherwise an error code.\n + */ +int linkDataToScopes(); + +/** \brief Deletes all created scope objects\n + * + * Should be called when destructs.\n + */ + +void deleteAllScopes(); + + + +# ifdef __cplusplus +} +# endif // ifdef __cplusplus + +#endif /* ECMC_SCOPE_WRAP_H_ */