diff --git a/api/icsneocpp/event.cpp b/api/icsneocpp/event.cpp index 2cd95ed..ec95b20 100644 --- a/api/icsneocpp/event.cpp +++ b/api/icsneocpp/event.cpp @@ -93,6 +93,7 @@ static constexpr const char* NO_DEVICE_RESPONSE = "Expected a response from the static constexpr const char* MESSAGE_FORMATTING = "The message was not properly formed."; static constexpr const char* CANFD_NOT_SUPPORTED = "This device does not support CANFD."; static constexpr const char* RTR_NOT_SUPPORTED = "RTR is not supported with CANFD."; +static constexpr const char* DEVICE_DISCONNECTED = "The device was disconnected."; // Transport Errors static constexpr const char* FAILED_TO_READ = "A read operation failed."; @@ -184,6 +185,8 @@ const char* APIEvent::DescriptionForType(Type type) { return CANFD_NOT_SUPPORTED; case Type::RTRNotSupported: return RTR_NOT_SUPPORTED; + case Type::DeviceDisconnected: + return DEVICE_DISCONNECTED; // Transport Errors case Type::FailedToRead: diff --git a/device/device.cpp b/device/device.cpp index 7361baf..0ca2ca0 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -205,6 +205,32 @@ bool Device::open() { handleInternalMessage(message); })); + std::atomic receivedMessage{false}; + messageReceivedCallbackID = com->addMessageCallback(MessageCallback(filter, [&](std::shared_ptr message) { + receivedMessage = true; + })); + + heartbeatThread = std::thread([&]() { + EventManager::GetInstance().downgradeErrorsOnCurrentThread(); + while(true) { + // Wait for 110ms for a possible heartbeat + std::this_thread::sleep_for(std::chrono::milliseconds(110)); + if(!receivedMessage) { + // No heartbeat received, request a status + com->sendCommand(Command::RequestStatusUpdate); + // The response should come back quickly if the com is quiet + 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) + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + return; + } + } + receivedMessage = false; + } + }); + forEachExtension([](const std::shared_ptr& ext) { ext->onDeviceOpen(); return true; }); return true; } @@ -215,12 +241,17 @@ bool Device::close() { return false; } + stopHeartbeatThread = true; + if(isOnline()) goOffline(); if(internalHandlerCallbackID) com->removeMessageCallback(internalHandlerCallbackID); + if(messageReceivedCallbackID) + com->removeMessageCallback(messageReceivedCallbackID); + internalHandlerCallbackID = 0; forEachExtension([](const std::shared_ptr& ext) { ext->onDeviceClose(); return true; }); diff --git a/device/idevicesettings.cpp b/device/idevicesettings.cpp index 7ab95be..bd78490 100644 --- a/device/idevicesettings.cpp +++ b/device/idevicesettings.cpp @@ -210,6 +210,8 @@ bool IDeviceSettings::apply(bool temporary) { bytestream[6] = (uint8_t)(gs_checksum >> 8); memcpy(bytestream.data() + 7, getMutableRawStructurePointer(), settings.size()); + // Pause I/O with the device while the settings are applied + applyingSettings = true; std::shared_ptr msg = com->waitForMessageSync([this, &bytestream]() { return com->sendCommand(Command::SetSettings, bytestream); @@ -250,6 +252,8 @@ bool IDeviceSettings::apply(bool temporary) { }, Main51MessageFilter(Command::SaveSettings), std::chrono::milliseconds(5000)); } + applyingSettings = false; + refresh(); // Refresh our buffer with what the device has, whether we were successful or not bool ret = (msg && msg->data[0] == 1); // Device sends 0x01 for success @@ -270,6 +274,8 @@ bool IDeviceSettings::applyDefaults(bool temporary) { return false; } + applyingSettings = true; + std::shared_ptr msg = com->waitForMessageSync([this]() { return com->sendCommand(Command::SetDefaultSettings); }, Main51MessageFilter(Command::SetDefaultSettings), std::chrono::milliseconds(1000)); @@ -317,6 +323,8 @@ bool IDeviceSettings::applyDefaults(bool temporary) { return com->sendCommand(Command::SaveSettings); }, Main51MessageFilter(Command::SaveSettings), std::chrono::milliseconds(5000)); } + + applyingSettings = false; refresh(); // Refresh our buffer with what the device has, whether we were successful or not diff --git a/include/icsneo/api/event.h b/include/icsneo/api/event.h index fdc5a6b..2b3751b 100644 --- a/include/icsneo/api/event.h +++ b/include/icsneo/api/event.h @@ -70,6 +70,7 @@ public: MessageFormattingError = 0x2019, CANFDNotSupported = 0x2020, RTRNotSupported = 0x2021, + DeviceDisconnected = 0x2022, // Transport Events FailedToRead = 0x3000, diff --git a/include/icsneo/device/device.h b/include/icsneo/device/device.h index e66df10..417b099 100644 --- a/include/icsneo/device/device.h +++ b/include/icsneo/device/device.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "icsneo/api/eventmanager.h" #include "icsneo/device/neodevice.h" #include "icsneo/device/idevicesettings.h" @@ -30,6 +31,8 @@ public: if(isMessagePollingEnabled()) disableMessagePolling(); close(); + if(heartbeatThread.joinable()) + heartbeatThread.join(); } static std::string SerialNumToString(uint32_t serial); @@ -100,6 +103,7 @@ protected: bool online = false; int messagePollingCallbackID = 0; int internalHandlerCallbackID = 0; + int messageReceivedCallbackID = 0; device_eventhandler_t report; // START Initialization Functions @@ -211,6 +215,9 @@ private: size_t pollingMessageLimit = 20000; moodycamel::BlockingConcurrentQueue> pollingContainer; void enforcePollingMessageLimit(); + + std::atomic stopHeartbeatThread{false}; + std::thread heartbeatThread; }; } diff --git a/include/icsneo/device/idevicesettings.h b/include/icsneo/device/idevicesettings.h index d9bc026..5bc21a9 100644 --- a/include/icsneo/device/idevicesettings.h +++ b/include/icsneo/device/idevicesettings.h @@ -324,6 +324,7 @@ static_assert(sizeof(UART_SETTINGS) == UART_SETTINGS_SIZE, "UART_SETTINGS is the #ifdef __cplusplus #include "icsneo/communication/communication.h" #include +#include namespace icsneo { @@ -404,6 +405,8 @@ public: bool readonly = false; bool disableGSChecksumming = false; + + std::atomic applyingSettings{false}; protected: std::shared_ptr com; device_eventhandler_t report;