diff --git a/device/device.cpp b/device/device.cpp index a023afa..e2683b9 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -488,27 +488,6 @@ bool Device::clearScript() return written > 0; } -std::optional Device::getCoreMiniVersion() -{ - if(!isOpen()) { - report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); - return std::nullopt; - } - - static std::shared_ptr filter = std::make_shared(Message::Type::ScriptStatus); - const auto generic = com->waitForMessageSync([this]() { - return com->sendCommand(Command::ScriptStatus); - }, filter); - - if (!generic || generic->type != Message::Type::ScriptStatus) { - report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); - return std::nullopt; - } - - const auto resp = std::static_pointer_cast(generic); - return resp->coreminiVersion; -} - bool Device::transmit(std::shared_ptr frame) { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); @@ -1175,6 +1154,246 @@ bool Device::allowSleep(bool remoteWakeup) { return true; } +void Device::scriptStatusThreadBody() +{ + std::unique_lock lk(scriptStatusMutex); + + EventManager::GetInstance().downgradeErrorsOnCurrentThread(); + + bool first = true; + while(!stopScriptStatusThread) + { + if(first) // Skip the first wait + first = false; + else + stopScriptStatusCv.wait_for(lk, std::chrono::seconds(10)); + + const auto resp = getScriptStatus(); + + //If value changed/was inserted, notify callback + if(updateScriptStatusValue(ScriptStatus::CoreMiniRunning, resp->isCoreminiRunning)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::CoreMiniRunning, resp->isCoreminiRunning); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::SectorOverflow, resp->sectorOverflows)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::SectorOverflow, resp->sectorOverflows); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::RemainingSectors, resp->numRemainingSectorBuffers)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::RemainingSectors, resp->numRemainingSectorBuffers); + lk.lock(); + } + + bool logging = false; + if(updateScriptStatusValue(ScriptStatus::LastSector, resp->lastSector)) + { + logging = true; + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::LastSector, resp->lastSector); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::Logging, logging)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::Logging, logging); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::ReadBinSize, resp->readBinSize)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::ReadBinSize, resp->readBinSize); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::MinSector, resp->minSector)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::MinSector, resp->minSector); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::MaxSector, resp->maxSector)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::MaxSector, resp->maxSector); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::CurrentSector, resp->currentSector)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::CurrentSector, resp->currentSector); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::CoreMiniCreateTime, resp->coreminiCreateTime)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::CoreMiniCreateTime, resp->coreminiCreateTime); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::FileChecksum, resp->fileChecksum)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::FileChecksum, resp->fileChecksum); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::CoreMiniVersion, resp->coreminiVersion)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::CoreMiniVersion, resp->coreminiVersion); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::CoreMiniHeaderSize, resp->coreminiHeaderSize)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::CoreMiniHeaderSize, resp->coreminiHeaderSize); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::DiagnosticErrorCode, resp->diagnosticErrorCode)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::DiagnosticErrorCode, resp->diagnosticErrorCode); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::DiagnosticErrorCodeCount, resp->diagnosticErrorCodeCount)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::DiagnosticErrorCodeCount, resp->diagnosticErrorCodeCount); + lk.lock(); + } + + if(updateScriptStatusValue(ScriptStatus::MaxCoreMiniSize, resp->maxCoreminiSizeKB)) + { + lk.unlock(); + notifyScriptStatusCallback(ScriptStatus::MaxCoreMiniSize, resp->maxCoreminiSizeKB); + lk.lock(); + } + } +} + +std::shared_ptr Device::getScriptStatus() const +{ + static std::shared_ptr filter = std::make_shared(Message::Type::ScriptStatus); + + const auto generic = com->waitForMessageSync([this]() { + return com->sendCommand(Command::ScriptStatus); + }, filter, std::chrono::milliseconds(3000)); + + if(!generic || generic->type != Message::Type::ScriptStatus) { + report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); + return nullptr; + } + + return std::static_pointer_cast(generic); +} + +bool Device::updateScriptStatusValue(ScriptStatus key, uint64_t value) +{ + auto pair = scriptStatusValues.find(key); + if(pair != scriptStatusValues.end()) + { + if(pair->second != value) + { + //Value changed + scriptStatusValues[key] = value; + return true; + } + //Value didn't change + return false; + } + + //Value was inserted + scriptStatusValues.insert(std::make_pair(key, value)); + return true; +} + +void Device::notifyScriptStatusCallback(ScriptStatus key, uint64_t value) +{ + auto callbackList = scriptStatusCallbacks.find(key); + if(callbackList != scriptStatusCallbacks.end()) + { + for(const auto& callback : callbackList->second) + { + if(callback) { + try { + callback(value); + } catch(...) { + report(APIEvent::Type::Unknown, APIEvent::Severity::Error); + } + } + } + } +} + +Lifetime Device::addScriptStatusCallback(ScriptStatus key, ScriptStatusCallback cb) +{ + if(!isOpen()) { + report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); + return {}; + } + + std::lock_guard lk(scriptStatusMutex); + if(!scriptStatusThread.joinable()) { + // Start the thread + stopScriptStatusThread = false; + scriptStatusThread = std::thread([this]() { scriptStatusThreadBody(); }); + } + + size_t idx = 0; + std::vector callbackList; + auto callbackPair = scriptStatusCallbacks.find(key); + if(callbackPair != scriptStatusCallbacks.end()) + callbackList = callbackPair->second; + + if(idx == callbackList.size()) + callbackList.push_back(std::move(cb)); + else callbackList[idx] = std::move(cb); + + scriptStatusCallbacks.insert_or_assign(key, callbackList); + + return Lifetime([this, key, idx](){ + std::unique_lock lk2(scriptStatusMutex); + auto callbackList = scriptStatusCallbacks.find(key); + if(callbackList != scriptStatusCallbacks.end()) + callbackList->second[idx] = ScriptStatusCallback(); + stopScriptStatusThreadIfNecessary(std::move(lk2)); + }); +} + +void Device::stopScriptStatusThreadIfNecessary(std::unique_lock lk) +{ + for(const auto& callbackList : scriptStatusCallbacks) + { + for(const auto& callback : callbackList.second) + { + if(callback) + return; + } + } + + stopScriptStatusThread = true; + lk.unlock(); + stopScriptStatusCv.notify_all(); + scriptStatusThread.join(); + scriptStatusThread = std::thread(); +} + Lifetime Device::suppressDisconnects() { std::lock_guard lk(heartbeatMutex); heartbeatSuppressedByUser++; diff --git a/include/icsneo/communication/message/scriptstatusmessage.h b/include/icsneo/communication/message/scriptstatusmessage.h index 0a3adbf..17e072f 100644 --- a/include/icsneo/communication/message/scriptstatusmessage.h +++ b/include/icsneo/communication/message/scriptstatusmessage.h @@ -13,21 +13,21 @@ class ScriptStatusMessage : public Message { public: ScriptStatusMessage() : Message( Message::Type::ScriptStatus ) {} - bool isCoreminiRunning; - uint32_t sectorOverflows; - uint32_t numRemainingSectorBuffers; - int32_t lastSector; - int32_t readBinSize; - int32_t minSector; - int32_t maxSector; - int32_t currentSector; - uint64_t coreminiCreateTime; - uint16_t fileChecksum; - uint16_t coreminiVersion; - uint16_t coreminiHeaderSize; - uint8_t diagnosticErrorCode; - uint8_t diagnosticErrorCodeCount; - uint16_t maxCoreminiSizeKB; + bool isCoreminiRunning = false; + uint32_t sectorOverflows = 0; + uint32_t numRemainingSectorBuffers = 0; + int32_t lastSector = 0; + int32_t readBinSize = 0; + int32_t minSector = 0; + int32_t maxSector = 0; + int32_t currentSector = 0; + uint64_t coreminiCreateTime = 0; + uint16_t fileChecksum = 0; + uint16_t coreminiVersion = 0; + uint16_t coreminiHeaderSize = 0; + uint8_t diagnosticErrorCode = 0; + uint8_t diagnosticErrorCodeCount = 0; + uint16_t maxCoreminiSizeKB = 0; }; } diff --git a/include/icsneo/device/device.h b/include/icsneo/device/device.h index 733d739..c7f8c98 100644 --- a/include/icsneo/device/device.h +++ b/include/icsneo/device/device.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "icsneo/api/eventmanager.h" #include "icsneo/api/lifetime.h" #include "icsneo/device/neodevice.h" @@ -29,6 +30,7 @@ #include "icsneo/communication/io.h" #include "icsneo/communication/message/resetstatusmessage.h" #include "icsneo/communication/message/wiviresponsemessage.h" +#include "icsneo/communication/message/scriptstatusmessage.h" #include "icsneo/device/extensions/flexray/controller.h" #include "icsneo/communication/message/flexray/control/flexraycontrolmessage.h" #include "icsneo/communication/message/ethphymessage.h" @@ -138,8 +140,6 @@ public: bool stopScript(); bool clearScript(); - std::optional getCoreMiniVersion(); - // Message polling related functions bool enableMessagePolling(); bool disableMessagePolling(); @@ -347,6 +347,129 @@ public: */ bool allowSleep(bool remoteWakeup = false); + enum class ScriptStatus { + CoreMiniRunning = 0, + SectorOverflow = 1, + RemainingSectors = 2, + LastSector = 3, + ReadBinSize = 4, + MinSector = 5, + MaxSector = 6, + CurrentSector = 7, + CoreMiniCreateTime = 8, + FileChecksum = 9, + CoreMiniVersion = 10, + CoreMiniHeaderSize = 11, + DiagnosticErrorCode = 12, + DiagnosticErrorCodeCount = 13, + MaxCoreMiniSize = 14, + Logging = 16, + }; + + typedef std::function< void(uint64_t value) > ScriptStatusCallback; + + /** + * Get all current script status values + */ + std::shared_ptr getScriptStatus() const; + + /** + * Add a callback to be called when VSSAL script running state changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addCoreMiniRunningCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::CoreMiniRunning, std::move(cb)); } + + /** + * Add a callback to be called when the number of times a sector was dropped due to lack of space + * in firmware's filesystem buffer changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addSectorOverflowsCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::SectorOverflow, std::move(cb)); } + + /** + * Add a callback to be called when number of sectors of space left in firmware's local file system buffer changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addNumberRemainingSectorsCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::RemainingSectors, std::move(cb)); } + + /** + * Add a callback to be called when last sector that was written to changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addLastSectorCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::LastSector, std::move(cb)); } + + /** + * Add a callback to be called when the size of the ReadBin changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addReadBinSizeCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::ReadBinSize, std::move(cb)); } + + /** + * Add a callback to be called when the first sector address of logged data changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addMinSectorCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::MinSector, std::move(cb)); } + + /** + * Add a callback to be called when the last sector address of logged data changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addMaxSectorCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::MaxSector, std::move(cb)); } + + /** + * Add a callback to be called when the sector that is about to be written changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addCurrentSectorCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::CurrentSector, std::move(cb)); } + + /** + * Add a callback to be called when the VSSAL script create time changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addCoreMiniCreateTimeCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::CoreMiniCreateTime, std::move(cb)); } + + /** + * Add a callback to be called when the VSSAL script checksum changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addFileChecksumCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::FileChecksum, std::move(cb)); } + + /** + * Add a callback to be called when the VSSAL script version changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addCoreMiniVersionCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::CoreMiniVersion, std::move(cb)); } + + /** + * Add a callback to be called when the VSSAL script header size changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addCoreMiniHeaderSizeCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::CoreMiniHeaderSize, std::move(cb)); } + + /** + * Add a callback to be called when the firmware diagnostic error code changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addDiagnosticErrorCodeCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::DiagnosticErrorCode, std::move(cb)); } + + /** + * Add a callback to be called when the firmware diagnostic error code count changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addDiagnosticErrorCodeCountCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::DiagnosticErrorCodeCount, std::move(cb)); } + + /** + * Add a callback to be called when the maximum size a VSSAL script can be changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addMaxCoreMiniSizeCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::MaxCoreMiniSize, std::move(cb)); } + + /** + * Add a callback to be called when the device logging state changes + */ + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addLoggingCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::Logging, std::move(cb)); } + virtual std::vector> getFlexRayControllers() const { return {}; } void addExtension(std::shared_ptr&& extension); @@ -456,8 +579,8 @@ protected: } template - std::unique_ptr makeSettings(std::shared_ptr com) { - return std::unique_ptr(new Settings(com)); + std::unique_ptr makeSettings(std::shared_ptr comm) { + return std::unique_ptr(new Settings(comm)); } virtual void setupSettings(IDeviceSettings&) {} @@ -544,6 +667,20 @@ private: std::vector< std::pair > sleepRequestedCallbacks; void wiviThreadBody(); void stopWiVIThreadIfNecessary(std::unique_lock lk); + + //Script status + std::atomic stopScriptStatusThread{false}; + std::condition_variable stopScriptStatusCv; + mutable std::mutex scriptStatusMutex; + std::thread scriptStatusThread; + std::unordered_map> scriptStatusCallbacks; + std::unordered_map scriptStatusValues; + Lifetime addScriptStatusCallback(ScriptStatus, ScriptStatusCallback); + bool updateScriptStatusValue(ScriptStatus, uint64_t newValue); + void notifyScriptStatusCallback(ScriptStatus, uint64_t); + void scriptStatusThreadBody(); + void stopScriptStatusThreadIfNecessary(std::unique_lock lk); + }; }