#include "IConnectionSerial.h" #if defined TARGET_OS_Windows #define WIN32_LEAN_AND_MEAN #include #include #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS #include #include #include #include #include #include #include #include #else #error "Unsupported platform" #endif #include #include #if defined TARGET_OS_Linux // Good resources at http://www.tldp.org/HOWTO/html_single/Serial-HOWTO/ #endif namespace Socket { class CConnectionSerial final : public IConnectionSerial { public: #if defined TARGET_OS_Windows CConnectionSerial() : m_file(nullptr) { } #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS CConnectionSerial() : m_file(0) { } #endif bool open() override { return false; } // Should never be used bool close() override { #if defined TARGET_OS_Windows if (m_file != nullptr) { if (!CloseHandle(m_file)) { m_LastError = "Failed to close the serial port:" + this->getLastErrorFormated(); m_file = nullptr; return false; } m_file = nullptr; } #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS if(m_file != -1) { ::close(m_file); m_file=-1; } #endif return true; } #if defined TARGET_OS_Windows bool isReadyToSend(const size_t /*timeOut*/) const override { if (!this->isConnected()) { return false; } return true; } #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS bool isReadyToSend(const size_t timeOut) const override { if (!this->isConnected()) { return false; } fd_set fileDescSet; struct timeval time; time.tv_sec = timeOut/1000; time.tv_usec = (timeOut%1000)*1000; FD_ZERO(&fileDescSet); FD_SET(m_file, &fileDescSet); if(!::select(m_file + 1, nullptr, &fileDescSet, nullptr, &time)) { return false; } if(FD_ISSET(m_file, &fileDescSet)) { return true; } return false; } #endif #if defined TARGET_OS_Windows bool isReadyToReceive(const size_t /*timeOut*/) const override { if (!this->isConnected()) { return false; } struct _COMSTAT status; DWORD state; if (ClearCommError(m_file, &state, &status) != 0) { return status.cbInQue != 0; } return false; } #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS bool isReadyToReceive(const size_t timeOut) const override { if (!this->isConnected()) { return false; } fd_set fileDescSet; struct timeval time; time.tv_sec=timeOut/1000; time.tv_usec=(timeOut%1000)*1000; FD_ZERO(&fileDescSet); FD_SET(m_file, &fileDescSet); if(!::select(m_file+1, &fileDescSet, nullptr, nullptr, &time)) { return false; } if(FD_ISSET(m_file, &fileDescSet)) { return true; } return false; } #endif size_t getPendingByteCount() override { if (!this->isConnected()) { m_LastError = "Serial port not connected."; return 0; } #if defined TARGET_OS_Windows struct _COMSTAT status; DWORD state; if (ClearCommError(m_file, &state, &status) == 0) { m_LastError = "Failed to clear the serial port communication error: " + this->getLastErrorFormated(); return 0; } return status.cbInQue; #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS int byteCount=0; if(-1 == ::ioctl(m_file, FIONREAD, &byteCount)) { m_LastError = "Failed to querry pending bytes in ioctl"; return 0; } return byteCount; #endif } bool flush() override { if (!this->isConnected()) { m_LastError = "Serial port not connected."; return false; } #if defined TARGET_OS_Windows if (!FlushFileBuffers(m_file)) { m_LastError = "Failed to flush serial port buffer: " + this->getLastErrorFormated(); return false; } return true; #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS if(-1 == ::tcdrain(m_file)) { m_LastError = "Could not flush connection in tcdrain"; return false; } return true; #else return false; #endif } size_t sendBuffer(const void* buffer, const size_t size) override { if (!this->isConnected()) { m_LastError = "Serial port not connected."; return 0; } #if defined TARGET_OS_Windows DWORD written = 0; if (!WriteFile(m_file, buffer, DWORD(size), &written, nullptr)) { m_LastError = "Failed to write on serial port: " + this->getLastErrorFormated(); this->close(); return 0; } if (written == 0) { m_LastError = "Serial port timeout when trying to write."; this->close(); return 0; } return written; #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS int res = ::write(m_file, buffer, size); if(res < 0) { m_LastError = "Could not write on connection"; this->close(); return 0; } return res; #else return 0; #endif } size_t receiveBuffer(void* buffer, const size_t size) override { if (!this->isConnected()) { m_LastError = "Serial port not connected."; return 0; } #if defined TARGET_OS_Windows DWORD read = 0; if (!ReadFile(m_file, buffer, DWORD(size), &read, nullptr)) { m_LastError = "Failed to read on serial port: " + this->getLastErrorFormated(); this->close(); return 0; } if (read == 0) { m_LastError = "Serial port timeout when trying to read."; this->close(); return 0; } return read; #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS int res = ::read(m_file, buffer, size); if (res < 0) { m_LastError = "Could not read from connection"; this->close(); return 0; } return res; #else return 0; #endif } bool sendBufferBlocking(const void* buffer, const size_t size) override { const char* p = reinterpret_cast(buffer); size_t bytesLeft = size; while (bytesLeft != 0 && this->isConnected()) { bytesLeft -= this->sendBuffer(p + size - bytesLeft, bytesLeft); if (this->isErrorRaised()) { return false; } } return bytesLeft == 0; } bool receiveBufferBlocking(void* buffer, const size_t size) override { char* p = reinterpret_cast(buffer); size_t bytesLeft = size; while (bytesLeft != 0 && this->isConnected()) { bytesLeft -= this->receiveBuffer(p + size - bytesLeft, bytesLeft); if (this->isErrorRaised()) { return false; } } return bytesLeft == 0; } bool isConnected() const override { #if defined TARGET_OS_Windows return ((m_file != nullptr) && (m_file != INVALID_HANDLE_VALUE)); #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS return m_file != 0; #endif } void release() override { delete this; } bool connect(const char* sURL, const size_t baudrate) override { m_LastError.clear(); if (this->isConnected()) { m_LastError = "Serial port already connected"; return false; } #if defined TARGET_OS_Windows m_file = ::CreateFile(sURL, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (m_file == INVALID_HANDLE_VALUE || m_file == nullptr) { m_LastError = "Failed to open serial port: " + this->getLastErrorFormated(); m_file = nullptr; return false; } DCB dcb = { 0 }; if (!GetCommState(m_file, &dcb)) { m_LastError = "Failed to get communication state: " + this->getLastErrorFormated(); this->close(); return false; } dcb.DCBlength = sizeof(dcb); dcb.BaudRate = DWORD(baudrate); dcb.ByteSize = 8; dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT; ClearCommError(m_file, nullptr, nullptr); if (!SetCommState(m_file, &dcb)) { m_LastError = "Could not set communication state: " + this->getLastErrorFormated(); this->close(); return false; } #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS /* $$$ BAUD RATE PARAMETER IS NOT CONSIDERED ON LINUX, MIGHT BE NECESSARY TO CHANGE THIS $$$ */ assert(baudrate == 230400); if ((m_file = ::open(sURL, O_RDWR)) == -1) { m_LastError = "Failed to open serial port."; return false; } struct termios terminalAtt; if(::tcgetattr(m_file, &terminalAtt)!=0) { m_LastError = "Could not get terminal attributes in tcgetattr"; this->close(); return false; } terminalAtt.c_cflag = B230400 | CS8 | CLOCAL | CREAD; terminalAtt.c_iflag = 0; terminalAtt.c_oflag = OPOST | ONLCR; terminalAtt.c_lflag = 0; if (::tcsetattr(m_file, TCSAFLUSH, &terminalAtt) != 0) { m_LastError = "Could not set terminal attributes in tcgetattr"; this->close(); return false; } #endif return true; } bool setTimeouts(const size_t decisecondsTimeout) override { if (!this->isConnected()) { return false; } #if defined TARGET_OS_Windows COMMTIMEOUTS timeouts; if (!GetCommTimeouts(m_file, &timeouts)) { m_LastError = "Could not get communication timeouts: " + this->getLastErrorFormated(); this->close(); return false; } timeouts.ReadTotalTimeoutConstant = decisecondsTimeout * 100; // Deciseconds to milliseconds timeouts.WriteTotalTimeoutConstant = decisecondsTimeout * 100; // Deciseconds to milliseconds if (!SetCommTimeouts(m_file, &timeouts)) { m_LastError = "Could not set communication timeouts: " + this->getLastErrorFormated(); this->close(); return false; } #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS struct termios terminalAtt; if(::tcgetattr(m_file, &terminalAtt)!=0) { m_LastError = "Could not get terminal attributes in tcgetattr"; this->close(); return false; } terminalAtt.c_cc[VTIME] = decisecondsTimeout; if (::tcsetattr(m_file, TCSAFLUSH, &terminalAtt) != 0) { m_LastError = "Could not set terminal attributes in tcgetattr"; this->close(); return false; } #endif return true; } const char* getLastError() override { return m_LastError.c_str(); } static std::string getLastErrorFormated() { #if defined TARGET_OS_Windows LPTSTR errorText; const DWORD error = GetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | // use system message tables to retrieve error text FORMAT_MESSAGE_ALLOCATE_BUFFER | // allocate buffer on local heap for error text FORMAT_MESSAGE_IGNORE_INSERTS, // Important! will fail otherwise, since we're not (and CANNOT) pass insertion parameters nullptr, // unused with FORMAT_MESSAGE_FROM_SYSTEM error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), LPTSTR(&errorText), // output 0, // minimum size for output buffer nullptr); // arguments - see note return errorText; #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS return ""; // TODO #endif } bool isErrorRaised() override { return !m_LastError.empty(); } void clearError() override { m_LastError.clear(); } std::string m_LastError; #if defined TARGET_OS_Windows void* m_file; #elif defined TARGET_OS_Linux || defined TARGET_OS_MacOS int m_file; #endif }; IConnectionSerial* createConnectionSerial() { return new CConnectionSerial(); } } // namespace Socket