/*************************************************************************\ * 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 #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; inewRxFrame(&rxmsg_); } if(cfgDbgMode_) { // Simulate candump printout printf("r 0x%03X", rxmsg_.can_id); printf(" [%d]", rxmsg_.can_dlc); for(int i=0; igetlastWritesErrorAndReset(); } 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; }