diff --git a/GNUmakefile b/GNUmakefile index ee0b528..1145c13 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -18,7 +18,7 @@ OPT_CXXFLAGS_YES = -O3 ECmasterECMC_VERSION = v1.1.0 # motorECMC_VERSION = 7.0.7-ESS #ecmc_VERSION = v9.0.1_RC4 -ecmc_VERSION = v9.0.1_RC4 +ecmc_VERSION = 9.1.0 ################################################################################ # THIS RELATES TO THE EtherCAT MASTER LIBRARY diff --git a/README.md b/README.md index 073ca20..22a8308 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,24 @@ ecmc_plugin_safety ====== -IMPORTANT: This plugin or ecmc does NOT offer any safety functionality. This plugin only offers a standard interface to an external safety system / safety PLC / safety relay!!!! +IMPORTANT: This plugin or ecmc does NOT offer any safety certified functionality. This plugin only offers a standard interface to an external safety system / safety PLC / safety relay!!!! +This plugin is designed for interfacing safety systems, see example in below picture, to ecmc motion axes. -This plugin is designed for interfacing safety systems to ecmc motion axes. +![Safety system](docs/safetyplc.png) -# Loading of plugin +# SS1-t +The plugin supports stopping axes according to a concept described as SS1-t, see below picture. In SS1-t the STO (or removal of power) from the axes are delayed for a certain defined time allowing a controlled rampdown of velocity. The rampdown of the axis is handled by the non-safe motion controller while the removal of power (or triggering of STO) after the time delay is handled by a safety system, . This makes it possible to stop moving axes in a controlled way and disable the drives before the power is interrupted (or STO triggered). This will result in a safer system and less harware failures and error messages. -Loading of the plugin is made by require: -``` -# Load safety plugin -require ecmc_plugin_safety -``` + After the time delay expires, hopfully all axes are at standstill and power is already removed from the drives. -# SS1 -The plugin supports stopping axes accoring to a concept described as SS1. In SS1 the STO (or removal of power) from the axes are delayed for a certain time allowing a controlled rampdown. This makes it possible to stop moving axes in a controlled way and disable of drives before the power is interrupted (or STO triggered). This will result in a safer system and less harware failures and error messages. -Basically the safey system is interfaced with binary signals (ethercat I/O). If, for instance, an emergency stop button is pressed the safety system immediately commands this plugin to rampdown velocity of axes (that a configured to stop). When all axes, that are configured to rampdown, have stopped then this plugin will disable the axes and set an ethercat output informing the safety system that the axes are standstill. After a certain timout the safety system will make sure power is removed from the motion axes by STO or removing power. The removal of power or triggering of STO will made regardless if the axes are at rest or not. A reset of the safety system, allowing power to the drives, will only be possible once the safety system gets a confirmation that all axes are at rest. +![SS1-t](docs/SS1-t.png) + +## Interface +Basically the safey system is interfaced with two binary signals (ethercat I/O): +* Ramp down command (from safety system to ecmc) +* Axis stand still status (to safety system from ecmc) + If, for instance, an safety event is triggerd by the safety system, it will immediately command this plugin to rampdown velocity of all axes (that a configured to stop). When all axes, that are configured to rampdown, have stopped then this plugin will disable the axes and set an ethercat output informing the safety system that the axes are standstill. After a certain timout the safety system will make sure power is removed from the motion axes by triggering an STO or removing power. The removal of power or triggering of STO will made regardless if the axes are at rest or not. A reset of the safety system, allowing power to the drives, will only be possible once the safety system gets a confirmation that all axes are at rest. The configuration is made by two commands: 1. ecmcAddSS1SafetyGroup() @@ -28,8 +30,8 @@ The ecmcAddSS1SafetyGroup() adds a SS1 safety group. The command takes the follo ``` ecmcAddSS1SafetyGroup(, , ,) : Name of group. - : Ethercat entry input for rampdown cmd. - : Ethercat entry output for group standstill status. + : Ethercat entry input for rampdown cmd. + : Ethercat entry output for group standstill status. : Time delay of STO [ms]. ``` Each group is interfacing the safety system through the same I/O (ethercat). @@ -69,7 +71,7 @@ epicsEnvSet(RAMP_DOWN_CMD,"ec${ECMC_EC_MASTER_ID}.s${DRV_SLAVE}.ZERO.0") epicsEnvSet(STANDSTILL_STAT,"ec${ECMC_EC_MASTER_ID}.s${DRV_SLAVE}.ZERO.1") ${SCRIPTEXEC} ${ecmc_plugin_safety_DIR}addSS1Group.cmd "NAME=first,EC_RAMP_DOWN_CMD=${RAMP_DOWN_CMD},EC_STANDSTILL_STAT=${STANDSTILL_STAT},DELAY_MS=500" -#- Add axes +# Add axes to group "first" ${SCRIPTEXEC} ${ecmc_plugin_safety_DIR}addAxisToGroup.cmd "NAME=first,AX_ID=1,VELO_LIM=1" ${SCRIPTEXEC} ${ecmc_plugin_safety_DIR}addAxisToGroup.cmd "NAME=first,AX_ID=2,VELO_LIM=1" ${SCRIPTEXEC} ${ecmc_plugin_safety_DIR}addAxisToGroup.cmd "NAME=first,AX_ID=3,VELO_LIM=1" diff --git a/docs/.MODULE_LOG b/docs/.MODULE_LOG deleted file mode 100644 index b60b012..0000000 --- a/docs/.MODULE_LOG +++ /dev/null @@ -1,28 +0,0 @@ ->> -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/docs/SS1-t.png b/docs/SS1-t.png new file mode 100644 index 0000000..2244164 Binary files /dev/null and b/docs/SS1-t.png differ diff --git a/docs/ecmcPLC5HzFFT.png b/docs/ecmcPLC5HzFFT.png deleted file mode 100644 index 70cdaa9..0000000 Binary files a/docs/ecmcPLC5HzFFT.png and /dev/null differ diff --git a/docs/ecmcPLC5HzRAW.png b/docs/ecmcPLC5HzRAW.png deleted file mode 100644 index 866cea7..0000000 Binary files a/docs/ecmcPLC5HzRAW.png and /dev/null differ diff --git a/docs/gui/ecmcArrayGui.png b/docs/gui/ecmcArrayGui.png deleted file mode 100644 index feba6bf..0000000 Binary files a/docs/gui/ecmcArrayGui.png and /dev/null differ diff --git a/docs/gui/ecmcFFTGui.png b/docs/gui/ecmcFFTGui.png deleted file mode 100644 index f53c601..0000000 Binary files a/docs/gui/ecmcFFTGui.png and /dev/null differ diff --git a/docs/gui/ecmcFFTMainGui.png b/docs/gui/ecmcFFTMainGui.png deleted file mode 100644 index fcbffe2..0000000 Binary files a/docs/gui/ecmcFFTMainGui.png and /dev/null differ diff --git a/docs/safetyplc.png b/docs/safetyplc.png new file mode 100644 index 0000000..1d275cf Binary files /dev/null and b/docs/safetyplc.png differ diff --git a/scripts/addSS1Group.cmd b/scripts/addSS1Group.cmd index d9a4d7b..5e65031 100644 --- a/scripts/addSS1Group.cmd +++ b/scripts/addSS1Group.cmd @@ -16,18 +16,18 @@ #- #- Arguments: #- NAME : Name of safety group -#- EC_RAMP_DOWN_CMD : Ethercat entry for rampd down command (cmd from safety PLC/system) -#- EC_STANDSTILL_STAT : Ethercat entry for axes at standstill status (feedback to safety PLC/system) +#- EC_RAMP_DOWN : Ethercat entry for rampd down command, input to ecmc (command from safety PLC/system) +#- EC_AXES_STANDSTILL : Ethercat entry for all axes in group at standstill, output from ecmc (feedback to safety PLC/system) #- DELAY_MS : Time between rampdown command and STO #- ################################################################################# #- ecmcAddSS1SafetyGroup(, , ,) #- : Name of group. -#- : Ethercat entry input for rampdown cmd. -#- : Ethercat entry output for group standstill status. +#- : Ethercat entry input for rampdown cmd. +#- : Ethercat entry output for group standstill status. #- : Time delay of STO [ms]. -ecmcAddSS1SafetyGroup("${NAME}","${EC_RAMP_DOWN_CMD}","${EC_STANDSTILL_STAT}",${DELAY_MS=0}) +ecmcAddSS1SafetyGroup("${NAME}","${EC_RAMP_DOWN}","${EC_AXES_STANDSTILL}",${DELAY_MS=0}) #- Load SS1 group records dbLoadRecords("ss1.template","P=${ECMC_PREFIX},NAME=${NAME}") diff --git a/src/ecmcSS1SafetyGroup.cpp b/src/ecmcSS1SafetyGroup.cpp index a07640c..08d5ecf 100644 --- a/src/ecmcSS1SafetyGroup.cpp +++ b/src/ecmcSS1SafetyGroup.cpp @@ -182,8 +182,7 @@ void ecmcSS1SafetyGroup::validateCfgs() { } } -void ecmcSS1SafetyGroup::validateAxes() { - +void ecmcSS1SafetyGroup::validateAxes() { // Ensure that axis is not linked twice to group for(std::vector::iterator iaxis = axes_.begin(); iaxis != axes_.end(); ++iaxis) { int axisLinkedCounter = 0; @@ -213,11 +212,19 @@ void ecmcSS1SafetyGroup::validateAxes() { } } + int axisCounter = 0; // Check axis object exists and valid for(std::vector::iterator saxis = axes_.begin(); saxis != axes_.end(); ++saxis) { if(!getAxisValid((*saxis)->axisId_)) { throw std::runtime_error( "Safety: Ecmc Axis object not valid."); } + axisCounter++; + } + + // do not allow empty group + if(axisCounter == 0) { + printf("Safety %s: Error, group empty (axis count zero)\n",sName_); + throw std::runtime_error( "Safety: Error, empty group not allowed."); } } diff --git a/src/ecmcSafetyPlgWrap.cpp b/src/ecmcSafetyPlgWrap.cpp index 0ab1810..3239f2f 100644 --- a/src/ecmcSafetyPlgWrap.cpp +++ b/src/ecmcSafetyPlgWrap.cpp @@ -37,13 +37,31 @@ int setCfgString(const char* cfgString) { return 0; } +ecmcSS1SafetyGroup* getGroupFromName(const char *name) { + // Find group by name + for(std::vector::iterator psafetyGroup = safetyGroups.begin(); psafetyGroup != safetyGroups.end(); ++psafetyGroup) { + bool found = strcmp(name, (*psafetyGroup)->getName().c_str()) == 0; + if(found) { + return (*psafetyGroup); + } + } + return NULL; +} + int createSafetyGroup(const char *name, const char *ec_rampdown_cmd, const char *ec_standstill_status, int time_delay_ms) { + // ensure group does not already exist + ecmcSS1SafetyGroup* safetyGroup = getGroupFromName(name); + if( safetyGroup ) { + printf("Safety: Error, group %s already defined. Plugin will unload.\n",name); + throw std::runtime_error( "Safety: Error, group already defined."); + } + // create new ecmcSS1SafetyGroup object - ecmcSS1SafetyGroup* safetyGroup = NULL; + safetyGroup = NULL; // create asynport name for new object () memset(portNameBuffer, 0, ECMC_PLUGIN_MAX_PORTNAME_CHARS); @@ -61,7 +79,6 @@ int createSafetyGroup(const char *name, if(safetyGroup) { delete safetyGroup; } - return ECMC_PLUGIN_SAFETY_ERROR_CODE; } safetyGroups.push_back(safetyGroup); @@ -74,16 +91,17 @@ int addAxisToSafetyGroup(const char *groupName, int axisId, double veloLimit, int standStillTimeMs) { - // Find group by name - for(std::vector::iterator psafetyGroup = safetyGroups.begin(); psafetyGroup != safetyGroups.end(); ++psafetyGroup) { - bool found = strcmp(groupName, (*psafetyGroup)->getName().c_str()) == 0; - if(found) { - (*psafetyGroup)->addAxis(axisId,veloLimit,standStillTimeMs); - return asynSuccess; - } + + ecmcSS1SafetyGroup* grp = getGroupFromName(groupName); + + if( !grp ) { + printf("Safety: Error, group %s not found. Plugin will unload.\n",groupName); + throw std::runtime_error( "Safety: Error, group not found."); } - return asynError; + grp->addAxis(axisId,veloLimit,standStillTimeMs); + + return asynSuccess; } void deleteAllSafetyGroups() { @@ -98,17 +116,26 @@ void deleteAllSafetyGroups() { } int validate() { + int groupCount = 0; for(std::vector::iterator psafetyGroup = safetyGroups.begin(); psafetyGroup != safetyGroups.end(); ++psafetyGroup) { if(*psafetyGroup) { try { (*psafetyGroup)->validate(); + groupCount++; } catch(std::exception& e) { - printf("Exception: %s. Plugin will unload.\n",e.what()); + printf("Safety: %s. Plugin will unload.\n",e.what()); return ECMC_PLUGIN_SAFETY_ERROR_CODE; } } } + + // Do not allow loaded plugin with no group configured + if(groupCount == 0) { + printf("Saftey: Error, group count 0. Plugin will unload.\n"); + return ECMC_PLUGIN_SAFETY_ERROR_CODE; + } + return 0; } @@ -120,7 +147,7 @@ int executeSafetyGroups() { (*psafetyGroup)->execute(); } catch(std::exception& e) { - printf("Exception: %s. Plugin will unload.\n",e.what()); + printf("Safety: %s. Plugin will unload.\n",e.what()); return ECMC_PLUGIN_SAFETY_ERROR_CODE; } } @@ -165,7 +192,7 @@ int ecmcAddSS1SafetyGroup(const char* name, const char* ec_rampdown_cmd,const ch } if(time_delay_ms <= 0) { printf("Error: time_delay invalid.\n"); - exit (1); + exit (EXIT_FAILURE); } try { @@ -173,7 +200,7 @@ int ecmcAddSS1SafetyGroup(const char* name, const char* ec_rampdown_cmd,const ch } catch(std::exception& e) { printf("Exception: %s. Add safety group failed.\n",e.what()); - exit (1); + exit (EXIT_FAILURE); } return asynSuccess; @@ -224,24 +251,24 @@ int ecmcAddAxisToSafetyGroup(const char* name, int axis_id, double velo_lim, int if(axis_id <= 0) { printf("Error: Invalid axis id.\n"); - exit (1); + exit(EXIT_FAILURE); } if(velo_lim < 0) { printf("Error: Invalid velocity limit.\n"); - exit (1); + exit(EXIT_FAILURE); } if(stand_still_time < 0) { printf("Error: Invalid stand still filter time.\n"); - exit (1); + exit(EXIT_FAILURE); } try { return addAxisToSafetyGroup(name,axis_id, velo_lim, stand_still_time); } catch(std::exception& e) { printf("Exception: %s. Add axis to safety group failed.\n",e.what()); - exit (1); + exit(EXIT_FAILURE); } return asynSuccess;