Merge branch 'master' of git.psi.ch:epics_ioc_modules/ecmc_plugin_safety

This commit is contained in:
2024-02-21 16:10:11 +01:00
13 changed files with 76 additions and 68 deletions

View File

@@ -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

View File

@@ -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 <optional_version>
```
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>, <ec_rampdown_cmd>, <ec_standstill_status>,<time_delay_ms>)
<name> : Name of group.
<ec_rampdown_cmd> : Ethercat entry input for rampdown cmd.
<ec_standstill_status> : Ethercat entry output for group standstill status.
<ec_rampdown> : Ethercat entry input for rampdown cmd.
<ec_axes_at_rest> : Ethercat entry output for group standstill status.
<time_delay_ms> : 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"

View File

@@ -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

BIN
docs/SS1-t.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

BIN
docs/safetyplc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@@ -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>, <ec_rampdown_cmd>, <ec_standstill_status>,<time_delay_ms>)
#- <name> : Name of group.
#- <ec_rampdown_cmd> : Ethercat entry input for rampdown cmd.
#- <ec_standstill_status> : Ethercat entry output for group standstill status.
#- <ec_rampdown> : Ethercat entry input for rampdown cmd.
#- <ec_standstill> : Ethercat entry output for group standstill status.
#- <time_delay_ms> : 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}")

View File

@@ -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<safetyAxis*>::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<safetyAxis*>::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.");
}
}

View File

@@ -37,13 +37,31 @@ int setCfgString(const char* cfgString) {
return 0;
}
ecmcSS1SafetyGroup* getGroupFromName(const char *name) {
// Find group by name
for(std::vector<ecmcSS1SafetyGroup*>::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<ecmcSS1SafetyGroup*>::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<ecmcSS1SafetyGroup*>::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;