484 lines
15 KiB
C++
484 lines
15 KiB
C++
/*************************************************************************\
|
|
* 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.
|
|
*
|
|
* ecmcSocketCAN.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 <sstream>
|
|
#include "ecmcSocketCAN.h"
|
|
#include "ecmcPluginClient.h"
|
|
#include "ecmcAsynPortDriver.h"
|
|
#include "ecmcAsynPortDriverUtils.h"
|
|
#include "epicsThread.h"
|
|
|
|
// Start worker for socket read()
|
|
void f_worker_read(void *obj) {
|
|
if(!obj) {
|
|
printf("%s/%s:%d: Error: Worker read thread ecmcSocketCAN object NULL..\n",
|
|
__FILE__, __FUNCTION__, __LINE__);
|
|
return;
|
|
}
|
|
ecmcSocketCAN * canObj = (ecmcSocketCAN*)obj;
|
|
canObj->doReadWorker();
|
|
}
|
|
|
|
// Start worker for socket connect()
|
|
void f_worker_connect(void *obj) {
|
|
if(!obj) {
|
|
printf("%s/%s:%d: Error: Worker connect thread ecmcSocketCAN object NULL..\n",
|
|
__FILE__, __FUNCTION__, __LINE__);
|
|
return;
|
|
}
|
|
ecmcSocketCAN * canObj = (ecmcSocketCAN*)obj;
|
|
canObj->doConnectWorker();
|
|
}
|
|
|
|
/** ecmc ecmcSocketCAN class
|
|
* This object can throw:
|
|
* - bad_alloc
|
|
* - invalid_argument
|
|
* - runtime_error
|
|
*/
|
|
ecmcSocketCAN::ecmcSocketCAN(char* configStr,
|
|
char* portName,
|
|
int exeSampleTimeMs) {
|
|
// Init
|
|
cfgCanIFStr_ = NULL;
|
|
cfgDbgMode_ = 0;
|
|
cfgAutoConnect_ = 1;
|
|
destructs_ = 0;
|
|
socketId_ = -1;
|
|
connected_ = 0;
|
|
writeBuffer_ = NULL;
|
|
deviceCounter_ = 0;
|
|
refreshNeeded_ = 0;
|
|
errorCode_ = 0;
|
|
masterDev_ = NULL;
|
|
for(int i = 0; i<ECMC_CAN_MAX_DEVICES;i++) {
|
|
devices_[i] = NULL;
|
|
}
|
|
|
|
exeSampleTimeMs_ = exeSampleTimeMs;
|
|
|
|
memset(&ifr_,0,sizeof(struct ifreq));
|
|
memset(&rxmsg_,0,sizeof(struct can_frame));
|
|
memset(&addr_,0,sizeof(struct sockaddr_can));
|
|
|
|
parseConfigStr(configStr); // Assigns all configs
|
|
// Check valid nfft
|
|
if(!cfgCanIFStr_ ) {
|
|
throw std::out_of_range("CAN inteface must be defined (can0, vcan0...).");
|
|
}
|
|
|
|
// Create worker thread for reading socket
|
|
std::string threadname = "ecmc." ECMC_PLUGIN_ASYN_PREFIX".read";
|
|
if(epicsThreadCreate(threadname.c_str(), 0, 32768, f_worker_read, this) == NULL) {
|
|
throw std::runtime_error("Error: Failed create worker thread for read().");
|
|
}
|
|
|
|
// Create worker thread for connecting socket
|
|
threadname = "ecmc." ECMC_PLUGIN_ASYN_PREFIX".connect";
|
|
if(epicsThreadCreate(threadname.c_str(), 0, 32768, f_worker_connect, this) == NULL) {
|
|
throw std::runtime_error("Error: Failed create worker thread for connect().");
|
|
}
|
|
|
|
if(cfgAutoConnect_) {
|
|
connectPrivate();
|
|
}
|
|
writeBuffer_ = new ecmcSocketCANWriteBuffer(socketId_, cfgDbgMode_);
|
|
initAsyn();
|
|
}
|
|
|
|
ecmcSocketCAN::~ecmcSocketCAN() {
|
|
// kill worker
|
|
destructs_ = 1; // maybe need todo in other way..
|
|
doWriteEvent_.signal();
|
|
doConnectEvent_.signal();
|
|
|
|
for(int i = 0; i<ECMC_CAN_MAX_DEVICES;i++) {
|
|
delete devices_[i];
|
|
}
|
|
|
|
}
|
|
|
|
void ecmcSocketCAN::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_CONNECT_OPTION_CMD (1/0)
|
|
if (!strncmp(pThisOption, ECMC_PLUGIN_CONNECT_OPTION_CMD, strlen(ECMC_PLUGIN_CONNECT_OPTION_CMD))) {
|
|
pThisOption += strlen(ECMC_PLUGIN_DBG_PRINT_OPTION_CMD);
|
|
cfgAutoConnect_ = atoi(pThisOption);
|
|
}
|
|
|
|
// ECMC_PLUGIN_IF_OPTION_CMD (Source string)
|
|
else if (!strncmp(pThisOption, ECMC_PLUGIN_IF_OPTION_CMD, strlen(ECMC_PLUGIN_IF_OPTION_CMD))) {
|
|
pThisOption += strlen(ECMC_PLUGIN_IF_OPTION_CMD);
|
|
cfgCanIFStr_=strdup(pThisOption);
|
|
}
|
|
|
|
pThisOption = pNextOption;
|
|
}
|
|
free(pOptions);
|
|
}
|
|
if(!cfgCanIFStr_) {
|
|
throw std::invalid_argument( "CAN interface not defined.");
|
|
}
|
|
}
|
|
|
|
// For connect commands over asyn or plc. let worker connect
|
|
void ecmcSocketCAN::connectExternal() {
|
|
if(!connected_) {
|
|
doConnectEvent_.signal(); // let worker start
|
|
}
|
|
}
|
|
|
|
void ecmcSocketCAN::connectPrivate() {
|
|
|
|
if((socketId_ = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) {
|
|
throw std::runtime_error( "Error while opening socket.");
|
|
return;
|
|
}
|
|
|
|
strcpy(ifr_.ifr_name, cfgCanIFStr_);
|
|
ioctl(socketId_, SIOCGIFINDEX, &ifr_);
|
|
|
|
addr_.can_family = AF_CAN;
|
|
addr_.can_ifindex = ifr_.ifr_ifindex;
|
|
|
|
printf("%s at index %d\n", cfgCanIFStr_, ifr_.ifr_ifindex);
|
|
|
|
if(bind(socketId_, (struct sockaddr *)&addr_, sizeof(addr_)) == -1) {
|
|
throw std::runtime_error( "Error in socket bind.");
|
|
return;
|
|
}
|
|
connected_ = 1;
|
|
}
|
|
|
|
int ecmcSocketCAN::getConnected() {
|
|
return connected_;
|
|
}
|
|
|
|
// Read socket worker
|
|
void ecmcSocketCAN::doReadWorker() {
|
|
|
|
while(true) {
|
|
|
|
if(destructs_) {
|
|
break;
|
|
}
|
|
if(!connected_) {
|
|
timespec tempPauseTime;
|
|
tempPauseTime.tv_sec = 1;
|
|
tempPauseTime.tv_nsec = 0;
|
|
nanosleep(&tempPauseTime,NULL);
|
|
continue;
|
|
}
|
|
// Wait for new CAN frame
|
|
int bytes = read(socketId_, &rxmsg_, sizeof(rxmsg_));
|
|
|
|
// error in read()
|
|
if(bytes == -1) {
|
|
errorCode_ = errno;
|
|
printf("ecmcSocketCAN: read() fail with error: %s, (0x%x).\n", strerror(errno),errno);
|
|
refreshNeeded_ = 1;
|
|
continue;
|
|
}
|
|
|
|
// incomplete read()
|
|
if(bytes != sizeof(rxmsg_)) {
|
|
printf("ecmcSocketCAN: read() fail with error: Incomplete read, not a full can frame (0x%x).\n",ECMC_CAN_ERROR_READ_INCOMPLETE);
|
|
errorCode_ = ECMC_CAN_ERROR_READ_INCOMPLETE;
|
|
refreshNeeded_ = 1;
|
|
continue;
|
|
}
|
|
|
|
// forward all data to devices (including master)
|
|
for(int i = 0; i < deviceCounter_; i++){
|
|
devices_[i]->newRxFrame(&rxmsg_);
|
|
}
|
|
|
|
if(cfgDbgMode_) {
|
|
// Simulate candump printout
|
|
printf("r 0x%03X", rxmsg_.can_id);
|
|
printf(" [%d]", rxmsg_.can_dlc);
|
|
for(int i=0; i<rxmsg_.can_dlc; i++ ) {
|
|
printf(" 0x%02X", rxmsg_.data[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Connect socket worker
|
|
void ecmcSocketCAN::doConnectWorker() {
|
|
|
|
while(true) {
|
|
|
|
if(destructs_) {
|
|
return;
|
|
}
|
|
doConnectEvent_.wait();
|
|
if(destructs_) {
|
|
return;
|
|
}
|
|
connectPrivate();
|
|
}
|
|
}
|
|
|
|
int ecmcSocketCAN::getlastWritesError() {
|
|
if(!writeBuffer_) {
|
|
return ECMC_CAN_ERROR_WRITE_BUFFER_NULL;
|
|
}
|
|
return writeBuffer_->getlastWritesErrorAndReset();
|
|
}
|
|
|
|
int ecmcSocketCAN::addWriteCAN(uint32_t canId,
|
|
uint8_t len,
|
|
uint8_t data0,
|
|
uint8_t data1,
|
|
uint8_t data2,
|
|
uint8_t data3,
|
|
uint8_t data4,
|
|
uint8_t data5,
|
|
uint8_t data6,
|
|
uint8_t data7) {
|
|
|
|
if(!writeBuffer_) {
|
|
return ECMC_CAN_ERROR_WRITE_BUFFER_NULL;
|
|
}
|
|
|
|
writeBuffer_->addWriteCAN(canId,
|
|
len,
|
|
data0,
|
|
data1,
|
|
data2,
|
|
data3,
|
|
data4,
|
|
data5,
|
|
data6,
|
|
data7);
|
|
return 0;
|
|
}
|
|
|
|
void ecmcSocketCAN::execute() {
|
|
|
|
for(int i = 0; i < deviceCounter_; i++){
|
|
devices_[i]->execute();
|
|
}
|
|
|
|
int writeError=getlastWritesError();
|
|
if (writeError) {
|
|
errorCode_ = writeError;
|
|
refreshNeeded_ = 1;
|
|
}
|
|
refreshAsynParams();
|
|
return;
|
|
}
|
|
|
|
// Avoid issues with std:to_string()
|
|
std::string ecmcSocketCAN::to_string(int value) {
|
|
std::ostringstream os;
|
|
os << value;
|
|
return os.str();
|
|
}
|
|
|
|
void ecmcSocketCAN::addMaster(uint32_t nodeId,
|
|
const char* name,
|
|
int lssSampleTimeMs,
|
|
int syncSampleTimeMs,
|
|
int heartSampleTimeMs) {
|
|
|
|
if(masterDev_) {
|
|
throw std::runtime_error("Master already added.");
|
|
}
|
|
if(deviceCounter_ >= ECMC_CAN_MAX_DEVICES) {
|
|
throw std::out_of_range("Device array full.");
|
|
}
|
|
if(nodeId >= 128) {
|
|
throw std::out_of_range("Node id out of range.");
|
|
}
|
|
|
|
if(lssSampleTimeMs <= 0) {
|
|
throw std::out_of_range("LSS sample time ms out of range.");
|
|
}
|
|
|
|
if(syncSampleTimeMs <= 0) {
|
|
throw std::out_of_range("Sync sample time ms out of range.");
|
|
}
|
|
|
|
if(heartSampleTimeMs <= 0) {
|
|
throw std::out_of_range("Heart sample time ms out of range.");
|
|
}
|
|
|
|
masterDev_ = new ecmcCANOpenMaster(writeBuffer_,
|
|
nodeId,
|
|
exeSampleTimeMs_,
|
|
lssSampleTimeMs,
|
|
syncSampleTimeMs,
|
|
heartSampleTimeMs,
|
|
name,
|
|
cfgDbgMode_);
|
|
// add as a normal device also for execute and rxframe
|
|
devices_[deviceCounter_] = masterDev_;
|
|
deviceCounter_++;
|
|
}
|
|
|
|
void ecmcSocketCAN::addDevice(uint32_t nodeId,
|
|
const char* name,
|
|
int heartTimeoutMs){
|
|
if(deviceCounter_ >= ECMC_CAN_MAX_DEVICES) {
|
|
throw std::out_of_range("Device array full.");
|
|
}
|
|
if(nodeId >= 128) {
|
|
throw std::out_of_range("Node id out of range.");
|
|
}
|
|
|
|
devices_[deviceCounter_] = new ecmcCANOpenDevice(writeBuffer_,nodeId,exeSampleTimeMs_,name,heartTimeoutMs,cfgDbgMode_);
|
|
deviceCounter_++;
|
|
}
|
|
|
|
int ecmcSocketCAN::findDeviceWithNodeId(uint32_t nodeId) {
|
|
for(int i=0; i < deviceCounter_;i++) {
|
|
if(devices_[i]) {
|
|
if(devices_[i]->getNodeId() == nodeId) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void ecmcSocketCAN::addPDO(uint32_t nodeId,
|
|
uint32_t cobId,
|
|
ecmc_can_direction rw,
|
|
uint32_t ODSize,
|
|
int readTimeoutMs,
|
|
int writeCycleMs, //if <0 then write on demand.
|
|
const char* name) {
|
|
int devId = findDeviceWithNodeId(nodeId);
|
|
if(devId < 0) {
|
|
throw std::out_of_range("Node id not found in any configured device.");
|
|
}
|
|
|
|
int errorCode = devices_[devId]->addPDO(cobId,
|
|
rw,
|
|
ODSize,
|
|
readTimeoutMs,
|
|
writeCycleMs,
|
|
name);
|
|
if(errorCode > 0) {
|
|
throw std::runtime_error("AddPDO() failed.");
|
|
}
|
|
}
|
|
|
|
void ecmcSocketCAN::addSDO(uint32_t nodeId,
|
|
uint32_t cobIdTx, // 0x580 + CobId
|
|
uint32_t cobIdRx, // 0x600 + Cobid
|
|
ecmc_can_direction rw,
|
|
uint16_t ODIndex, // Object dictionary index
|
|
uint8_t ODSubIndex, // Object dictionary subindex
|
|
uint32_t ODSize,
|
|
int readSampleTimeMs,
|
|
const char* name) {
|
|
|
|
int devId = findDeviceWithNodeId(nodeId);
|
|
if(devId < 0) {
|
|
throw std::out_of_range("Node id not found in any configured device.");
|
|
}
|
|
|
|
int errorCode = devices_[devId]->addSDO(cobIdTx,
|
|
cobIdRx,
|
|
rw,
|
|
ODIndex,
|
|
ODSubIndex,
|
|
ODSize,
|
|
readSampleTimeMs,
|
|
name);
|
|
if(errorCode > 0) {
|
|
throw std::runtime_error("AddSDO() failed.");
|
|
}
|
|
}
|
|
|
|
void ecmcSocketCAN::initAsyn() {
|
|
|
|
ecmcAsynPortDriver *ecmcAsynPort = (ecmcAsynPortDriver *)getEcmcAsynPortDriver();
|
|
if(!ecmcAsynPort) {
|
|
printf("ERROR: ecmcAsynPort NULL.");
|
|
throw std::runtime_error( "ERROR: ecmcAsynPort NULL." );
|
|
}
|
|
|
|
// Add resultdata "plugin.can.read.error"
|
|
std::string paramName = ECMC_PLUGIN_ASYN_PREFIX + std::string(".com.error");
|
|
|
|
errorParam_ = ecmcAsynPort->addNewAvailParam(
|
|
paramName.c_str(), // name
|
|
asynParamInt32, // asyn type
|
|
(uint8_t*)&errorCode_, // pointer to data
|
|
sizeof(errorCode_), // size of data
|
|
ECMC_EC_U32, // ecmc data type
|
|
0); // die if fail
|
|
|
|
if(!errorParam_) {
|
|
printf("ERROR: Failed create asyn param for data.");
|
|
throw std::runtime_error( "ERROR: Failed create asyn param for: " + paramName);
|
|
}
|
|
errorParam_->setAllowWriteToEcmc(false); // need to callback here
|
|
errorParam_->refreshParam(1); // read once into asyn param lib
|
|
ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR);
|
|
|
|
// Add resultdata "plugin.can.read.connected"
|
|
paramName = ECMC_PLUGIN_ASYN_PREFIX + std::string(".com.connected");
|
|
|
|
connectedParam_ = ecmcAsynPort->addNewAvailParam(
|
|
paramName.c_str(), // name
|
|
asynParamInt32, // asyn type
|
|
(uint8_t*)&connected_, // pointer to data
|
|
sizeof(connected_), // size of data
|
|
ECMC_EC_U32, // ecmc data type
|
|
0); // die if fail
|
|
|
|
if(!connectedParam_) {
|
|
printf("ERROR: Failed create asyn param for connected.");
|
|
throw std::runtime_error( "ERROR: Failed create asyn param for: " + paramName);
|
|
}
|
|
connectedParam_->setAllowWriteToEcmc(false); // need to callback here
|
|
connectedParam_->refreshParam(1); // read once into asyn param lib
|
|
ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR);
|
|
}
|
|
|
|
// only refresh from "execute" thread
|
|
void ecmcSocketCAN::refreshAsynParams() {
|
|
if(refreshNeeded_) {
|
|
connectedParam_->refreshParamRT(1); // read once into asyn param lib
|
|
errorParam_->refreshParamRT(1); // read once into asyn param lib
|
|
}
|
|
refreshNeeded_ = 0;
|
|
} |