/* Copyright (c) 20011 Hovanes Egiyan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "BaseIUDevice.hh" using namespace std; //! Debug flag unsigned BaseIUDevice::bdDebFlag = 0; //! Parameters that are needed for configuration of AnaGateCAN device int BaseIUDevice::bdFormatFlag = 0x01; //! bit 0 => extended ID, bit 1 => remote telegram long BaseIUDevice::bdConnectTimeOut = 10000; //! Timeout for connection in msec int BaseIUDevice::bdRate = 50000; //! 50kb/s rate unsigned char BaseIUDevice::bdOperMode = 0; //! Run in default mode bool BaseIUDevice::bdTermination = false; //! No termination bool BaseIUDevice::bdHighSped = false; //! High speed off bool BaseIUDevice::bdDataConfirm = true; //! All data requests confirmed bool BaseIUDevice::bdMonitor = true; //! Telegram are NOT discarded bool BaseIUDevice::bdTimeStamp = false; //! Timestamp flag //! Error parameters for this class const int BaseIUDevice::bdSuccess = 0; //! Return value success const int BaseIUDevice::bdError = -1; //! Return generic error //! Communication parameters for this class //const long BaseIUDevice::bdWriteReadDelay = 5000000; //! W/R time delay in usec //const long BaseIUDevice::bdWriteReadDelay = 360000; //! W/R time delay in usec const long BaseIUDevice::bdWriteReadDelay = 700000; //! W/R time delay in usec //const long BaseIUDevice::bdWriteReadDelay = 150000; //! W/R time delay in usec, new firmware is faster //! Mutexes and mutex attributes pthread_mutex_t BaseIUDevice::bdGlobMutex = PTHREAD_MUTEX_INITIALIZER; //! Global mutex for class pthread_mutexattr_t BaseIUDevice::bdGlobMtxAttr ; //! Global mutex attributes static volatile int bdDummyInt = BaseIUDevice::InitGlobalMutex(); //! Dummy call to initialize global mutex using namespace std; //! Initialize the static map for the connections map BaseIUDevice::bdDeviceMap; map BaseIUDevice::bdPtrMap; //! Init FIFO for the readback for this communication device BaseIUBuffer BaseIUDevice::bdFIFO; //! Constructor for the port object. It makes sure that we do not //! have a port for that device and calls Init method to initialize the //! port for use with Brooks2054 module BaseIUDevice::BaseIUDevice( DeviceID& devID ) : bdID( devID ), bdMutex(PTHREAD_MUTEX_INITIALIZER) { if( bdDebFlag > 1 ) { cout << "BaseIUDevice::BaseIUDevice() : Starting constructor" << endl; } //! Initialize mutex for this object InitMutex(); //! Check in the map of connections if this connection is open //! already and throw an exception BaseIUDevice* existDevPtr = DeviceExists(); if( existDevPtr != 0 ) { stringstream errMsg; errMsg << "BaseIUDevice(): Device " << bdID.GetIP() << " : " << hex << showbase << bdID.GetBus() << " was already open as port " << existDevPtr->GetPortName() << " on IP address " << existDevPtr->GetIP() << " and bus " << existDevPtr->GetBusStr() << endl; throw FailedOpenDevice( errMsg.str() ); } //! Open and Initialize the CAN bus connection on AnaGate InitConnection(); //! Make an entry in the device-map hash tables MtxLock classLock( bdGlobMutex ); bdDeviceMap[bdHandle] = bdID; // Record the ID of the connection with a given handle bdPtrMap[bdID] = this; // Record the pointer of connection object for that ID if( bdDebFlag > 1 ) cout << "Finished BaseIUDevice constructor function" << endl; return; } //! Method to check if a device connection with that IP address and bus exists. //! It it does then return the port name from the device map BaseIUDevice* BaseIUDevice::DeviceExists() { MtxLock classLock( bdGlobMutex ); //! Check if this device connection has already been created //! Remember that equality of DeviceID is determined by the //! IP number and the bus on the Anagate. The port names //! are not included in the comparison map::iterator ptrItr = bdPtrMap.find( bdID ); if ( ptrItr != bdPtrMap.end() ) { // return ptrItr->second->GetPortName(); return ptrItr->second; } return 0; } //! Open device and set global settings void BaseIUDevice::InitConnection() { MtxLock instanceLock( bdMutex ); //! Open CAN Device int errCode = CANOpenDevice( &bdHandle, bdDataConfirm, bdMonitor, bdID.GetBus(), bdID.GetIP().c_str(), bdConnectTimeOut ); if( errCode != 0 ) { //! AnaGateCAN-based exception throw BaseIUDevice::FailedOpenDevice( errCode ); } //! Set up CAN communication errCode = CANSetGlobals( bdHandle, bdRate, bdOperMode, bdTermination, bdHighSped, bdTimeStamp ); if( errCode != 0 ) { //! AnaGateCAN-based exception throw BaseIUDevice::FailedOpenDevice( errCode ); } //! Set the callback function for readback errCode = CANSetCallback( bdHandle, CallBack ); if( errCode != 0 ) { //! AnaGateCAN-based exception throw BaseIUDevice::FailedOpenDevice( errCode ); } if( bdDebFlag > 1 ) cout << "BaseIUDevice::Init(): CAN bus on port " << hex << showbase << bdID.GetPort() << " successfully opened and configured... Handle: " << bdHandle << endl; return; } //! Destructor BaseIUDevice::~BaseIUDevice() { if( bdDebFlag > 1 ) { cout << "BaseIUDevice::~BaseIUDevice() : " << " Starting ~BaseIUDevice() destructor" << endl; } //! PreoperlycClose connection to AnaGateCAN device CloseConnection(); //! Close the mutex for this instance CloseMutex(); //! Remove this instance from the maps MtxLock classLock( bdGlobMutex ); bdDeviceMap.erase( bdHandle ); bdPtrMap.erase( bdID ); if( bdDebFlag > 1 ) { cout << "BaseIUDevice::~BaseIUDevice() : Exiting destructor" << endl; } return ; } //! This method resets the serial port. It closes the port, reopens //! and initializes by calling Init method. void BaseIUDevice::Reset() { if( bdDebFlag > 1 ) { MtxLock instanceLock( bdMutex ); cout << "BaseIUDevice::Reset() : Resetting port " << hex << showbase << bdID.GetPort() << endl; } this->CloseConnection(); // Close connection this->InitConnection(); // Reopen connection return; } //! Method to close the connection to the device void BaseIUDevice::CloseConnection() { MtxLock instanceLock( bdMutex ); int errFlag = CANCloseDevice( bdHandle ); //! If returned value is not zero then print a message and //! throw an exception using the error code from AnaGate driver if( errFlag != bdSuccess ) { stringstream errMsg; errMsg << "~BaseIUDevice(): Could not close device for port " << bdID.GetPort() << " " << bdID.GetIP() << ":" << hex << showbase << bdID.GetBus() << endl; cerr << errMsg.str() << endl; throw FailedCloseDevice( errFlag ); } return; } //! Send command to a board with a specific address on a //! AnaGateCAN connection specified by dev pointer BaseIUMessage BaseIUDevice::Send( long int addr, BaseIUMessage& cmdMsg ) { MtxLock instanceLock( bdMutex ); //! Call AnaGateCAN writing function int errCode = CANWrite( bdHandle, addr, (const char*)cmdMsg.data_addr(), cmdMsg.size(), bdFormatFlag ); //! In case of error throw an exception with a text taken from //! AnaGateCAN library if( errCode != bdSuccess ) { stringstream errMsg; errMsg << "BaseIUDevice::Send(): Error writing to " << hex << showbase << bdID.GetIP() << ":" << bdID.GetBus() << " , Address " << addr << endl ; cerr << errMsg.str() << endl; //! AnaGateCAN-based exception throw BaseIUDevice::FailedWriteDevice( errCode ); } return cmdMsg ; } unsigned long BaseIUDevice::WriteDigital( unsigned long outputWord ) { MtxLock objLock( bdMutex ); int errCode = CANWriteDigital( bdHandle, outputWord ); if( errCode != bdSuccess ) { stringstream errMsg; errMsg << "BaseIUDevice::WriteDigital(): Error writing to " << hex << showbase << bdID.GetIP() << ":" << bdID.GetBus() << endl ; cerr << errMsg.str() << endl; //! AnaGateCAN-based exception throw BaseIUDevice::FailedWriteDevice( errCode ); } return outputWord; } unsigned long BaseIUDevice::ReadDigitalInputs() { AnaUInt32 inputBits, outputBits; MtxLock objLock( bdMutex ); int errCode = CANReadDigital( bdHandle, &inputBits, &outputBits ); if( errCode != bdSuccess ) { stringstream errMsg; errMsg << "BaseIUDevice::WriteDigital(): Error writing to " << hex << showbase << bdID.GetIP() << ":" << bdID.GetBus() << endl ; cerr << errMsg.str() << endl; //! AnaGateCAN-based exception throw BaseIUDevice::FailedReadDevice( errCode ); } return inputBits; } unsigned long BaseIUDevice::ReadDigitalOutputs() { AnaUInt32 inputBits, outputBits; MtxLock objLock( bdMutex ); int errCode = CANReadDigital( bdHandle, &inputBits, &outputBits ); if( errCode != bdSuccess ) { stringstream errMsg; errMsg << "BaseIUDevice::WriteDigital(): Error writing to " << hex << showbase << bdID.GetIP() << ":" << bdID.GetBus() << endl ; cerr << errMsg.str() << endl; //! AnaGateCAN-based exception throw BaseIUDevice::FailedReadDevice( errCode ); } return outputBits; } //! Callback function which fills the FIFO //! This function will be called when there is a message //! in the corresponding AnaGateCAN bus void WINAPI BaseIUDevice::CallBack( AnaUInt32 canAddr, const char* inBuffer, AnaInt32 bufLength, AnaInt32 nFlags, AnaInt32 handle ) { //! Fore some reason AnaGateCAN driver is using signed char for buffer. //! We convert it to unsigned char in this EPICS driver because using //! signed char may result in unexpected results. const unsigned char* buffer = (const unsigned char*) inBuffer; //! Determine the device ID of this message based on the handle MtxLock classLock( bdGlobMutex ); DeviceID devID = bdDeviceMap[handle]; // Get ID knowing the handle string portName = devID.GetPort(); // port name if( bdDebFlag > 1 ) cout << "BaseIUDevice::CallBack(): is called for port " << hex << showbase << portName << " AnaGateCAN handle " << handle << " with Address " << canAddr << endl; //! Read the first bufLength bytes into a message BaseIUMessage message( buffer, bufLength ); if( bdDebFlag > 1 ) { cout << "BaseIUDevice::CallBack(): Received message is: " << endl; cout << " " << message << endl; } //! Do not check the existence since here we need to create //! keys that do not exist in the FIFO (which is really a map of deques) // cout << bdFIFO << endl; // cout << "Pushing into FIFO" << endl; //! Push the bytes into the FIFO bdFIFO.PushBack( portName, canAddr, message ); // cout << "Pushed into FIFO" << endl; // cout << bdFIFO << endl; return; } //! Requests IDs from he boards on the bus by broadcasting on the bus. //! Returns a vector of addresses which have response in the FIFO vector BaseIUDevice::GetAddressVector() { map addrFound; // Flag for addresses already found vector addrVec; // This vector will be returned map >::iterator it; int addr = 0; //! Will broadcast to all all boards BaseIUCommand cmdMsg; //! Set command to GetID cmdMsg.setGetID(); if( bdDebFlag > 1 ) { cout << "BaseIUDevice::GetAddressVector() : Sending message " << cmdMsg << " to port " << GetPortName() << endl; } //! Send the command from the port device to the boards this->Send( addr, cmdMsg ); if( bdDebFlag > 1 ) { cout << "BaseIUDevice::GetAddressVector() : Sleeping for " << bdWriteReadDelay << " microseconds" << endl; } //! Wait a little before reading what ended up in the FIFO deque this->Sleep(); if( bdDebFlag > 1 ) { cout << "BaseIUDevice::GetAddressVector() : Will read the response " << endl; } //! Get the connection number MtxLock instanceLock( bdMutex ); string portName = bdID.GetPort(); instanceLock.Unlock(); MtxLock classLock( bdGlobMutex ); BaseIUBuffer tempFIFO = bdFIFO; if( tempFIFO.KeyExists( portName ) ) { //! For this port loop over all pairs in the map and pick out //! the addresses of the boards. Then fill in the vector of addresses for( it = tempFIFO[portName].begin(); it != tempFIFO[portName].end(); it++ ) { int addr = it->first; if( addrFound.count( addr ) == 0 ) { //! Such address has been seen there addrVec.push_back( addr ); addrFound[addr] = true; // Inserts a pair (addr, true ) } //! Remove all message for this connection for address "addr" bdFIFO.Clear( portName, addr ); } } if( bdDebFlag > 1 ) { cout << "BaseIUDevice::GetAddressVector() : Found " << addrVec.size() << " responses." << endl; } return addrVec; } //! Read the FIFO for address "addr" and clear the FIFO for //! that address. Then return the saved copy of the queue. //! addr = 0 does not have special meaning of "all addresses" //! for this particular method. deque BaseIUDevice::GetDeque( int addr, bool clearFlag ) { //! Set a object lock and get the connection number then unlock MtxLock instanceLock( bdMutex ); string portName = bdID.GetPort(); if( bdDebFlag > 1 ) { cout << "BaseIUDevice::GetDeque() : Received request for address " << addr << " on connection " << bdID.GetPort() << endl; } instanceLock.Unlock(); // Lock all instances of this class. The FIFO is the same for all devices, it is feature of // the Anagate driver. MtxLock classLock( bdGlobMutex ); if ( bdFIFO.KeyExists( portName, addr ) ) { // If the port and address exist then return the deque for that port and address. if (bdDebFlag > 1) { cout << "BaseIUDevice::GetDeque() : Found the keys " << endl; } //! Keys (port, addr) exist in the current buffer, //! so we will extract the deque and clear it from FIFO if clearing is requested. //! Create a copy of the FIFO for that address deque theQueue = bdFIFO[portName][addr]; //! Remove all entries from the FIFO from that address if clearing is requested if ( clearFlag ) bdFIFO.Clear( portName, addr ); //! Return the saved copy return theQueue; } else if ( bdFIFO.KeyExists( portName ) && addr == 0 ) { // Address 0 refers to the whole bus. If addr=0 is specified // all messages from the given port name are returned (include all CAN addresses). if (bdDebFlag > 1) { cout << "BaseIUDevice::GetDeque() : returning deque for all addresses " << endl; } // cout // << "BaseIUDevice::GetDeque() : filling the deque for all addresses on " // << bdID.GetPort() << endl; // vector addrList; deque newDeque; // New deque deque::iterator newDeqIter = newDeque.begin(); // iterator for new deque map >::iterator itAddr; // iterator for the map with the addresses as the keys // Loop over all addresses and fill the newDeque with the content of all // the deques for all addresses. for ( itAddr = bdFIFO[portName].begin(); itAddr != bdFIFO[portName].end(); itAddr++ ) { int currentAddr = itAddr->first; deque& currentDeque = itAddr->second; if ( currentAddr > 0 && currentDeque.size() > 0 ) { newDeque.insert( newDeque.begin(), currentDeque.begin(), currentDeque.end() ); // addrList.push_back( currentAddr ); } } // Remove all entries from the FIFO for all address if clearing is requested. bdFIFO.Clear( portName ); // cout << "BaseIUDevice::GetDeque() : got the deque, returning... " << endl; return newDeque; } else { if (bdDebFlag > 1) { cout << "BaseIUDevice::GetDeque() : Returning empty deque " << endl; } //! Return empty deque if the (conNum, addr) do not exist in //! the current FIFO return deque(); } } // Clear all address for this device inline void BaseIUDevice::ClearFIFO() { if (bdDebFlag > 1) { cout << "BaseIUDevice::ClearFIFO() : Clearing FIFO for " << bdID.GetPort() << " aka " << GetPortName() << endl; } MtxLock classLock( bdGlobMutex ); bdFIFO.Clear( bdID.GetPort() ); // cout << " FIFO has been cleared" << endl; return; } // Initialize mutex for individual objects void BaseIUDevice::InitMutex() { pthread_mutexattr_init( &bdMtxAttr ); pthread_mutexattr_setpshared( &bdMtxAttr, PTHREAD_PROCESS_PRIVATE ); pthread_mutexattr_settype(&bdMtxAttr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init( &bdMutex, &bdMtxAttr ); return; } // Destroy mutex for individual objects void BaseIUDevice::CloseMutex() { pthread_mutex_destroy( &bdMutex ); pthread_mutexattr_destroy( &bdMtxAttr ); return; } //! Initialize global mutex int BaseIUDevice::InitGlobalMutex() { pthread_mutexattr_init( &bdGlobMtxAttr ); pthread_mutexattr_setpshared( &bdGlobMtxAttr, PTHREAD_PROCESS_PRIVATE ); pthread_mutexattr_settype(&bdGlobMtxAttr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init( &bdGlobMutex, &bdGlobMtxAttr ); return 0; } //! Convert "A", "B", "C", "D", "E", "a", "b", "c", "d", "e" //! into 0 , 1, 2, 3, 4. For invalid string return -1 int BaseIUDevice::DeviceID::String2Int( const string bus ) { if( bus.size() != 1 ) { cerr << "BaseIUDevice::DeviceID::String2Int: Bad bus label <" << bus << ">" << endl; return -1; //! invalid string } char firstChar = bus[0]; //! Character 'A' ASCII code is 0x41 and //! character 'a' ASCII code is 0x61 if( firstChar >= 'A' && firstChar <= 'E' ) { // Uppercase return ( firstChar - 'A' ); } else if( firstChar >= 'a' && firstChar <= 'd' ) { // Lowercase return ( firstChar - 'a' ); } else { // invalid string cerr << "BaseIUDevice::DeviceID::String2Int: Bad bus label <" << bus << ">" << endl; return -1; } } //! Convert 0 , 1, 2, 3. //! into "A", "B", "C", "D". For invalid string return -1 string BaseIUDevice::DeviceID::Int2String( const int bus) { if( bus < 0 || bus > 3 ) { // cerr << "BaseIUDevice::DeviceID::Int2String: Bad bus number <" << bus << ">" << endl; return string("E"); //! All buses } stringstream ssReturn; ssReturn << static_cast('A' + bus ); return ssReturn.str(); }