From a5b27a15b016db421593fde4bdbcc2a8e2bc5da4 Mon Sep 17 00:00:00 2001 From: Kyle Schwarz Date: Thu, 8 Oct 2020 17:38:10 -0400 Subject: [PATCH] Allow disconnections to be signaled by drivers This allows for disconnections to be detected quickly where possible. It also makes sure other driver errors aren't thrown in the event of a disconnection. --- communication/communication.cpp | 6 ++- device/device.cpp | 7 +++- include/icsneo/communication/communication.h | 1 + include/icsneo/communication/driver.h | 2 + include/icsneo/device/device.h | 1 + include/icsneo/platform/posix/ftdi.h | 2 +- include/icsneo/platform/posix/stm32.h | 2 + platform/posix/ftdi.cpp | 41 ++++++++++++++------ platform/posix/stm32.cpp | 38 ++++++++++++++---- platform/windows/vcp.cpp | 22 ++++++++--- 10 files changed, 95 insertions(+), 27 deletions(-) diff --git a/communication/communication.cpp b/communication/communication.cpp index b0f2e8b..62d3fff 100644 --- a/communication/communication.cpp +++ b/communication/communication.cpp @@ -43,7 +43,7 @@ void Communication::joinThreads() { bool Communication::close() { joinThreads(); - if(!isOpen()) { + if(!isOpen() && !isDisconnected()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } @@ -55,6 +55,10 @@ bool Communication::isOpen() { return driver->isOpen(); } +bool Communication::isDisconnected() { + return driver->isDisconnected(); +} + bool Communication::sendPacket(std::vector& bytes) { // This is here so that other communication types (like multichannel) can override it return rawWrite(bytes); diff --git a/device/device.cpp b/device/device.cpp index 2ab887f..e9533c5 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -238,7 +238,7 @@ bool Device::open() { std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Check if we got a message, and if not, if settings are being applied if(!receivedMessage && !settings->applyingSettings) { - if(!stopHeartbeatThread) + if(!stopHeartbeatThread && !isDisconnected()) report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); break; } @@ -316,6 +316,11 @@ bool Device::goOnline() { bool Device::goOffline() { forEachExtension([](const std::shared_ptr& ext) { ext->onGoOffline(); return true; }); + if(isDisconnected()) { + online = false; + return true; + } + if(!com->sendCommand(Command::EnableNetworkCommunication, false)) return false; diff --git a/include/icsneo/communication/communication.h b/include/icsneo/communication/communication.h index d464d61..e4cb952 100644 --- a/include/icsneo/communication/communication.h +++ b/include/icsneo/communication/communication.h @@ -37,6 +37,7 @@ public: bool open(); bool close(); bool isOpen(); + bool isDisconnected(); virtual void spawnThreads(); virtual void joinThreads(); bool rawWrite(const std::vector& bytes) { return driver->write(bytes); } diff --git a/include/icsneo/communication/driver.h b/include/icsneo/communication/driver.h index 10df00d..c43b2a2 100644 --- a/include/icsneo/communication/driver.h +++ b/include/icsneo/communication/driver.h @@ -20,6 +20,7 @@ public: virtual ~Driver() {} virtual bool open() = 0; virtual bool isOpen() = 0; + virtual bool isDisconnected() { return disconnected; }; virtual bool close() = 0; virtual bool read(std::vector& bytes, size_t limit = 0); virtual bool readWait(std::vector& bytes, std::chrono::milliseconds timeout = std::chrono::milliseconds(100), size_t limit = 0); @@ -47,6 +48,7 @@ protected: moodycamel::BlockingConcurrentQueue writeQueue; std::thread readThread, writeThread; std::atomic closing{false}; + std::atomic disconnected{false}; }; } diff --git a/include/icsneo/device/device.h b/include/icsneo/device/device.h index c379a82..ed757d1 100644 --- a/include/icsneo/device/device.h +++ b/include/icsneo/device/device.h @@ -49,6 +49,7 @@ public: virtual bool close(); virtual bool isOnline() const { return online; } virtual bool isOpen() const { return com->isOpen(); } + virtual bool isDisconnected() const { return com->isDisconnected(); } virtual bool goOnline(); virtual bool goOffline(); diff --git a/include/icsneo/platform/posix/ftdi.h b/include/icsneo/platform/posix/ftdi.h index 6711593..2b3274d 100644 --- a/include/icsneo/platform/posix/ftdi.h +++ b/include/icsneo/platform/posix/ftdi.h @@ -19,7 +19,7 @@ public: static std::vector FindByProduct(int product); FTDI(const device_eventhandler_t& err, neodevice_t& forDevice); - ~FTDI() { close(); } + ~FTDI() { if(isOpen()) close(); } bool open(); bool close(); bool isOpen() { return ftdi.isOpen(); } diff --git a/include/icsneo/platform/posix/stm32.h b/include/icsneo/platform/posix/stm32.h index bd7d5ef..e70589c 100644 --- a/include/icsneo/platform/posix/stm32.h +++ b/include/icsneo/platform/posix/stm32.h @@ -24,6 +24,7 @@ public: * Other POSIX systems (BSDs, QNX, etc) will need bespoke code written in the future */ STM32(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err), device(forDevice) {} + ~STM32() { if(isOpen()) close(); } static std::vector FindByProduct(int product); bool open(); @@ -38,6 +39,7 @@ private: void readTask(); void writeTask(); + bool fdIsValid(); }; } diff --git a/platform/posix/ftdi.cpp b/platform/posix/ftdi.cpp index 66fc6cc..eda7082 100644 --- a/platform/posix/ftdi.cpp +++ b/platform/posix/ftdi.cpp @@ -1,3 +1,4 @@ +#include "libusb-1.0/libusb.h" #include "icsneo/platform/ftdi.h" #include #include @@ -81,7 +82,7 @@ bool FTDI::open() { } bool FTDI::close() { - if(!isOpen()) { + if(!isOpen() && !isDisconnected()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } @@ -94,9 +95,12 @@ bool FTDI::close() { if(writeThread.joinable()) writeThread.join(); - bool ret = ftdi.closeDevice(); - if(!ret) - report(APIEvent::Type::DriverFailedToClose, APIEvent::Severity::Error); + bool ret = true; + if(!isDisconnected()) { + ret = ftdi.closeDevice(); + if(!ret) + report(APIEvent::Type::DriverFailedToClose, APIEvent::Severity::Error); + } uint8_t flush; WriteOperation flushop; @@ -184,11 +188,17 @@ void FTDI::readTask() { constexpr size_t READ_BUFFER_SIZE = 8; uint8_t readbuf[READ_BUFFER_SIZE]; EventManager::GetInstance().downgradeErrorsOnCurrentThread(); - while(!closing) { + while(!closing && !isDisconnected()) { auto readBytes = ftdi.read(readbuf, READ_BUFFER_SIZE); - if(readBytes < 0) - report(APIEvent::Type::FailedToRead, APIEvent::Severity::EventWarning); - else + if(readBytes < 0) { + if(readBytes == LIBUSB_ERROR_NO_DEVICE || readBytes == LIBUSB_ERROR_PIPE) { + if(!isDisconnected()) { + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + } + } else + report(APIEvent::Type::FailedToRead, APIEvent::Severity::EventWarning); + } else readQueue.enqueue_bulk(readbuf, readBytes); } } @@ -196,16 +206,23 @@ void FTDI::readTask() { void FTDI::writeTask() { WriteOperation writeOp; EventManager::GetInstance().downgradeErrorsOnCurrentThread(); - while(!closing) { + while(!closing && !isDisconnected()) { if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100))) continue; size_t offset = 0; while(offset < writeOp.bytes.size()) { auto writeBytes = ftdi.write(writeOp.bytes.data() + offset, (int)writeOp.bytes.size() - offset); - if(writeBytes < 0) - report(APIEvent::Type::FailedToWrite, APIEvent::Severity::EventWarning); - else + if(writeBytes < 0) { + if(writeBytes == LIBUSB_ERROR_NO_DEVICE || writeBytes == LIBUSB_ERROR_PIPE) { + if(!isDisconnected()) { + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + } + break; + } else + report(APIEvent::Type::FailedToWrite, APIEvent::Severity::EventWarning); + } else offset += writeBytes; } diff --git a/platform/posix/stm32.cpp b/platform/posix/stm32.cpp index d3c0977..754e0bf 100644 --- a/platform/posix/stm32.cpp +++ b/platform/posix/stm32.cpp @@ -26,7 +26,13 @@ bool STM32::open() { return false; } - fd = ::open(ttyPath.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + // Some devices can take a while to boot + for(int i = 0; i != 50; ++i) { + fd = ::open(ttyPath.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + if(fd != -1) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } if(!isOpen()) { //std::cout << "Open of " << ttyPath.c_str() << " failed with " << strerror(errno) << ' '; report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); @@ -82,7 +88,7 @@ bool STM32::isOpen() { } bool STM32::close() { - if(!isOpen()) { + if(!isOpen() && !isDisconnected()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } @@ -117,7 +123,7 @@ void STM32::readTask() { constexpr size_t READ_BUFFER_SIZE = 2048; uint8_t readbuf[READ_BUFFER_SIZE]; EventManager::GetInstance().downgradeErrorsOnCurrentThread(); - while(!closing) { + while(!closing && !isDisconnected()) { fd_set rfds = {0}; struct timeval tv = {0}; FD_SET(fd, &rfds); @@ -126,19 +132,37 @@ void STM32::readTask() { auto bytesRead = ::read(fd, readbuf, READ_BUFFER_SIZE); if(bytesRead > 0) readQueue.enqueue_bulk(readbuf, bytesRead); + else { + if(!fdIsValid() && !isDisconnected()) { + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + } + } } } void STM32::writeTask() { WriteOperation writeOp; EventManager::GetInstance().downgradeErrorsOnCurrentThread(); - while(!closing) { + while(!closing && !isDisconnected()) { if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100))) continue; const ssize_t writeSize = (ssize_t)writeOp.bytes.size(); ssize_t actualWritten = ::write(fd, writeOp.bytes.data(), writeSize); - if(actualWritten != writeSize) - report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error); + if(actualWritten != writeSize) { + if(!fdIsValid()) { + if(!isDisconnected()) { + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + } + } else + report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error); + } } -} \ No newline at end of file +} + +bool STM32::fdIsValid() { + struct termios tty = {}; + return tcgetattr(fd, &tty) == 0 ? true : false; +} diff --git a/platform/windows/vcp.cpp b/platform/windows/vcp.cpp index 2b616fc..e950c2a 100644 --- a/platform/windows/vcp.cpp +++ b/platform/windows/vcp.cpp @@ -346,7 +346,7 @@ void VCP::readTask() { IOTaskState state = LAUNCH; DWORD bytesRead = 0; EventManager::GetInstance().downgradeErrorsOnCurrentThread(); - while(!closing) { + while(!closing && !isDisconnected()) { switch(state) { case LAUNCH: { COMSTAT comStatus; @@ -365,8 +365,15 @@ void VCP::readTask() { auto lastError = GetLastError(); if(lastError == ERROR_IO_PENDING) state = WAIT; - else if(lastError != ERROR_SUCCESS) - report(APIEvent::Type::FailedToRead, APIEvent::Severity::Error); + else if(lastError != ERROR_SUCCESS) { + if(lastError == ERROR_ACCESS_DENIED) { + if(!isDisconnected()) { + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + } + } else + report(APIEvent::Type::FailedToRead, APIEvent::Severity::Error); + } } break; case WAIT: { @@ -392,7 +399,7 @@ void VCP::writeTask() { VCP::WriteOperation writeOp; DWORD bytesWritten = 0; EventManager::GetInstance().downgradeErrorsOnCurrentThread(); - while(!closing) { + while(!closing && !isDisconnected()) { switch(state) { case LAUNCH: { if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100))) @@ -406,7 +413,12 @@ void VCP::writeTask() { if(winerr == ERROR_IO_PENDING) { state = WAIT; } - else + else if(winerr == ERROR_ACCESS_DENIED) { + if(!isDisconnected()) { + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + } + } else report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error); } break;