commit f36c6b3f4899e2ef2a43dce61dd92a59009d99a8 Author: Anders Sandstrom Date: Mon Mar 1 10:52:10 2021 +0100 Initial commint (based on fft plugin, still sources left) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b849ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*~ +*-dev +modules.order +Module.symvers +Mkfile.old +core.* +#* +.#* +\#* +*.local +\#* +.cvsignore +*_old/ +*PVs.list +*-loc/*.Makefile +ecmc_plugin_fft/*.Makefile +*__* \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..48d516b --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +# +# Copyright (c) 2018 - 2019 European Spallation Source ERIC +# +# The program is free software: you can redistribute +# it and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, either version 2 of the +# License, or any newer version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt +# +# +# Author : Jeong Han Lee +# email : han.lee@esss.se +# Date : 2020Mar22-1607-33CET +# version : 1.0.0 + +TOP:=$(CURDIR) + +include $(TOP)/configure/CONFIG + +include $(TOP)/configure/RULES + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b09dec0 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +e3-ecmc_plugin_socketcan +====== +ESS Site-specific EPICS module : ecmcPlugin_socketcan diff --git a/cmds/.keep b/cmds/.keep new file mode 100644 index 0000000..e69de29 diff --git a/configure/CONFIG b/configure/CONFIG new file mode 100644 index 0000000..2286cf9 --- /dev/null +++ b/configure/CONFIG @@ -0,0 +1,11 @@ +# +VARS_EXCLUDES := $(.VARIABLES) + +include $(TOP)/configure/RELEASE +include $(TOP)/configure/CONFIG_MODULE + +E3_REQUIRE_LOCATION := $(EPICS_BASE)/$(E3_REQUIRE_NAME)/$(E3_REQUIRE_VERSION) +REQUIRE_CONFIG := $(E3_REQUIRE_LOCATION)/configure + +include $(REQUIRE_CONFIG)/CONFIG + diff --git a/configure/CONFIG_MODULE b/configure/CONFIG_MODULE new file mode 100644 index 0000000..b37911a --- /dev/null +++ b/configure/CONFIG_MODULE @@ -0,0 +1,37 @@ +# +EPICS_MODULE_NAME:=ecmc_plugin_socketcan + +EPICS_MODULE_TAG:=master +# +E3_MODULE_VERSION:=master + +# DEPENDENT MODULE VERSION +# For Example, + +ECMC_DEP_VERSION:=6.3.0 +ASYN_DEP_VERSION:=4.37.0 + +#DEVLIB2_DEP_VERSION:=2.9.0 +#PCRE_DEP_VERSION:=8.41.0 +#ADCORE_DEP_VERSION:=3.7.0 +#ADSUPPORT_DEP_VERSION:=1.9.0 +#LOKI_DEP_VERSION=1.0.0 +#NDS_DEP_VERSION=2.3.3 +#SIS8300DRV_DEP_VERSION=4.3.1 +#SEQUENCER_DEP_VERSION=2.2.7 +# +# +#E3_KMOD_SRC_PATH:=$(E3_MODULE_SRC_PATH) +# +# In most case, we don't need to touch the following variables. +# + +E3_MODULE_NAME:=$(EPICS_MODULE_NAME) +E3_MODULE_SRC_PATH:=ecmc_plugin_fft +E3_MODULE_MAKEFILE:=$(EPICS_MODULE_NAME).Makefile + + +-include $(TOP)/configure/CONFIG_OPTIONS +# The definitions shown below can also be placed in an untracked CONFIG_MODULE.local +-include $(TOP)/configure/CONFIG_MODULE.local + diff --git a/configure/CONFIG_OPTIONS b/configure/CONFIG_OPTIONS new file mode 100644 index 0000000..2e022c1 --- /dev/null +++ b/configure/CONFIG_OPTIONS @@ -0,0 +1,6 @@ +# +# WITH_PVA:=NO +# +# The definitions shown below can also be placed in an untracked CONFIG_OPTIONS.local +-include $(TOP)/configure/CONFIG_OPTIONS.local + diff --git a/configure/RELEASE b/configure/RELEASE new file mode 100644 index 0000000..f7dcbee --- /dev/null +++ b/configure/RELEASE @@ -0,0 +1,11 @@ +# +EPICS_BASE:=${HOME}/epics/base-7.0.4 + +E3_REQUIRE_NAME:=require +E3_REQUIRE_VERSION:=3.3.0 + +# The definitions shown below can also be placed in an untracked RELEASE.local +-include $(TOP)/../../RELEASE.local +-include $(TOP)/../RELEASE.local +-include $(TOP)/configure/RELEASE.local + diff --git a/configure/RULES b/configure/RULES new file mode 100644 index 0000000..56e9524 --- /dev/null +++ b/configure/RULES @@ -0,0 +1,12 @@ + +include $(REQUIRE_CONFIG)/RULES_E3 +include $(REQUIRE_CONFIG)/DEFINES_FT +include $(REQUIRE_CONFIG)/RULES_PATCH +include $(REQUIRE_CONFIG)/RULES_E3_SITELIBS + +include $(REQUIRE_CONFIG)/RULES_VLIBS +include $(REQUIRE_CONFIG)/RULES_VARS + +include $(TOP)/configure/module/RULES_MODULE +-include $(TOP)/configure/module/RULES_DKMS_L + diff --git a/configure/module/RULES_DKMS_L b/configure/module/RULES_DKMS_L new file mode 100644 index 0000000..c66bf32 --- /dev/null +++ b/configure/module/RULES_DKMS_L @@ -0,0 +1,38 @@ + +# KMOD_NAME := mrf + +# .PHONY: dkms_add + +# dkms_add: conf +# $(MSI) -M name="$(E3_MODULE_NAME)" -M version="$(E3_MODULE_VERSION)" -M kmod_name="$(KMOD_NAME)" $(TOP)/dkms/dkms_with_msi.conf.in > $(TOP)/dkms/dkms_with_msi.conf +# $(QUIET) cat $(TOP)/dkms/dkms_with_msi.conf $(TOP)/dkms/dkms_without_msi.conf > $(TOP)/dkms/dkms.conf +# $(QUIET) install -m 644 $(TOP)/dkms/dkms.conf $(E3_KMOD_SRC_PATH)/ +# $(SUDO) install -d /usr/src/$(E3_MODULE_NAME)-$(E3_MODULE_VERSION) +# $(SUDO) cp -r $(TOP)/$(E3_KMOD_SRC_PATH)/* /usr/src/$(E3_MODULE_NAME)-$(E3_MODULE_VERSION)/ +# $(SUDO) $(DKMS) add $(DKMS_ARGS) + + +# setup: +# $(QUIET) $(SUDO2) 'echo KERNEL==\"uio*\", ATTR{name}==\"mrf-pci\", MODE=\"0666\" | tee /etc/udev/rules.d/99-$(KMOD_NAME).rules' +# $(QUIET) $(SUDO) /bin/udevadm control --reload-rules +# $(QUIET) $(SUDO) /bin/udevadm trigger +# $(QUIET) $(SUDO2) 'echo $(KMOD_NAME) | tee /etc/modules-load.d/$(KMOD_NAME).conf' +# $(QUIET) $(SUDO) depmod --quick +# $(QUIET) $(SUDO) modprobe -rv $(KMOD_NAME) +# $(QUIET) $(SUDO) modprobe -v $(KMOD_NAME) +# $(QUIET) echo "" +# $(QUIET) echo "" +# $(QUIET) echo "It is OK to see \"E3/RULES_DKMS:37: recipe for target 'setup' failed\"" +# $(QUIET) echo "---------------------------------------------------------------------" +# $(QUIET) -ls -l /dev/uio* 2>/dev/null +# $(QUIET) echo "---------------------------------------------------------------------" + + +# setup_clean: +# $(QUIET) $(SUDO) modprobe -rv $(KMOD_NAME) +# $(SUDO) rm -f /etc/modules-load.d/$(KMOD_NAME).conf +# $(SUDO) rm -f /etc/udev/rules.d/99-$(KMOD_NAME).rules + + +# .PHONY: setup setup_clean + diff --git a/configure/module/RULES_MODULE b/configure/module/RULES_MODULE new file mode 100644 index 0000000..775070a --- /dev/null +++ b/configure/module/RULES_MODULE @@ -0,0 +1,20 @@ +# +.PHONY: db hdrs + +db: conf + $(QUIET) $(E3_MODULE_MAKE_CMDS) db + +hdrs: +# $(SUDO) install -m 755 -d $(E3_MODULES_INSTALL_LOCATION_INC)/pv +# cd $(E3_MODULES_INSTALL_LOCATION_INC) && $(SUDO) mv *.h pv/ + +#.PHONY: epics +#epics: +# $(QUIET)echo "EPICS_BASE:=$(EPICS_BASE)" > $(TOP)/$(E3_MODULE_SRC_PATH)/configure/RELEASE +# $(QUIET)echo "ASYN:=$(M_ASYN)" > $(TOP)/$(E3_MODULE_SRC_PATH)/configure/RELEASE +# $(QUIET)echo "SSCAN:=$(M_SSCAN)" >> $(TOP)/$(E3_MODULE_SRC_PATH)/configure/RELEASE +# $(QUIET)echo "SNCSEQ:=$(M_SNCSEQ)" >> $(TOP)/$(E3_MODULE_SRC_PATH)/configure/RELEASE +# $(QUIET)echo "CHECK_RELEASE:=YES" > $(TOP)/$(E3_MODULE_SRC_PATH)/configure/CONFIG_SITE +# $(QUIET)echo "INSTALL_LOCATION:=$(M_DEVLIB2)" >> $(TOP)/$(E3_MODULE_SRC_PATH)/configure/CONFIG_SITE +# $(SUDOBASH) "$(MAKE) -C $(E3_MODULE_SRC_PATH)" + diff --git a/docs/.MODULE_LOG b/docs/.MODULE_LOG new file mode 100644 index 0000000..b60b012 --- /dev/null +++ b/docs/.MODULE_LOG @@ -0,0 +1,28 @@ +>> +Script is used : e3TemplateGenerator.bash +Script Path : /home/anderssandstrom/plugin_ecmc/e3-tools/e3TemplateGenerator +Script Version : 1.0.8 +Script Run Time : 2020Mar22-1607-33CET +User : anderssandstrom +e3-tools Hash : bf03d40 +>> +>> git diff + +>> +>> git diff --cached + +>> +>> git diff HEAD + + +>> +>> Your sources are located in e3-ecmcPlugin_Simple. +>> +EPICS_MODULE_NAME : ecmcPlugin_Simple +E3_MODULE_SRC_PATH : ecmcPlugin_Simple +E3_TARGET_URL : https://github.com/anderssandstrom +>> +e3 module name : e3-ecmcPlugin_Simple +e3 target url full : https://github.com/anderssandstrom/e3-ecmcPlugin_Simple.git +>> +e3 module is located in siteMods diff --git a/ecmc_plugin_socketcan.Makefile b/ecmc_plugin_socketcan.Makefile new file mode 100644 index 0000000..48a4ab7 --- /dev/null +++ b/ecmc_plugin_socketcan.Makefile @@ -0,0 +1,62 @@ +# +# Copyright (c) 2019 European Spallation Source ERIC +# +# The program is free software: you can redistribute +# it and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, either version 2 of the +# License, or any newer version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt +# +# +# Author : anderssandstrom +# email : anderssandstrom@esss.se +# Date : 2020Mar22-1607-33CET +# version : 0.0.0 +# +# template file is generated by ./e3TemplateGenerator.bash with bf03d40 +# Please look at many other _module_.Makefile in e3-* repository +# + +## The following lines are mandatory, please don't change them. +where_am_I := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +include $(E3_REQUIRE_TOOLS)/driver.makefile +include $(E3_REQUIRE_CONFIG)/DECOUPLE_FLAGS + +ifneq ($(strip $(ASYN_DEP_VERSION)),) +asyn_VERSION=$(ASYN_DEP_VERSION) +endif + +ifneq ($(strip $(ECMC_DEP_VERSION)),) +ecmc_VERSION=$(ECMC_DEP_VERSION) +endif + +APP:=ecmc_plugin_fftApp +APPDB:=$(APP)/Db +APPSRC:=$(APP)/src + +USR_CFLAGS += -shared -fPIC -Wall -Wextra +USR_LDFLAGS += -lstdc++ +USR_INCLUDES += -I$(where_am_I)$(APPSRC) + +TEMPLATES += $(wildcard $(APPDB)/*.db) +TEMPLATES += $(wildcard $(APPDB)/*.template) +SOURCES += $(APPSRC)/ecmcPluginFFT.c +SOURCES += $(APPSRC)/ecmcFFTWrap.cpp +SOURCES += $(APPSRC)/ecmcFFT.cpp + +db: + +.PHONY: db + +vlibs: + +.PHONY: vlibs + +### diff --git a/ecmc_plugin_socketcan/ecmc_plugin_socketcan.Makefile b/ecmc_plugin_socketcan/ecmc_plugin_socketcan.Makefile new file mode 100644 index 0000000..48a4ab7 --- /dev/null +++ b/ecmc_plugin_socketcan/ecmc_plugin_socketcan.Makefile @@ -0,0 +1,62 @@ +# +# Copyright (c) 2019 European Spallation Source ERIC +# +# The program is free software: you can redistribute +# it and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, either version 2 of the +# License, or any newer version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt +# +# +# Author : anderssandstrom +# email : anderssandstrom@esss.se +# Date : 2020Mar22-1607-33CET +# version : 0.0.0 +# +# template file is generated by ./e3TemplateGenerator.bash with bf03d40 +# Please look at many other _module_.Makefile in e3-* repository +# + +## The following lines are mandatory, please don't change them. +where_am_I := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +include $(E3_REQUIRE_TOOLS)/driver.makefile +include $(E3_REQUIRE_CONFIG)/DECOUPLE_FLAGS + +ifneq ($(strip $(ASYN_DEP_VERSION)),) +asyn_VERSION=$(ASYN_DEP_VERSION) +endif + +ifneq ($(strip $(ECMC_DEP_VERSION)),) +ecmc_VERSION=$(ECMC_DEP_VERSION) +endif + +APP:=ecmc_plugin_fftApp +APPDB:=$(APP)/Db +APPSRC:=$(APP)/src + +USR_CFLAGS += -shared -fPIC -Wall -Wextra +USR_LDFLAGS += -lstdc++ +USR_INCLUDES += -I$(where_am_I)$(APPSRC) + +TEMPLATES += $(wildcard $(APPDB)/*.db) +TEMPLATES += $(wildcard $(APPDB)/*.template) +SOURCES += $(APPSRC)/ecmcPluginFFT.c +SOURCES += $(APPSRC)/ecmcFFTWrap.cpp +SOURCES += $(APPSRC)/ecmcFFT.cpp + +db: + +.PHONY: db + +vlibs: + +.PHONY: vlibs + +### diff --git a/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/Db/.keep b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/Db/.keep new file mode 100644 index 0000000..e69de29 diff --git a/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/.keep b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/.keep new file mode 100644 index 0000000..e69de29 diff --git a/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFT.cpp b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFT.cpp new file mode 100644 index 0000000..7456600 --- /dev/null +++ b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFT.cpp @@ -0,0 +1,1057 @@ +/*************************************************************************\ +* 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. +* +* ecmcFFT.cpp +* +* Created on: Mar 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.fft" +#define ECMC_PLUGIN_ASYN_ENABLE "enable" +#define ECMC_PLUGIN_ASYN_RAWDATA "rawdata" +#define ECMC_PLUGIN_ASYN_PPDATA "preprocdata" +#define ECMC_PLUGIN_ASYN_FFT_AMP "fftamplitude" +#define ECMC_PLUGIN_ASYN_FFT_MODE "mode" +#define ECMC_PLUGIN_ASYN_FFT_STAT "status" +#define ECMC_PLUGIN_ASYN_FFT_SOURCE "source" +#define ECMC_PLUGIN_ASYN_FFT_TRIGG "trigg" +#define ECMC_PLUGIN_ASYN_FFT_X_FREQS "fftxaxis" +#define ECMC_PLUGIN_ASYN_NFFT "nfft" +#define ECMC_PLUGIN_ASYN_RATE "samplerate" +#define ECMC_PLUGIN_ASYN_BUFF_ID "buffid" + + +#include +#include "ecmcFFT.h" +#include "ecmcPluginClient.h" +#include "ecmcAsynPortDriver.h" +#include "ecmcAsynPortDriverUtils.h" +#include "epicsThread.h" + + +// New data callback from ecmc +static int printMissingObjError = 1; + +/** This callback will not be used (sample data inteface is used instead to get an stable sample freq) + since the callback is called when data is updated it might */ +void f_dataUpdatedCallback(uint8_t* data, size_t size, ecmcEcDataType dt, void* obj) { + if(!obj) { + if(printMissingObjError){ + printf("%s/%s:%d: Error: Callback object NULL.. Data will not be added to buffer.\n", + __FILE__, __FUNCTION__, __LINE__); + printMissingObjError = 0; + return; + } + } + ecmcFFT * fftObj = (ecmcFFT*)obj; + + // Call the correct fft object with new data + fftObj->dataUpdatedCallback(data,size,dt); +} + +void f_worker(void *obj) { + if(!obj) { + printf("%s/%s:%d: Error: Worker thread FFT object NULL..\n", + __FILE__, __FUNCTION__, __LINE__); + return; + } + ecmcFFT * fftObj = (ecmcFFT*)obj; + fftObj->doCalcWorker(); +} + +/** ecmc FFT class + * This object can throw: + * - bad_alloc + * - invalid_argument + * - runtime_error +*/ +ecmcFFT::ecmcFFT(int fftIndex, // index of this object (if several is created) + char* configStr, + char* portName) + : asynPortDriver(portName, + 1, /* maxAddr */ + asynInt32Mask | asynFloat64Mask | asynFloat32ArrayMask | + asynFloat64ArrayMask | asynEnumMask | asynDrvUserMask | + asynOctetMask | asynInt8ArrayMask | asynInt16ArrayMask | + asynInt32ArrayMask | asynUInt32DigitalMask, /* Interface mask */ + asynInt32Mask | asynFloat64Mask | asynFloat32ArrayMask | + asynFloat64ArrayMask | asynEnumMask | asynDrvUserMask | + asynOctetMask | asynInt8ArrayMask | asynInt16ArrayMask | + asynInt32ArrayMask | asynUInt32DigitalMask, /* Interrupt mask */ + ASYN_CANBLOCK , /*NOT ASYN_MULTI_DEVICE*/ + 1, /* Autoconnect */ + 0, /* Default priority */ + 0) /* Default stack size */ + { + cfgDataSourceStr_ = NULL; + rawDataBuffer_ = NULL; + dataItem_ = NULL; + dataItemInfo_ = NULL; + fftDouble_ = NULL; + status_ = NO_STAT; + elementsInBuffer_ = 0; + fftWaitingForCalc_= 0; + destructs_ = 0; + callbackHandle_ = -1; + objectId_ = fftIndex; + scale_ = 1.0; + triggOnce_ = 0; + cycleCounter_ = 0; + ignoreCycles_ = 0; + dataSourceLinked_ = 0; + + // Asyn + asynEnableId_ = -1; // Enable/disable acq./calcs + asynRawDataId_ = -1; // Raw data (input) array (double) + asynPPDataId_ = -1; // Pre-processed data array (double) + asynFFTAmpId_ = -1; // FFT amplitude array (double) + asynFFTModeId_ = -1; // FFT mode (cont/trigg) + asynFFTStatId_ = -1; // FFT status (no_stat/idle/acq/calc) + asynSourceId_ = -1; // SOURCE + asynTriggId_ = -1; // Trigg new measurement + asynFFTXAxisId_ = -1; // FFT X-axis frequencies + asynNfftId_ = -1; // Nfft + asynSRateId_ = -1; // Sample rate Hz + asynElementsInBuffer_= -1; + + ecmcSampleRateHz_ = getEcmcSampleRate(); + cfgFFTSampleRateHz_ = ecmcSampleRateHz_; + cfgDataSampleRateHz_ = ecmcSampleRateHz_; + + // Config defaults + cfgDbgMode_ = 0; + cfgNfft_ = ECMC_PLUGIN_DEFAULT_NFFT; // samples in fft (must be n^2) + cfgDcRemove_ = 0; + cfgLinRemove_ = 0; + //cfgApplyScale_ = 1; // Scale as default to get correct amplitude in fft + cfgEnable_ = 0; // start disabled (enable over asyn) + cfgMode_ = TRIGG; + cfgScale_ = 1.0; + + parseConfigStr(configStr); // Assigns all configs + // Check valid nfft + if(cfgNfft_ <= 0) { + throw std::out_of_range("NFFT must be > 0 and even N^2."); + } + + // Check valid sample rate + if(cfgFFTSampleRateHz_ <= 0) { + throw std::out_of_range("FFT Invalid sample rate"); + } + if(cfgFFTSampleRateHz_ > ecmcSampleRateHz_) { + printf("Warning FFT sample rate faster than ecmc rate. FFT rate will be set to ecmc rate.\n"); + cfgFFTSampleRateHz_ = ecmcSampleRateHz_; + } + + // Se if any data update cycles should be ignored + // example ecmc 1000Hz, fft 100Hz then ignore 9 cycles (could be strange if not multiples) + ignoreCycles_ = ecmcSampleRateHz_ / cfgFFTSampleRateHz_ -1; + + // set scale factor + scale_ = 1.0 / ((double)cfgNfft_); // sqrt((double)cfgNfft_); + + // Allocate buffers + rawDataBuffer_ = new double[cfgNfft_]; // Raw input data (real) + prepProcDataBuffer_ = new double[cfgNfft_]; // Data for preprocessing + fftBufferInput_ = new std::complex[cfgNfft_]; // FFT input (complex) + fftBufferResult_ = new std::complex[cfgNfft_]; // FFT result (complex) + fftBufferResultAmp_ = new double[cfgNfft_ / 2 + 1]; // FFT result amplitude (real) + fftBufferXAxis_ = new double[cfgNfft_ / 2 + 1]; // FFT x axis with freqs + clearBuffers(); + + // Allocate KissFFT + fftDouble_ = new kissfft(cfgNfft_,false); + + // Create worker thread + std::string threadname = "ecmc." ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_); + if(epicsThreadCreate(threadname.c_str(), 0, 32768, f_worker, this) == NULL) { + throw std::runtime_error("Error: Failed create worker thread."); + } + + initAsyn(); +} + +ecmcFFT::~ecmcFFT() { + // kill worker + destructs_ = 1; // maybe need todo in other way.. + doCalcEvent_.signal(); + + if(rawDataBuffer_) { + delete[] rawDataBuffer_; + } + + if(prepProcDataBuffer_) { + delete[] prepProcDataBuffer_; + } + + // De register callback when unload + if(callbackHandle_ >= 0) { + dataItem_->deregDataUpdatedCallback(callbackHandle_); + } + if(cfgDataSourceStr_) { + free(cfgDataSourceStr_); + } + if(fftDouble_) { + delete fftDouble_; + } + if (fftBufferInput_){ + delete[] fftBufferInput_; + } +} + +void ecmcFFT::parseConfigStr(char *configStr) { + + // 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_NFFT_OPTION_CMD (1/0) + else if (!strncmp(pThisOption, ECMC_PLUGIN_NFFT_OPTION_CMD, strlen(ECMC_PLUGIN_NFFT_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_NFFT_OPTION_CMD); + cfgNfft_ = atoi(pThisOption); + } + + // // ECMC_PLUGIN_APPLY_SCALE_OPTION_CMD (1/0) + // else if (!strncmp(pThisOption, ECMC_PLUGIN_APPLY_SCALE_OPTION_CMD, strlen(ECMC_PLUGIN_APPLY_SCALE_OPTION_CMD))) { + // pThisOption += strlen(ECMC_PLUGIN_APPLY_SCALE_OPTION_CMD); + // cfgApplyScale_ = atoi(pThisOption); + // } + + // ECMC_PLUGIN_RM_DC_OPTION_CMD (1/0) + else if (!strncmp(pThisOption, ECMC_PLUGIN_RM_DC_OPTION_CMD, strlen(ECMC_PLUGIN_RM_DC_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_RM_DC_OPTION_CMD); + cfgDcRemove_ = atoi(pThisOption); + } + + // ECMC_PLUGIN_RM_LIN_OPTION_CMD (1/0) + else if (!strncmp(pThisOption, ECMC_PLUGIN_RM_LIN_OPTION_CMD, strlen(ECMC_PLUGIN_RM_LIN_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_RM_LIN_OPTION_CMD); + cfgLinRemove_ = 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_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; + } + } + + // ECMC_PLUGIN_RATE_OPTION_CMD rate in HZ + else if (!strncmp(pThisOption, ECMC_PLUGIN_RATE_OPTION_CMD, strlen(ECMC_PLUGIN_RATE_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_RATE_OPTION_CMD); + cfgFFTSampleRateHz_ = atof(pThisOption); + } + + // ECMC_PLUGIN_SCALE_OPTION_CMD rate in HZ + else if (!strncmp(pThisOption, ECMC_PLUGIN_SCALE_OPTION_CMD, strlen(ECMC_PLUGIN_SCALE_OPTION_CMD))) { + pThisOption += strlen(ECMC_PLUGIN_SCALE_OPTION_CMD); + cfgScale_ = atof(pThisOption); + } + + pThisOption = pNextOption; + } + free(pOptions); + } + + // Data source must be defined... + if(!cfgDataSourceStr_) { + throw std::invalid_argument( "Data source not defined."); + } +} + +void ecmcFFT::connectToDataSource() { + /* Check if already linked (one call to enterRT per loaded FFT lib (FFT object)) + But link should only happen once!!*/ + if( dataSourceLinked_ ) { + return; + } + + // Get dataItem + dataItem_ = (ecmcDataItem*) getEcmcDataItem(cfgDataSourceStr_); + if(!dataItem_) { + throw std::runtime_error( "Data item NULL." ); + } + + dataItemInfo_ = dataItem_->getDataItemInfo(); + + // Register data callback + callbackHandle_ = dataItem_->regDataUpdatedCallback(f_dataUpdatedCallback, this); + if (callbackHandle_ < 0) { + throw std::runtime_error( "Failed to register data source callback."); + } + + // Check data source + if( !dataTypeSupported(dataItem_->getEcmcDataType()) ) { + throw std::invalid_argument( "Data type not supported." ); + } + + // Add oversampling + cfgDataSampleRateHz_ = cfgFFTSampleRateHz_ * dataItem_->getEcmcDataSize()/dataItem_->getEcmcDataElementSize(); + setDoubleParam(asynSRateId_, cfgDataSampleRateHz_); + callParamCallbacks(); + + dataSourceLinked_ = 1; + updateStatus(IDLE); +} + +void ecmcFFT::dataUpdatedCallback(uint8_t* data, + size_t size, + ecmcEcDataType dt) { + + if(fftWaitingForCalc_) { + return; + } + // No buffer or full or not enabled + if(!rawDataBuffer_ || !cfgEnable_) { + return; + } + + // See if data should be ignored + if(cycleCounter_ < ignoreCycles_) { + cycleCounter_++; + return; // ignore this callback + } + + cycleCounter_ = 0; + + if (cfgMode_ == TRIGG && !triggOnce_ ) { + updateStatus(IDLE); + return; // Wait for trigger from plc or asyn + } + + if(cfgDbgMode_) { + printEcDataArray(data, size, dt, objectId_); + + if(elementsInBuffer_ == cfgNfft_) { + printf("Buffer full (%zu elements appended).\n",elementsInBuffer_); + } + } + + if(elementsInBuffer_ >= cfgNfft_) { + //Buffer full + if(!fftWaitingForCalc_){ + // Perform calcs + updateStatus(CALC); + fftWaitingForCalc_ = 1; + doCalcEvent_.signal(); // let worker start + } + return; + } + + updateStatus(ACQ); + + size_t dataElementSize = getEcDataTypeByteSize(dt); + + uint8_t *pData = data; + for(unsigned int i = 0; i < size / dataElementSize; ++i) { + //printf("dataElementSize=%d, size=%d\n",dataElementSize,size); + switch(dt) { + case ECMC_EC_U8: + addDataToBuffer((double)getUint8(pData)); + break; + case ECMC_EC_S8: + addDataToBuffer((double)getInt8(pData)); + break; + case ECMC_EC_U16: + addDataToBuffer((double)getUint16(pData)); + break; + case ECMC_EC_S16: + addDataToBuffer((double)getInt16(pData)); + break; + case ECMC_EC_U32: + addDataToBuffer((double)getUint32(pData)); + break; + case ECMC_EC_S32: + addDataToBuffer((double)getInt32(pData)); + break; + case ECMC_EC_U64: + addDataToBuffer((double)getUint64(pData)); + break; + case ECMC_EC_S64: + addDataToBuffer((double)getInt64(pData)); + break; + case ECMC_EC_F32: + addDataToBuffer((double)getFloat32(pData)); + break; + case ECMC_EC_F64: + addDataToBuffer((double)getFloat64(pData)); + break; + default: + break; + } + + pData += dataElementSize; + } +} + +void ecmcFFT::addDataToBuffer(double data) { + if(rawDataBuffer_ && (elementsInBuffer_ < cfgNfft_) ) { + rawDataBuffer_[elementsInBuffer_] = data* cfgScale_; + prepProcDataBuffer_[elementsInBuffer_] = data *cfgScale_; + } + elementsInBuffer_ ++; +} + +void ecmcFFT::clearBuffers() { + memset(rawDataBuffer_, 0, cfgNfft_ * sizeof(double)); + memset(prepProcDataBuffer_, 0, cfgNfft_ * sizeof(double)); + memset(fftBufferResultAmp_, 0, (cfgNfft_ / 2 + 1) * sizeof(double)); + memset(fftBufferXAxis_, 0, (cfgNfft_ / 2 + 1) * sizeof(double)); + for(unsigned int i = 0; i < cfgNfft_; ++i) { + fftBufferResult_[i].real(0); + fftBufferResult_[i].imag(0); + fftBufferInput_[i].real(0); + fftBufferInput_[i].imag(0); + } + elementsInBuffer_ = 0; +} + +void ecmcFFT::calcFFT() { + // move pre-processed data to fft input buffer + for(unsigned int i = 0; i < cfgNfft_; ++i) { + fftBufferInput_[i].real(prepProcDataBuffer_[i]); + fftBufferInput_[i].imag(0); + } + + // Do fft + fftDouble_->transform(fftBufferInput_, fftBufferResult_); +} + +void ecmcFFT::scaleFFT() { + // Always scale + //if(!cfgApplyScale_) { + // return; + //} + + for(unsigned int i = 0 ; i < cfgNfft_ ; ++i ) { + fftBufferResult_[i] = fftBufferResult_[i] * scale_; + } +} + +void ecmcFFT::calcFFTAmp() { + for(unsigned int i = 0 ; i < cfgNfft_ / 2 + 1 ; ++i ) { + fftBufferResultAmp_[i] = std::abs(fftBufferResult_[i]); + } +} + +// Should be enough todo once +void ecmcFFT::calcFFTXAxis() { + //fill x axis buffer with freqs + + double freq = 0; + size_t size = dataItemInfo_->dataSize; + + double deltaFreq = cfgDataSampleRateHz_* ((double)size / + (double)dataItemInfo_->dataElementSize) / ((double)(cfgNfft_)); + for(unsigned int i = 0; i < (cfgNfft_ / 2 + 1); ++i) { + fftBufferXAxis_[i] = freq; + freq = freq + deltaFreq; + } +} + +void ecmcFFT::removeDCOffset() { + if(!cfgDcRemove_) { + return; + } + + // calc average of preprocess buffer data + double sum = 0; + for(unsigned int i = 0; i < cfgNfft_; ++i ) { + sum += prepProcDataBuffer_[i]; + } + double avg = sum / ((double)cfgNfft_); + for(unsigned int i = 0; i < cfgNfft_; ++i ) { + prepProcDataBuffer_[i] = (prepProcDataBuffer_[i]-avg); + } +} + +void ecmcFFT::removeLin() { + if(!cfgLinRemove_) { + return; + } + + double k=0; + double m=0; + // calc least square (best fit of line) + if(leastSquare(cfgNfft_,prepProcDataBuffer_,&k,&m)) { + printf("%s/%s:%d: Error: " ECMC_PLUGIN_RM_LIN_OPTION_CMD " failed, divison by 0. Data will not be processed with the option/configuration.\n", + __FILE__, __FUNCTION__, __LINE__); + return; + } + + // remove linear component (now we have k and m (y=k*x+m)) + for(unsigned int x = 0; x < cfgNfft_; ++x ) { + prepProcDataBuffer_[x] = prepProcDataBuffer_[x] - (k*x + m); + } +} + +void ecmcFFT::printEcDataArray(uint8_t* data, + size_t size, + ecmcEcDataType dt, + int objId) { + printf("fft id: %d, data: ",objId); + + size_t dataElementSize = getEcDataTypeByteSize(dt); + + uint8_t *pData = data; + for(unsigned int i = 0; i < size / dataElementSize; ++i) { + switch(dt) { + case ECMC_EC_U8: + printf("%hhu\n",getUint8(pData)); + break; + case ECMC_EC_S8: + printf("%hhd\n",getInt8(pData)); + break; + case ECMC_EC_U16: + printf("%hu\n",getUint16(pData)); + break; + case ECMC_EC_S16: + printf("%hd\n",getInt16(pData)); + break; + case ECMC_EC_U32: + printf("%u\n",getUint32(pData)); + break; + case ECMC_EC_S32: + printf("%d\n",getInt32(pData)); + break; + case ECMC_EC_U64: + printf("%" PRIu64 "\n",getInt64(pData)); + break; + case ECMC_EC_S64: + printf("%" PRId64 "\n",getInt64(pData)); + break; + case ECMC_EC_F32: + printf("%f\n",getFloat32(pData)); + break; + case ECMC_EC_F64: + printf("%lf\n",getFloat64(pData)); + break; + default: + break; + } + + pData += dataElementSize; + } +} + +void ecmcFFT::printComplexArray(std::complex* fftBuff, + size_t elements, + int objId) { + printf("fft id: %d, results: \n",objId); + for(unsigned int i = 0 ; i < elements ; ++i ) { + printf("%d: %lf\n", i, std::abs(fftBuff[i])); + } +} + +int ecmcFFT::dataTypeSupported(ecmcEcDataType dt) { + + 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; +} + +uint8_t ecmcFFT::getUint8(uint8_t* data) { + return *data; +} + +int8_t ecmcFFT::getInt8(uint8_t* data) { + int8_t* p=(int8_t*)data; + return *p; +} + +uint16_t ecmcFFT::getUint16(uint8_t* data) { + uint16_t* p=(uint16_t*)data; + return *p; +} + +int16_t ecmcFFT::getInt16(uint8_t* data) { + int16_t* p=(int16_t*)data; + return *p; +} + +uint32_t ecmcFFT::getUint32(uint8_t* data) { + uint32_t* p=(uint32_t*)data; + return *p; +} + +int32_t ecmcFFT::getInt32(uint8_t* data) { + int32_t* p=(int32_t*)data; + return *p; +} + +uint64_t ecmcFFT::getUint64(uint8_t* data) { + uint64_t* p=(uint64_t*)data; + return *p; +} + +int64_t ecmcFFT::getInt64(uint8_t* data) { + int64_t* p=(int64_t*)data; + return *p; +} + +float ecmcFFT::getFloat32(uint8_t* data) { + float* p=(float*)data; + return *p; +} + +double ecmcFFT::getFloat64(uint8_t* data) { + double* p=(double*)data; + return *p; +} + +size_t ecmcFFT::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 ecmcFFT::initAsyn() { + + // Add enable "plugin.fft%d.enable" + std::string paramName =ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_ENABLE; + + if( createParam(0, paramName.c_str(), asynParamInt32, &asynEnableId_) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter enable"); + } + setIntegerParam(asynEnableId_, cfgEnable_); + + // Add rawdata "plugin.fft%d.rawdata" + paramName =ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_RAWDATA; + + if( createParam(0, paramName.c_str(), asynParamFloat64Array, &asynRawDataId_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter rawdata"); + } + doCallbacksFloat64Array(rawDataBuffer_, cfgNfft_, asynRawDataId_,0); + + // Add rawdata "plugin.fft%d.preprocdata" + paramName =ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_PPDATA; + + if( createParam(0, paramName.c_str(), asynParamFloat64Array, &asynPPDataId_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter preprocdata"); + } + doCallbacksFloat64Array(prepProcDataBuffer_, cfgNfft_, asynPPDataId_,0); + + + + // Add fft amplitude "plugin.fft%d.fftamplitude" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_FFT_AMP; + + if( createParam(0, paramName.c_str(), asynParamFloat64Array, &asynFFTAmpId_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter fftamplitude"); + } + doCallbacksFloat64Array(fftBufferResultAmp_, cfgNfft_/2+1, asynFFTAmpId_,0); + + // Add fft "plugin.fft%d.mode" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_FFT_MODE; + + if( createParam(0, paramName.c_str(), asynParamInt32, &asynFFTModeId_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter mode"); + } + setIntegerParam(asynFFTModeId_, (epicsInt32)cfgMode_); + + // Add fft "plugin.fft%d.status" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_FFT_STAT; + + if( createParam(0, paramName.c_str(), asynParamInt32, &asynFFTStatId_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter status"); + } + setIntegerParam(asynFFTStatId_, (epicsInt32)status_); + + // Add fft "plugin.fft%d.source" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_FFT_SOURCE; + + if( createParam(0, paramName.c_str(), asynParamInt8Array, &asynSourceId_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter source"); + } + doCallbacksInt8Array(cfgDataSourceStr_, strlen(cfgDataSourceStr_), asynSourceId_,0); + + // Add fft "plugin.fft%d.trigg" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_FFT_TRIGG; + + if( createParam(0, paramName.c_str(), asynParamInt32, &asynTriggId_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter trigg"); + } + setIntegerParam(asynTriggId_, (epicsInt32)triggOnce_); + + // Add fft "plugin.fft%d.fftxaxis" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_FFT_X_FREQS; + + if( createParam(0, paramName.c_str(), asynParamFloat64Array, &asynFFTXAxisId_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter xaxisfreqs"); + } + doCallbacksFloat64Array(fftBufferXAxis_,cfgNfft_ / 2 + 1, asynFFTXAxisId_,0); + + // Add fft "plugin.fft%d.nfft" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_NFFT; + + if( createParam(0, paramName.c_str(), asynParamInt32, &asynNfftId_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter nfft"); + } + setIntegerParam(asynNfftId_, (epicsInt32)cfgNfft_); + + // Add fft "plugin.fft%d.rate" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_RATE; + + if( createParam(0, paramName.c_str(), asynParamFloat64, &asynSRateId_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter rate"); + } + setDoubleParam(asynSRateId_, cfgDataSampleRateHz_); + + // Add fft "plugin.fft%d.buffid" + paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) + + "." + ECMC_PLUGIN_ASYN_BUFF_ID; + + if( createParam(0, paramName.c_str(), asynParamInt32, &asynElementsInBuffer_ ) != asynSuccess ) { + throw std::runtime_error("Failed create asyn parameter trigg"); + } + setIntegerParam(asynElementsInBuffer_, (epicsInt32)elementsInBuffer_); + + // Update integers + callParamCallbacks(); +} + +// Avoid issues with std:to_string() +std::string ecmcFFT::to_string(int value) { + std::ostringstream os; + os << value; + return os.str(); +} + +void ecmcFFT::setEnable(int enable) { + cfgEnable_ = enable; + setIntegerParam(asynEnableId_, enable); +} + +void ecmcFFT::triggFFT() { + clearBuffers(); + triggOnce_ = 1; + setIntegerParam(asynTriggId_,0); +} + +void ecmcFFT::setModeFFT(FFT_MODE mode) { + cfgMode_ = mode; + setIntegerParam(asynFFTModeId_,(epicsInt32)mode); +} + +FFT_STATUS ecmcFFT::getStatusFFT() { + return status_; +} + +void ecmcFFT::updateStatus(FFT_STATUS status) { + status_ = status; + setIntegerParam(asynFFTStatId_,(epicsInt32) status); + + setIntegerParam(asynElementsInBuffer_, (epicsInt32)elementsInBuffer_); + + callParamCallbacks(); +} + +// Called from low prio worker thread. Makes the hard work +void ecmcFFT::doCalcWorker() { + + while(true) { + doCalcEvent_.wait(); + if(destructs_) { + break; + } + // Pre-process + removeDCOffset(); // Remove dc on rawdata + removeLin(); // Remove fitted line + // Process + calcFFT(); // FFT cacluation + // Post-process + scaleFFT(); // Scale FFT + calcFFTAmp(); // Calculate amplitude from complex + calcFFTXAxis(); // Calculate x axis + + doCallbacksFloat64Array(rawDataBuffer_, cfgNfft_, asynRawDataId_, 0); + doCallbacksFloat64Array(prepProcDataBuffer_, cfgNfft_, asynPPDataId_, 0); + doCallbacksFloat64Array(fftBufferResultAmp_,cfgNfft_/2+1, asynFFTAmpId_, 0); + doCallbacksFloat64Array(fftBufferXAxis_, cfgNfft_/2+1, asynFFTXAxisId_,0); + callParamCallbacks(); + if(cfgDbgMode_){ + printComplexArray(fftBufferResult_, + cfgNfft_, + objectId_); + printEcDataArray((uint8_t*)rawDataBuffer_, + cfgNfft_*sizeof(double), + ECMC_EC_F64, + objectId_); + } + + clearBuffers(); + triggOnce_ = 0; // Wait for next trigger if in trigg mode + setIntegerParam(asynTriggId_,triggOnce_); + fftWaitingForCalc_ = 0; + } +} + +asynStatus ecmcFFT::writeInt32(asynUser *pasynUser, epicsInt32 value) { + int function = pasynUser->reason; + if( function == asynEnableId_ ) { + cfgEnable_ = value; + return asynSuccess; + } else if( function == asynFFTModeId_){ + cfgMode_ = (FFT_MODE)value; + return asynSuccess; + } else if( function == asynTriggId_){ + triggOnce_ = value > 0; + return asynSuccess; + } + return asynError; +} + +asynStatus ecmcFFT::readInt32(asynUser *pasynUser, epicsInt32 *value) { + int function = pasynUser->reason; + if( function == asynEnableId_ ) { + *value = cfgEnable_; + return asynSuccess; + } else if( function == asynFFTModeId_ ){ + *value = cfgMode_; + return asynSuccess; + } else if( function == asynTriggId_ ){ + *value = triggOnce_; + return asynSuccess; + }else if( function == asynFFTStatId_ ){ + *value = (epicsInt32)status_; + return asynSuccess; + }else if( function == asynNfftId_ ){ + *value = (epicsInt32)cfgNfft_; + return asynSuccess; + }else if( function == asynElementsInBuffer_){ + *value = (epicsInt32)elementsInBuffer_; + return asynSuccess; + } + + return asynError; +} + +asynStatus ecmcFFT::readFloat64Array(asynUser *pasynUser, epicsFloat64 *value, + size_t nElements, size_t *nIn) { + int function = pasynUser->reason; + if( function == asynRawDataId_ ) { + unsigned int ncopy = cfgNfft_; + if(nElements < ncopy) { + ncopy = nElements; + } + memcpy (value, rawDataBuffer_, ncopy); + *nIn = ncopy; + return asynSuccess; + } else if( function == asynPPDataId_) { + unsigned int ncopy = cfgNfft_; + if(nElements < ncopy) { + ncopy = nElements; + } + memcpy (value, prepProcDataBuffer_, ncopy); + *nIn = ncopy; + return asynSuccess; + } else if( function == asynFFTXAxisId_ ) { + unsigned int ncopy = cfgNfft_/ 2 + 1; + if(nElements < ncopy) { + ncopy = nElements; + } + memcpy (value, fftBufferXAxis_, ncopy); + *nIn = ncopy; + return asynSuccess; + } if( function == asynFFTAmpId_ ) { + unsigned int ncopy = cfgNfft_/ 2 + 1; + if(nElements < ncopy) { + ncopy = nElements; + } + memcpy (value, fftBufferResultAmp_, ncopy); + *nIn = ncopy; + return asynSuccess; + } + + *nIn = 0; + return asynError; +} + +asynStatus ecmcFFT::readInt8Array(asynUser *pasynUser, epicsInt8 *value, + size_t nElements, size_t *nIn) { + int function = pasynUser->reason; + if( function == asynSourceId_ ) { + unsigned int ncopy = strlen(cfgDataSourceStr_); + if(nElements < ncopy) { + ncopy = nElements; + } + memcpy (value, cfgDataSourceStr_, ncopy); + *nIn = ncopy; + return asynSuccess; + } + + *nIn = 0; + return asynError; +} + +asynStatus ecmcFFT::readFloat64(asynUser *pasynUser, epicsFloat64 *value) { + int function = pasynUser->reason; + if( function == asynSRateId_ ) { + *value = cfgDataSampleRateHz_; + return asynSuccess; + } + + return asynError; +} + +/* y = k*x+m */ +int ecmcFFT::leastSquare(int n, const double y[], double* k, double* m){ + double sumx = 0.0; + double sumx2 = 0.0; + double sumxy = 0.0; + double sumy = 0.0; + double sumy2 = 0.0; + + for (int x = 0; x < n; ++x){ + //simulate x by just index + sumx += x; + sumx2 += x * x; + sumxy += x * y[x]; + sumy += y[x]; + sumy2 += y[x] * y[x]; + } + + double denom = (n * sumx2 - sumx * sumx); + if (denom == 0) { + // Cannot dive by 0.. something wrong.. + *k = 0; + *m = 0; + return 1; // Error + } + + *k = (n * sumxy - sumx * sumy) / denom; + *m = (sumy * sumx2 - sumx * sumxy) / denom; + return 0; +} diff --git a/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFT.h b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFT.h new file mode 100644 index 0000000..3f0783f --- /dev/null +++ b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFT.h @@ -0,0 +1,153 @@ +/*************************************************************************\ +* 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. +* +* ecmcFFT.h +* +* Created on: Mar 22, 2020 +* Author: anderssandstrom +* +\*************************************************************************/ +#ifndef ECMC_FFT_H_ +#define ECMC_FFT_H_ + +#include +#include "ecmcDataItem.h" +#include "ecmcAsynPortDriver.h" +#include "ecmcFFTDefs.h" +#include "inttypes.h" +#include +#include "kissfft/kissfft.hh" + +class ecmcFFT : public asynPortDriver { + public: + + /** ecmc FFT class + * This object can throw: + * - bad_alloc + * - invalid_argument + * - runtime_error + * - out_of_range + */ + ecmcFFT(int fftIndex, // index of this object + char* configStr, + char* portName); + ~ecmcFFT(); + + // 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 connectToDataSource(); + void setEnable(int enable); + void setModeFFT(FFT_MODE mode); + FFT_STATUS getStatusFFT(); + void clearBuffers(); + void triggFFT(); + void doCalcWorker(); // Called from worker thread calc the results + virtual asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value); + virtual asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value); + virtual asynStatus readFloat64Array(asynUser *pasynUser, epicsFloat64 *value, + size_t nElements, size_t *nIn); + virtual asynStatus readInt8Array(asynUser *pasynUser, epicsInt8 *value, + size_t nElements, size_t *nIn); + virtual asynStatus readFloat64(asynUser *pasynUser, epicsFloat64 *value); + + + private: + void parseConfigStr(char *configStr); + void addDataToBuffer(double data); + void calcFFT(); + void scaleFFT(); + void calcFFTAmp(); + void calcFFTXAxis(); + void removeDCOffset(); + void removeLin(); + void initAsyn(); + void updateStatus(FFT_STATUS status); // Also updates asynparam + static int dataTypeSupported(ecmcEcDataType dt); + + ecmcDataItem *dataItem_; + ecmcDataItemInfo *dataItemInfo_; + ecmcAsynPortDriver *asynPort_; + kissfft* fftDouble_; + double* rawDataBuffer_; // Input data (real) + double* prepProcDataBuffer_; // Preprocessed data (real) + std::complex* fftBufferInput_; // Result (complex) + std::complex* fftBufferResult_; // Result (complex) + double* fftBufferResultAmp_; // Resulting amplitude (abs of fftBufferResult_) + double* fftBufferXAxis_; // FFT x axis with freqs + size_t elementsInBuffer_; + double ecmcSampleRateHz_; + int dataSourceLinked_; // To avoid link several times + // ecmc callback handle for use when deregister at unload + int callbackHandle_; + int fftWaitingForCalc_; + int destructs_; + int objectId_; // Unique object id + int triggOnce_; + int cycleCounter_; + int ignoreCycles_; + double scale_; // Config: Data set size + FFT_STATUS status_; // Status/state (NO_STAT, IDLE, ACQ, CALC) + + // Config options + char* cfgDataSourceStr_; // Config: data source string + int cfgDbgMode_; // Config: allow dbg printouts + int cfgApplyScale_; // Config: apply scale 1/nfft + int cfgDcRemove_; // Config: remove dc (average) + int cfgLinRemove_; // Config: remove linear componet (by least square) + size_t cfgNfft_; // Config: Data set size + int cfgEnable_; // Config: Enable data acq./calc. + FFT_MODE cfgMode_; // Config: Mode continous or triggered. + double cfgFFTSampleRateHz_; // Config: Sample rate (defaults to ecmc rate) + double cfgScale_; + double cfgDataSampleRateHz_; // Config: Sample for data + + // Asyn + int asynEnableId_; // Enable/disable acq./calcs + int asynRawDataId_; // Raw data (input) array (double) + int asynPPDataId_; // Pre-processed data array (double) + int asynFFTAmpId_; // FFT amplitude array (double) + int asynFFTModeId_; // FFT mode (cont/trigg) + int asynFFTStatId_; // FFT status (no_stat/idle/acq/calc) + int asynSourceId_; // SOURCE + int asynTriggId_; // Trigg new measurement + int asynFFTXAxisId_; // FFT X-axis frequencies + int asynNfftId_; // NFFT + int asynSRateId_; // Sample rate + int asynElementsInBuffer_; // Current buffer index + + // Thread related + epicsEvent doCalcEvent_; + + + // 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 void printComplexArray(std::complex* fftBuff, + size_t elements, + int objId); + static std::string to_string(int value); + static int leastSquare(int n, + const double y[], + double* k, + double* m); // y=kx+m +}; + +#endif /* ECMC_FFT_H_ */ diff --git a/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFTDefs.h b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFTDefs.h new file mode 100644 index 0000000..8e6d4cd --- /dev/null +++ b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFTDefs.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. +* +* ecmcFFTDefs.h +* +* Created on: Mar 22, 2020 +* Author: anderssandstrom +* Credits to https://github.com/sgreg/dynamic-loading +* +\*************************************************************************/ + +#ifndef ECMC_FFT_DEFS_H_ +#define ECMC_FFT_DEFS_H_ + +// Options +#define ECMC_PLUGIN_DBG_PRINT_OPTION_CMD "DBG_PRINT=" +#define ECMC_PLUGIN_SOURCE_OPTION_CMD "SOURCE=" +#define ECMC_PLUGIN_NFFT_OPTION_CMD "NFFT=" +//#define ECMC_PLUGIN_APPLY_SCALE_OPTION_CMD "APPLY_SCALE=" +#define ECMC_PLUGIN_RM_DC_OPTION_CMD "RM_DC=" +#define ECMC_PLUGIN_ENABLE_OPTION_CMD "ENABLE=" +#define ECMC_PLUGIN_RATE_OPTION_CMD "RATE=" +#define ECMC_PLUGIN_RM_LIN_OPTION_CMD "RM_LIN=" +#define ECMC_PLUGIN_SCALE_OPTION_CMD "SCALE=" + + +// CONT, TRIGG +#define ECMC_PLUGIN_MODE_OPTION_CMD "MODE=" +#define ECMC_PLUGIN_MODE_CONT_OPTION "CONT" +#define ECMC_PLUGIN_MODE_TRIGG_OPTION "TRIGG" + +typedef enum FFT_MODE{ + NO_MODE = 0, + CONT = 1, + TRIGG = 2, +} FFT_MODE; + +typedef enum FFT_STATUS{ + NO_STAT = 0, + IDLE = 1, // Doing nothing, waiting for trigg + ACQ = 2, // Acquireing data + CALC = 3, // Calc FFT +} FFT_STATUS; + +/** Just one error code in "c" part of plugin +(error handled with exceptions i c++ part) */ +#define ECMC_PLUGIN_FFT_ERROR_CODE 1 + +// Default size (must be n²) +#define ECMC_PLUGIN_DEFAULT_NFFT 4096 + +#endif /* ECMC_FFT_DEFS_H_ */ diff --git a/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFTWrap.cpp b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFTWrap.cpp new file mode 100644 index 0000000..bf07ab0 --- /dev/null +++ b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFTWrap.cpp @@ -0,0 +1,133 @@ +/*************************************************************************\ +* 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. +* +* ecmcFFTWrap.cpp +* +* Created on: Mar 22, 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 "ecmcFFTWrap.h" +#include "ecmcFFT.h" +#include "ecmcFFTDefs.h" + +#define ECMC_PLUGIN_MAX_PORTNAME_CHARS 64 +#define ECMC_PLUGIN_PORTNAME_PREFIX "PLUGIN.FFT" + +static std::vector ffts; +static int fftObjCounter = 0; +static char portNameBuffer[ECMC_PLUGIN_MAX_PORTNAME_CHARS]; + +int createFFT(char* configStr) { + + // create new ecmcFFT object + ecmcFFT* fft = NULL; + + // create asynport name for new object () + memset(portNameBuffer, 0, ECMC_PLUGIN_MAX_PORTNAME_CHARS); + snprintf (portNameBuffer, ECMC_PLUGIN_MAX_PORTNAME_CHARS, + ECMC_PLUGIN_PORTNAME_PREFIX "%d", fftObjCounter); + try { + fft = new ecmcFFT(fftObjCounter, configStr, portNameBuffer); + } + catch(std::exception& e) { + if(fft) { + delete fft; + } + printf("Exception: %s. Plugin will unload.\n",e.what()); + return ECMC_PLUGIN_FFT_ERROR_CODE; + } + + ffts.push_back(fft); + fftObjCounter++; + + return 0; +} + +void deleteAllFFTs() { + for(std::vector::iterator pfft = ffts.begin(); pfft != ffts.end(); ++pfft) { + if(*pfft) { + delete (*pfft); + } + } +} + +int linkDataToFFTs() { + for(std::vector::iterator pfft = ffts.begin(); pfft != ffts.end(); ++pfft) { + if(*pfft) { + try { + (*pfft)->connectToDataSource(); + } + catch(std::exception& e) { + printf("Exception: %s. Plugin will unload.\n",e.what()); + return ECMC_PLUGIN_FFT_ERROR_CODE; + } + } + } + return 0; +} + +int enableFFT(int fftIndex, int enable) { + try { + ffts.at(fftIndex)->setEnable(enable); + } + catch(std::exception& e) { + printf("Exception: %s. FFT index out of range.\n",e.what()); + return ECMC_PLUGIN_FFT_ERROR_CODE; + } + return 0; +} + +int clearFFT(int fftIndex) { + try { + ffts.at(fftIndex)->clearBuffers(); + } + catch(std::exception& e) { + printf("Exception: %s. FFT index out of range.\n",e.what()); + return ECMC_PLUGIN_FFT_ERROR_CODE; + } + return 0; +} + +int triggFFT(int fftIndex) { + try { + ffts.at(fftIndex)->triggFFT(); + } + catch(std::exception& e) { + printf("Exception: %s. FFT index out of range.\n",e.what()); + return ECMC_PLUGIN_FFT_ERROR_CODE; + } + return 0; +} + +int modeFFT(int fftIndex, FFT_MODE mode) { + try { + ffts.at(fftIndex)->setModeFFT(mode); + } + catch(std::exception& e) { + printf("Exception: %s. FFT index out of range.\n",e.what()); + return ECMC_PLUGIN_FFT_ERROR_CODE; + } + return 0; +} + +FFT_STATUS statFFT(int fftIndex) { + try { + return ffts.at(fftIndex)->getStatusFFT(); + } + catch(std::exception& e) { + printf("Exception: %s. FFT index out of range.\n",e.what()); + return NO_STAT; + } + return NO_STAT; +} diff --git a/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFTWrap.h b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFTWrap.h new file mode 100644 index 0000000..fe4650c --- /dev/null +++ b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcFFTWrap.h @@ -0,0 +1,112 @@ +/*************************************************************************\ +* 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. +* +* ecmcFFTWrap.h +* +* Created on: Mar 22, 2020 +* Author: anderssandstrom +* +\*************************************************************************/ +#ifndef ECMC_FFT_WRAP_H_ +#define ECMC_FFT_WRAP_H_ +#include "ecmcFFTDefs.h" + +# ifdef __cplusplus +extern "C" { +# endif // ifdef __cplusplus + +/** \brief Create new FFT object + * + * The plugin supports creation of multiple FFT objects\n + * (if loaded several times).\n + * The different fft are adressed by fftindex (in other functions below).\n + * The first loaded fft get index 0 and then increases for each load.\n + * This function call will create the custom asynparameters dedicated for this plugin.\ + * The configuration string needs to define a data source by:\n + * "SOURCE=;"\n + * Example:\n + * "SOURCE=ec0.s1.AI_1";\n + * \param[in] configStr Configuration string.\n + * + * \return 0 if success or otherwise an error code.\n + */ +int createFFT(char *configStr); + +/** \brief Enable/disable FFT object + * + * Enable/disable FFT object. If disabled no data will be acquired\n + * and no calculations will be made.\n + * \param[in] fftIndex Index of fft (first loaded fft have index 0 then increases)\n + * \param[in] enable enable/disable (1/0).\n + * + * \return 0 if success or otherwise an error code.\n + */ +int enableFFT(int fftIndex, int enable); + +/** \brief Clear FFT object\n + * + * Clears buffers. After this command the acquistion can start from scratch.\n + * \param[in] fftIndex Index of fft (first loaded fft have index 0 then increases)\n + * + * \return 0 if success or otherwise an error code.\n + */ +int clearFFT(int fftIndex); + +/** \brief Set mode of FFT object + * + * The FFT object can measure in two differnt modes:\n + * CONT(1) : Continious measurement (Acq data, calc, then Acq data ..)\n + * TRIGG(2): Measurements are triggered from plc or over asyn and is only done once (untill next trigger)\n + * \param[in] fftIndex Index of fft (first loaded fft have index 0 then increases)\n + * \param[in] mode Mode CONT(1) or TRIGG(2)\n + * + * \return 0 if success or otherwise an error code.\n + */ +int modeFFT(int fftIndex, FFT_MODE mode); + +/** \brief Trigger FFT object\n + * + * If in triggered mode a new measurment cycle is initiated (fft will be cleared first).\n + * \param[in] fftIndex Index of fft (first loaded fft have index 0 then increases)\n + * + * \return 0 if success or otherwise an error code.\n + */ +int triggFFT(int fftIndex); + +/** \brief Get status of FFT object + * + * The FFT object can be in different states:\n + * NO_STAT(0): Invalid state (something is most likely wrong)\n + * IDLE(1) : Waiting for trigger in triggered mode\n + * ACQ(2) : Acquiring data (filling data buffer)\n + * CALC(3) : Calculating FFT results\n + * \param[in] fftIndex Index of fft (first loaded fft have index 0 then increases)\n + * + * \return Status of fft (if index is out of range NO_STAT will be returned).\n + */ +FFT_STATUS statFFT(int fftIndex); + +/** \brief Link data to _all_ fft objects + * + * This tells the FFT 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 linkDataToFFTs(); + +/** \brief Deletes all created fft objects\n + * + * Should be called when destructs.\n + */ + +void deleteAllFFTs(); + +# ifdef __cplusplus +} +# endif // ifdef __cplusplus + +#endif /* ECMC_FFT_WRAP_H_ */ diff --git a/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcPluginFFT.c b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcPluginFFT.c new file mode 100644 index 0000000..f0efe90 --- /dev/null +++ b/ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/ecmcPluginFFT.c @@ -0,0 +1,301 @@ +/*************************************************************************\ +* 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. +* +* ecmcPluginExample.cpp +* +* Created on: Mar 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 "ecmcFFTDefs.h" +#include "ecmcFFTWrap.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 fftConstruct(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 createFFT(configStr); +} + +/** Optional function. + * Will be called once at unload. + **/ +void fftDestruct(void) +{ + deleteAllFFTs(); + 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 fftRealtime(int ecmcError) +{ + lastEcmcError = ecmcError; + return 0; +} + +/** 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 fftEnterRT(){ + return linkDataToFFTs(); +} + +/** Optional function. + * Will be called once just before leaving realtime mode + * Return value other than 0 will be considered error. + **/ +int fftExitRT(void){ + return 0; +} + +// Plc function for clear of buffers +double fft_clear(double index) { + return (double)clearFFT((int)index); +} + +// Plc function for enable +double fft_enable(double index, double enable) { + return (double)enableFFT((int)index, (int)enable); +} + +// Plc function for trigg new measurement (will clear buffers) +double fft_trigg(double index) { + return (double)triggFFT((int)index); +} + +// Plc function for enable +double fft_mode(double index, double mode) { + return (double)modeFFT((int)index, (FFT_MODE)((int)mode)); +} + +// Plc function for enable +double fft_stat(double index) { + return (double)statFFT((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_FFT", + // Description + .desc = "FFT 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" : Sets source variable for FFT (example: ec0.s1.AI_1).\n" + " "ECMC_PLUGIN_NFFT_OPTION_CMD" : Data points to collect, default = 4096.\n" + " "ECMC_PLUGIN_SCALE_OPTION_CMD"scalefactor : Apply scale to source data, default = 1.0.\n" + " "ECMC_PLUGIN_RM_DC_OPTION_CMD"<1/0> : Remove DC offset of input data (SOURCE), default = disabled.\n" + " "ECMC_PLUGIN_RM_LIN_OPTION_CMD"<1/0> : Remove linear component in data (SOURCE) by least square, default = disabled.\n" + " "ECMC_PLUGIN_ENABLE_OPTION_CMD"<1/0> : Enable data acq. and calcs (can be controlled over asyn), default = disabled.\n" + " "ECMC_PLUGIN_MODE_OPTION_CMD" : Continious or triggered mode, defaults to TRIGG\n" + " "ECMC_PLUGIN_RATE_OPTION_CMD" : fft data sample rate in hz (must be lower than ecmc rate and (ecmc_rate/fft_rate)=integer), default = ecmc rate." + , + // Plugin version + .version = ECMC_EXAMPLE_PLUGIN_VERSION, + // Optional construct func, called once at load. NULL if not definded. + .constructFnc = fftConstruct, + // Optional destruct func, called once at unload. NULL if not definded. + .destructFnc = fftDestruct, + // Optional func that will be called each rt cycle. NULL if not definded. + .realtimeFnc = fftRealtime, + // Optional func that will be called once just before enter realtime mode + .realtimeEnterFnc = fftEnterRT, + // Optional func that will be called once just before exit realtime mode + .realtimeExitFnc = fftExitRT, + // PLC funcs + .funcs[0] = + { /*----fft_clear----*/ + // Function name (this is the name you use in ecmc plc-code) + .funcName = "fft_clear", + // Function description + .funcDesc = "double fft_clear(index) : Clear/reset fft[index].", + /** + * 7 different prototypes allowed (only doubles since reg in plc). + * Only funcArg${argCount} func shall be assigned the rest set to NULL. + **/ + .funcArg0 = NULL, + .funcArg1 = fft_clear, + .funcArg2 = NULL, + .funcArg3 = NULL, + .funcArg4 = NULL, + .funcArg5 = NULL, + .funcArg6 = NULL, + .funcArg7 = NULL, + .funcArg8 = NULL, + .funcArg9 = NULL, + .funcArg10 = NULL, + .funcGenericObj = NULL, + }, + .funcs[1] = + { /*----fft_enable----*/ + // Function name (this is the name you use in ecmc plc-code) + .funcName = "fft_enable", + // Function description + .funcDesc = "double fft_enable(index, enable) : Set enable for fft[index].", + /** + * 7 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 = fft_enable, + .funcArg3 = NULL, + .funcArg4 = NULL, + .funcArg5 = NULL, + .funcArg6 = NULL, + .funcArg7 = NULL, + .funcArg8 = NULL, + .funcArg9 = NULL, + .funcArg10 = NULL, + .funcGenericObj = NULL, + }, + .funcs[2] = + { /*----fft_trigg----*/ + // Function name (this is the name you use in ecmc plc-code) + .funcName = "fft_trigg", + // Function description + .funcDesc = "double fft_trigg(index) : Trigg new measurement for fft[index]. Will clear buffers.", + /** + * 7 different prototypes allowed (only doubles since reg in plc). + * Only funcArg${argCount} func shall be assigned the rest set to NULL. + **/ + .funcArg0 = NULL, + .funcArg1 = fft_trigg, + .funcArg2 = NULL, + .funcArg3 = NULL, + .funcArg4 = NULL, + .funcArg5 = NULL, + .funcArg6 = NULL, + .funcArg7 = NULL, + .funcArg8 = NULL, + .funcArg9 = NULL, + .funcArg10 = NULL, + .funcGenericObj = NULL, + }, + .funcs[3] = + { /*----fft_mode----*/ + // Function name (this is the name you use in ecmc plc-code) + .funcName = "fft_mode", + // Function description + .funcDesc = "double fft_mode(index, mode) : Set mode Cont(1)/Trigg(2) for fft[index].", + /** + * 7 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 = fft_mode, + .funcArg3 = NULL, + .funcArg4 = NULL, + .funcArg5 = NULL, + .funcArg6 = NULL, + .funcArg7 = NULL, + .funcArg8 = NULL, + .funcArg9 = NULL, + .funcArg10 = NULL, + .funcGenericObj = NULL, + }, + .funcs[4] = + { /*----fft_stat----*/ + // Function name (this is the name you use in ecmc plc-code) + .funcName = "fft_stat", + // Function description + .funcDesc = "double fft_stat(index) : Get status of fft (NO_STAT, IDLE, ACQ, CALC) for fft[index].", + /** + * 7 different prototypes allowed (only doubles since reg in plc). + * Only funcArg${argCount} func shall be assigned the rest set to NULL. + **/ + .funcArg0 = NULL, + .funcArg1 = fft_stat, + .funcArg2 = NULL, + .funcArg3 = NULL, + .funcArg4 = NULL, + .funcArg5 = NULL, + .funcArg6 = NULL, + .funcArg7 = NULL, + .funcArg8 = NULL, + .funcArg9 = NULL, + .funcArg10 = NULL, + .funcGenericObj = NULL, + }, + .funcs[5] = {0}, // last element set all to zero.. + // PLC consts + /* CONTINIOUS MODE = 1 */ + .consts[0] = { + .constName = "fft_CONT", + .constDesc = "FFT Mode: Continious", + .constValue = CONT + }, + /* TRIGGERED MODE = 2 */ + .consts[1] = { + .constName = "fft_TRIGG", + .constDesc = "FFT Mode :Triggered", + .constValue = TRIGG + }, + /* TRIGGERED MODE = 2 */ + .consts[2] = { + .constName = "fft_NO_STAT", + .constDesc = "FFT Status: Invalid state", + .constValue = NO_STAT, + }, + /* TRIGGERED MODE = 2 */ + .consts[3] = { + .constName = "fft_IDLE", + .constDesc = "FFT Status: Idle state (waiting for trigger)", + .constValue = IDLE + }, + /* TRIGGERED MODE = 2 */ + .consts[4] = { + .constName = "fft_ACQ", + .constDesc = "FFT Status: Acquiring data", + .constValue = ACQ + }, + /* TRIGGERED MODE = 2 */ + .consts[5] = { + .constName = "fft_CALC", + .constDesc = "FFT Status: Calculating result", + .constValue = CALC + }, + .consts[6] = {0}, // last element set all to zero.. +}; + +ecmc_plugin_register(pluginDataDef); + +# ifdef __cplusplus +} +# endif // ifdef __cplusplus diff --git a/iocsh/.keep b/iocsh/.keep new file mode 100644 index 0000000..e69de29 diff --git a/opi/.keep b/opi/.keep new file mode 100644 index 0000000..e69de29 diff --git a/patch/Site/HISTORY.md b/patch/Site/HISTORY.md new file mode 100644 index 0000000..3516ac7 --- /dev/null +++ b/patch/Site/HISTORY.md @@ -0,0 +1,7 @@ +# E3_MODULE_VERSION-what_ever_filename.p0.patch + +Generic Description..... + +* created by Jeong Han Lee, han.lee@esss.se +* related URL or reference https://github.com/icshwi +* Tuesday, February 13 13:24:57 CET 2018 diff --git a/patch/Site/README.md b/patch/Site/README.md new file mode 100644 index 0000000..fbed8e6 --- /dev/null +++ b/patch/Site/README.md @@ -0,0 +1,22 @@ +# Site Specific EPICS Module Patch Files + +## Changes +The changes were tested in local environemnt, and commits to the forked repository and do pull request to the epics community module repository. + +* Check the original HASH, and your own master +* feb8856 : The original HASH +* master : Changed + + +## How to create a p0 patch file between commits + + +* Show what the difference between commits + + +* Create p0 patch + +``` +$git diff feb8856 master --no-prefix > ../patch/Site/E3_MODULE_VERSION-what_ever_filename.p0.patch +``` + diff --git a/template/.keep b/template/.keep new file mode 100644 index 0000000..e69de29