diff --git a/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h b/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h index 51abd06..a3c49db 100644 --- a/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h +++ b/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h @@ -1,9 +1,11 @@ #ifndef DEBUGBROKERWRAPPER_H #define DEBUGBROKERWRAPPER_H +#include "AdvancedErrorManagement.h" #include "BrokerI.h" #include "DataSourceI.h" #include "DebugService.h" +#include "ErrorType.h" #include "FastPollingMutexSem.h" #include "HighResolutionTimer.h" #include "MemoryMapBroker.h" @@ -23,6 +25,7 @@ #include "MemoryMapSynchronisedMultiBufferInputBroker.h" #include "MemoryMapSynchronisedMultiBufferOutputBroker.h" #include "MemoryMapSynchronisedOutputBroker.h" +#include "RealTimeThreadSynchBroker.h" namespace MARTe { @@ -86,12 +89,11 @@ public: StreamString dsPath; DebugService::GetFullObjectName(dataSourceIn, dsPath); - fprintf(stderr, ">> %s broker for %s [%d]\n", - direction == InputSignals ? "Input" : "Output", dsPath.Buffer(), - numCopies); MemoryMapBroker *mmb = dynamic_cast(broker); if (mmb == NULL_PTR(MemoryMapBroker *)) { - fprintf(stderr, ">> Impossible to get broker pointer!!\n"); + REPORT_ERROR_STATIC(ErrorManagement::Warning, + "Impossible to get broker pointer for %s!!\n", + dsPath.Buffer()); } for (uint32 i = 0; i < numCopies; i++) { @@ -106,8 +108,8 @@ public: StreamString signalName; if (!dataSourceIn.GetSignalName(dsIdx, signalName)) signalName = "Unknown"; - fprintf(stderr, ">> registering %s.%s [%p]\n", dsPath.Buffer(), - signalName.Buffer(), mmb); + REPORT_ERROR_STATIC(ErrorManagement::Debug, "Registering %s.%s [%p]\n", + dsPath.Buffer(), signalName.Buffer(), mmb); uint8 dims = 0; uint32 elems = 1; @@ -148,18 +150,18 @@ public: // Register short path (In/Out) for GUI compatibility gamFullName.Printf("%s.%s.%s", absGamPath.Buffer(), dirStrShort, signalName.Buffer()); - signalInfoPointers[i] = - service->RegisterSignal(addr, type, gamFullName.Buffer(), dims, elems); + signalInfoPointers[i] = service->RegisterSignal( + addr, type, gamFullName.Buffer(), dims, elems); } else { // Fallback to short form gamFullName.Printf("%s.%s.%s", functionName, dirStrShort, signalName.Buffer()); - signalInfoPointers[i] = - service->RegisterSignal(addr, type, gamFullName.Buffer(), dims, elems); + signalInfoPointers[i] = service->RegisterSignal( + addr, type, gamFullName.Buffer(), dims, elems); } } else { - signalInfoPointers[i] = - service->RegisterSignal(addr, type, dsFullName.Buffer(), dims, elems); + signalInfoPointers[i] = service->RegisterSignal( + addr, type, dsFullName.Buffer(), dims, elems); } } @@ -199,8 +201,8 @@ public: virtual bool Init(SignalDirection direction, DataSourceI &ds, const char8 *const name, void *gamMem) { bool ret = BaseClass::Init(direction, ds, name, gamMem); - fprintf(stderr, ">> INIT BROKER %s %s\n", name, - direction == InputSignals ? "In" : "Out"); + REPORT_ERROR_STATIC(ErrorManagement::Debug, "INIT BROKER %s %s\n", name, + direction == InputSignals ? "In" : "Out"); if (ret) { numSignals = this->GetNumberOfCopies(); DebugBrokerHelper::InitSignals(this, ds, service, signalInfoPointers, @@ -214,8 +216,8 @@ public: virtual bool Init(SignalDirection direction, DataSourceI &ds, const char8 *const name, void *gamMem, const bool optim) { bool ret = BaseClass::Init(direction, ds, name, gamMem, false); - fprintf(stderr, ">> INIT optimized BROKER %s %s\n", name, - direction == InputSignals ? "In" : "Out"); + REPORT_ERROR_STATIC(ErrorManagement::Debug, "INIT optimized BROKER %s %s\n", + name, direction == InputSignals ? "In" : "Out"); if (ret) { numSignals = this->GetNumberOfCopies(); DebugBrokerHelper::InitSignals(this, ds, service, signalInfoPointers, @@ -403,6 +405,55 @@ typedef DebugBrokerWrapper DebugMemoryMapSynchronisedMultiBufferOutputBroker; // LCOV_EXCL_STOP +/** + * @brief Specialized wrapper for RealTimeThreadSynchBroker. + * @details The RealTimeThreadSynchBroker copies data in AddSample() which is + * called from the DataSource thread, not from Execute(). We trace signals + * in Execute() after the data has been safely copied. + */ +class DebugRealTimeThreadSyncBroker : public RealTimeThreadSynchBroker { +public: + DebugRealTimeThreadSyncBroker() : RealTimeThreadSynchBroker() { + service = NULL_PTR(DebugService *); + signalInfoPointers = NULL_PTR(DebugSignalInfo **); + numSignals = 0; + anyActive = false; + } + virtual ~DebugRealTimeThreadSyncBroker() { + if (signalInfoPointers) + delete[] signalInfoPointers; + } + virtual bool Execute() { + bool ret = RealTimeThreadSynchBroker::Execute(); + // Trace after data has been safely copied by AddSample + if (ret && (anyActive || (service && service->IsPaused()))) { + DebugBrokerHelper::Process(service, signalInfoPointers, activeIndices, + activeSizes, activeMutex); + } + return ret; + } + virtual bool Init(SignalDirection direction, DataSourceI &ds, + const char8 *const name, void *gamMem) { + bool ret = RealTimeThreadSynchBroker::Init(direction, ds, name, gamMem); + if (ret) { + numSignals = this->GetNumberOfCopies(); + DebugBrokerHelper::InitSignals(this, ds, service, signalInfoPointers, + numSignals, this->copyTable, name, + direction, &anyActive, &activeIndices, + &activeSizes, &activeMutex); + } + return ret; + } + DebugService *service; + DebugSignalInfo **signalInfoPointers; + uint32 numSignals; + volatile bool anyActive; + Vec activeIndices; + Vec activeSizes; + FastPollingMutexSem activeMutex; +}; +// LCOV_EXCL_START + typedef DebugBrokerBuilder DebugMemoryMapInputBrokerBuilder; // LCOV_EXCL_START @@ -426,6 +477,8 @@ typedef DebugBrokerBuilder DebugMemoryMapAsyncOutputBrokerBuilder; typedef DebugBrokerBuilder DebugMemoryMapAsyncTriggerOutputBrokerBuilder; +typedef DebugBrokerBuilder + DebugRealTimeThreadSyncBrokerBuilder; // LCOV_EXCL_STOP } // namespace MARTe diff --git a/Source/Components/Interfaces/DebugService/DebugService.cpp b/Source/Components/Interfaces/DebugService/DebugService.cpp index 27af8b6..ebf90f7 100644 --- a/Source/Components/Interfaces/DebugService/DebugService.cpp +++ b/Source/Components/Interfaces/DebugService/DebugService.cpp @@ -1,22 +1,47 @@ +#include "AdvancedErrorManagement.h" +#include "AnyType.h" #include "BasicTCPSocket.h" #include "ClassRegistryItem.h" #include "ConfigurationDatabase.h" #include "DataSourceI.h" #include "DebugBrokerWrapper.h" #include "DebugService.h" +#include "ErrorType.h" #include "GAM.h" #include "GlobalObjectsDatabase.h" #include "HighResolutionTimer.h" +#include "Matrix.h" #include "Message.h" #include "ObjectBuilder.h" #include "ObjectRegistryDatabase.h" +#include "RealTimeApplication.h" +#include "ReferenceT.h" +#include "StateMachine.h" #include "StreamString.h" #include "TimeoutType.h" #include "TypeConversion.h" -#include "ReferenceT.h" +#include "TypeDescriptor.h" namespace MARTe { +// Proxy to access private members of RealTimeApplication +class RealTimeApplicationProxy : public ReferenceContainer { +public: + // ReferenceContainer members + LinkedListHolderT list; + FastPollingMutexSem mux; + TimeoutType muxTimeout; + + // MessageI members (approximate alignment) + void *messageI_vtable; + uint8 messageFilters_padding[sizeof( + ReferenceContainer)]; // MessageFilterPool is a ReferenceContainer + + // RealTimeApplication members + StreamString stateNameHolder[2]; + uint32 index; +}; + DebugService *DebugService::instance = (DebugService *)0; static void EscapeJson(const char8 *src, StreamString &dst) { @@ -79,6 +104,176 @@ static bool FindPathInContainer(ReferenceContainer *container, return false; } +static void JsonifyScalar(const AnyType &at, StreamString &json) { + if (at.GetDataPointer() == NULL_PTR(void *)) { + json += "null"; + return; + } + char8 buf[1024]; + AnyType st(CharString, 0u, buf); + st.SetNumberOfElements(0, 1024); + if (TypeConvert(st, at)) { + TypeDescriptor td = at.GetTypeDescriptor(); + if (td.IsNumericType() || td == BooleanType) { + if (buf[0] == '\0') + json += "0"; + else + json += buf; + } else { + json += "\""; + EscapeJson(buf, json); + json += "\""; + } + } else { + json += "null"; + } +} + +static void JsonifyDatabaseInternal(ConfigurationDatabase &db, + StreamString &json) { + json += "{"; + uint32 n = db.GetNumberOfChildren(); + for (uint32 i = 0u; i < n; i++) { + const char8 *name = db.GetChildName(i); + json += "\""; + EscapeJson(name, json); + json += "\": "; + if (db.MoveRelative(name)) { + ConfigurationDatabase child; + db.Copy(child); + child.MoveToRoot(); + JsonifyDatabaseInternal(child, json); + db.MoveToAncestor(1u); + } else { + AnyType at = db.GetType(name); + uint32 nDims = at.GetNumberOfDimensions(); + if (nDims == 0u) { + JsonifyScalar(at, json); + } else if (nDims == 1u) { + uint32 nElements = at.GetNumberOfElements(0u); + json += "["; + for (uint32 j = 0u; j < nElements; j++) { + JsonifyScalar(at[j], json); + if (j < nElements - 1u) + json += ", "; + } + json += "]"; + } else if (nDims == 2u) { + uint32 nRows = at.GetNumberOfElements(0u); + uint32 nCols = at.GetNumberOfElements(1u); + json += "["; + for (uint32 r = 0u; r < nRows; r++) { + json += "["; + for (uint32 c = 0u; c < nCols; c++) { + JsonifyScalar(at[r][c], json); + if (c < nCols - 1u) + json += ", "; + } + json += "]"; + if (r < nRows - 1u) + json += ", "; + } + json += "]"; + } else { + json += "\"Unsupported dimensions\""; + } + } + if (i < n - 1) + json += ", "; + } + json += "}"; +} + +static void JsonifyObjectSafe(Reference ref, StreamString &json, uint32 depth, + Vec &visited) { + if (!ref.IsValid() || depth > 8) { + json += "null"; + return; + } + + for (uint32 i = 0u; i < visited.Size(); i++) { + if (visited[i] == ref.operator->()) { + json += "\"\""; + return; + } + } + visited.Push(ref.operator->()); + + json += "{\n"; + const char8 *className = "Unknown"; + const ClassProperties *cp = ref->GetClassProperties(); + if (cp != NULL_PTR(const ClassProperties *)) { + className = cp->GetName(); + } + json += "\"Class\": \""; + EscapeJson(className, json); + json += "\""; + + ConfigurationDatabase db; + bool exported = false; + + // CRITICAL: Avoid objects that are known to cause real-time interference + // or crashes when introspected live. + bool safeToExport = true; + if (StringHelper::Compare(className, "RealTimeThreadSynchronisation") == 0 || + StringHelper::Compare(className, "SyncDB") == 0) { + safeToExport = false; + } + + if (safeToExport) { + ClassRegistryItem *cri = ClassRegistryDatabase::Instance()->Find(className); + if (cri != NULL_PTR(ClassRegistryItem *)) { + if (cri->GetIntrospection() != NULL_PTR(const Introspection *)) { + exported = ref->ExportMetadata(db); + } + } + + if (!exported) { + exported = ref->ExportData(db); + } + } + + if (exported) { + db.MoveToRoot(); + uint32 n = db.GetNumberOfChildren(); + for (uint32 i = 0u; i < n; i++) { + const char8 *name = db.GetChildName(i); + json += ",\n\""; + EscapeJson(name, json); + json += "\": "; + + if (db.MoveRelative(name)) { + ConfigurationDatabase sub; + db.Copy(sub); + sub.MoveToRoot(); + JsonifyDatabaseInternal(sub, json); + db.MoveToAncestor(1u); + } else { + JsonifyScalar(db.GetType(name), json); + } + } + } + + ReferenceContainer *container = + dynamic_cast(ref.operator->()); + if (container) { + uint32 n = container->Size(); + if (n > 25) + n = 25; // Even tighter breadth limit + for (uint32 i = 0u; i < n; i++) { + Reference child = container->Get(i); + if (child.IsValid()) { + json += ",\n\"+"; + EscapeJson(child->GetName(), json); + json += "\": "; + JsonifyObjectSafe(child, json, depth + 1, visited); + } + } + } + json += "\n}"; + (void)visited.Remove(visited.Size() - 1u); +} + CLASS_REGISTER(DebugService, "1.0") DebugService::DebugService() @@ -113,6 +308,53 @@ DebugService::~DebugService() { } } +static void +DiscoverStatesRecursive(ReferenceContainer *container, + Vec &monitoredStates) { + if (container == NULL_PTR(ReferenceContainer *)) { + return; + } + uint32 n = container->Size(); + for (uint32 i = 0u; i < n; i++) { + Reference ref = container->Get(i); + if (ref.IsValid()) { + bool alreadyMonitored = false; + for (uint32 j = 0u; j < monitoredStates.Size(); j++) { + if (monitoredStates[j].obj == ref) { + alreadyMonitored = true; + break; + } + } + + if (!alreadyMonitored) { + bool isStateful = false; + if (dynamic_cast(ref.operator->()) != + NULL_PTR(RealTimeApplication *)) { + isStateful = true; + } else if (dynamic_cast(ref.operator->()) != + NULL_PTR(StateMachine *)) { + isStateful = true; + } + + if (isStateful) { + DebugService::MonitoredState ms; + ms.obj = ref; + ms.internalID = 0x90000000 | monitoredStates.Size(); + DebugService::GetFullObjectName(*(ref.operator->()), ms.path); + ms.lastState = ""; + monitoredStates.Push(ms); + } + } + + ReferenceContainer *sub = + dynamic_cast(ref.operator->()); + if (sub != NULL_PTR(ReferenceContainer *)) { + DiscoverStatesRecursive(sub, monitoredStates); + } + } + } +} + bool DebugService::Initialise(StructuredDataI &data) { if (!ReferenceContainer::Initialise(data)) return false; @@ -137,7 +379,7 @@ bool DebugService::Initialise(StructuredDataI &data) { (void)data.Read("UdpPort", port); streamPort = (uint16)port; } - + port = 8082; if (data.Read("LogPort", port)) { logPort = (uint16)port; @@ -157,24 +399,23 @@ bool DebugService::Initialise(StructuredDataI &data) { suppressTimeoutLogs = (suppress == 1); } - // Try to capture full configuration autonomously if data is a - // ConfigurationDatabase - ConfigurationDatabase *cdb = dynamic_cast(&data); - if (cdb != NULL_PTR(ConfigurationDatabase *)) { - // Save current position - StreamString currentPath; - // In MARTe2 ConfigurationDatabase there isn't a direct GetCurrentPath, - // but we can at least try to copy from root if we are at root. - // For now, we rely on explicit SetFullConfig or documentary injection. + // Copy local branch as fallback + const char *name = data.GetName(); + data.MoveToRoot(); + (void)data.Copy(fullConfig); + if (!data.MoveRelative(name)) { + REPORT_ERROR(ErrorManagement::FatalError, "Impossible to move back..."); + return false; } - // Copy local branch as fallback - (void)data.Copy(fullConfig); - if (isServer) { + monitoredStates.Clear(); + if (!traceBuffer.Init(8 * 1024 * 1024)) return false; + PatchRegistry(); + ConfigurationDatabase threadData; threadData.Write("Timeout", (uint32)1000); threadService.Initialise(threadData); @@ -230,7 +471,7 @@ void DebugService::PatchRegistry() { PatchItemInternal("MemoryMapMultiBufferOutputBroker", b7); DebugMemoryMapSynchronisedMultiBufferInputBrokerBuilder *b8 = new DebugMemoryMapSynchronisedMultiBufferInputBrokerBuilder(); - PatchItemInternal("MemoryMapSynchronisedMultiBufferInputBroker", b8); + PatchItemInternal("MemoryMapMultiBufferInputBroker", b8); DebugMemoryMapSynchronisedMultiBufferOutputBrokerBuilder *b9 = new DebugMemoryMapSynchronisedMultiBufferOutputBrokerBuilder(); PatchItemInternal("MemoryMapSynchronisedMultiBufferOutputBroker", b9); @@ -240,6 +481,9 @@ void DebugService::PatchRegistry() { DebugMemoryMapAsyncTriggerOutputBrokerBuilder *b11 = new DebugMemoryMapAsyncTriggerOutputBrokerBuilder(); PatchItemInternal("MemoryMapAsyncTriggerOutputBroker", b11); + DebugRealTimeThreadSyncBrokerBuilder *b12 = + new DebugRealTimeThreadSyncBrokerBuilder(); + PatchItemInternal("RealTimeThreadSyncBroker", b12); } DebugSignalInfo *DebugService::RegisterSignal(void *memoryAddress, @@ -247,7 +491,7 @@ DebugSignalInfo *DebugService::RegisterSignal(void *memoryAddress, const char8 *name, uint8 numberOfDimensions, uint32 numberOfElements) { - printf(" registering: %s\n", name); + REPORT_ERROR(ErrorManagement::Debug, "registering: %s", name); mutex.FastLock(); DebugSignalInfo *res = NULL_PTR(DebugSignalInfo *); uint32 sigIdx = 0xFFFFFFFF; @@ -369,8 +613,11 @@ ErrorManagement::ErrorType DebugService::Execute(ExecutionInfo &info) { return ErrorManagement::FatalError; } -ErrorManagement::ErrorType DebugService::HandleMessage(ReferenceT &data) { - printf(" DebugService received custom message: Function=%s\n", (const char8*)data->GetFunction()); +ErrorManagement::ErrorType +DebugService::HandleMessage(ReferenceT &data) { + REPORT_ERROR(ErrorManagement::Debug, + "DebugService received custom message: Function=%s", + (const char8 *)data->GetFunction()); return ErrorManagement::NoError; } @@ -452,20 +699,89 @@ ErrorManagement::ErrorType DebugService::Streamer(ExecutionInfo &info) { uint8 packetBuffer[4096]; uint32 packetOffset = 0; uint32 sequenceNumber = 0; + uint64 lastStatePollTime = 0; + while (info.GetStage() == ExecutionInfo::MainStage) { - // Poll monitored signals uint64 currentTimeMs = (uint64)((float64)HighResolutionTimer::Counter() * HighResolutionTimer::Period() * 1000.0); + + // Poll states every 100ms + if (currentTimeMs >= (lastStatePollTime + 100u)) { + lastStatePollTime = currentTimeMs; + mutex.FastLock(); + for (uint32 i = 0; i < monitoredStates.Size(); i++) { + StreamString currentState; + RealTimeApplication *app = dynamic_cast( + monitoredStates[i].obj.operator->()); + StateMachine *sm = + dynamic_cast(monitoredStates[i].obj.operator->()); + + if (app != NULL_PTR(RealTimeApplication *)) { + // Look for child RealTimeState objects and check if their GetIndex() + // matches app's But wait, RealTimeState is not easily accessible + // here. Let's use the proxy but with more padding to be safe, or + // better: use the public GetIndex() and then we need a way to get the + // state names. + + // Re-attempting proxy with what is most likely the layout: + // [ReferenceContainer][MessageI][RealTimeApplication] + // MessageI has virtual destructor -> vtable (8 bytes) + // MessageI has MessageFilterPool -> ReferenceContainer (40 bytes) + + struct RTAP { + uint8 padding[sizeof(ReferenceContainer) + 8 + + sizeof(ReferenceContainer)]; + StreamString stateNameHolder[2]; + uint32 index; + }; + + RTAP *proxy = reinterpret_cast(app); + uint32 idx = app->GetIndex(); + if (idx < 2u) { + currentState = proxy->stateNameHolder[idx]; + } + if (currentState.Size() == 0) { + // Maybe index is 1 but we only have 1 state? Or alignment still + // off. Try the other index if it's empty + currentState = proxy->stateNameHolder[1u - idx]; + } + } else if (sm != NULL_PTR(StateMachine *)) { + Reference s = sm->GetCurrentState(); + if (s.IsValid()) { + currentState = s->GetName(); + } + } + + if (currentState.Size() > 0u && + currentState != monitoredStates[i].lastState) { + monitoredStates[i].lastState = currentState; + uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() * + HighResolutionTimer::Period() * 1000000.0); + + char8 stateBuf[64]; + memset(stateBuf, 0, 64); + strncpy(stateBuf, currentState.Buffer(), 63); + traceBuffer.Push(monitoredStates[i].internalID, ts, (uint8 *)stateBuf, + 64); + } + } + mutex.FastUnLock(); + } + + // Poll monitored signals mutex.FastLock(); for (uint32 i = 0; i < monitoredSignals.Size(); i++) { - if (currentTimeMs >= (monitoredSignals[i].lastPollTime + monitoredSignals[i].periodMs)) { + if (currentTimeMs >= + (monitoredSignals[i].lastPollTime + monitoredSignals[i].periodMs)) { monitoredSignals[i].lastPollTime = currentTimeMs; uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() * HighResolutionTimer::Period() * 1000000.0); - + void *address = NULL_PTR(void *); - if (monitoredSignals[i].dataSource->GetSignalMemoryBuffer(monitoredSignals[i].signalIdx, 0, address)) { - traceBuffer.Push(monitoredSignals[i].internalID, ts, (uint8 *)address, monitoredSignals[i].size); + if (monitoredSignals[i].dataSource->GetSignalMemoryBuffer( + monitoredSignals[i].signalIdx, 0, address)) { + traceBuffer.Push(monitoredSignals[i].internalID, ts, (uint8 *)address, + monitoredSignals[i].size); } } } @@ -575,141 +891,145 @@ void DebugService::HandleCommand(StreamString cmd, BasicTCPSocket *client) { (void)client->Write(resp.Buffer(), s); } } - } else if (token == "DISCOVER") + } else if (token == "DISCOVER") { Discover(client); - else if (token == "MSG") { - StreamString dest, func, waitStr; - if (cmd.GetToken(dest, delims, term) && - cmd.GetToken(func, delims, term) && - cmd.GetToken(waitStr, delims, term)) { - bool wait = (waitStr == "1"); - - const char8 *pStart = cmd.Buffer() + cmd.Position(); - StreamString rawPayload = pStart; - - // Decode escaped newlines (\n) - StreamString payload; - rawPayload.Seek(0u); - char8 c; - while (rawPayload.Size() > rawPayload.Position()) { - uint32 readS = 1; - if (rawPayload.Read(&c, readS)) { - if (c == '\\') { - char8 next; - if (rawPayload.Read(&next, readS)) { - if (next == 'n') { - payload += '\n'; - } else { - payload += c; - payload += next; - } - } else { - payload += c; - } + } else if (token == "MSG") { + StreamString dest, func, waitStr; + if (cmd.GetToken(dest, delims, term) && + cmd.GetToken(func, delims, term) && + cmd.GetToken(waitStr, delims, term)) { + bool wait = (waitStr == "1"); + + const char8 *pStart = cmd.Buffer() + cmd.Position(); + StreamString rawPayload = pStart; + + // Decode escaped newlines (\n) + StreamString payload; + rawPayload.Seek(0u); + char8 c; + while (rawPayload.Size() > rawPayload.Position()) { + uint32 readS = 1; + if (rawPayload.Read(&c, readS)) { + if (c == '\\') { + char8 next; + if (rawPayload.Read(&next, readS)) { + if (next == 'n') { + payload += '\n'; } else { payload += c; + payload += next; } - } - } - - ReferenceT msg( - "Message", GlobalObjectsDatabase::Instance()->GetStandardHeap()); - - ConfigurationDatabase msgConfig; - msgConfig.Write("Destination", dest.Buffer()); - msgConfig.Write("Function", func.Buffer()); - if (wait) { - msgConfig.Write("Mode", "ExpectsReply"); - } - - if (payload.Size() > 0u) { - payload.Seek(0u); - StreamString line; - while (payload.GetToken(line, "\n", term)) { - if (line.Size() > 0u) { - const char8 *eq = StringHelper::SearchChar(line.Buffer(), '='); - if (eq != NULL_PTR(const char8 *)) { - StreamString key, val; - uint32 eqPos = (uint32)(eq - line.Buffer()); - (void)line.Seek(0u); - - char8* keyBuf = new char8[eqPos + 1]; - uint32 keyReadSize = eqPos; - if (line.Read(keyBuf, keyReadSize)) { - keyBuf[eqPos] = '\0'; - key = keyBuf; - } - delete[] keyBuf; - - (void)line.Seek(eqPos + 1u); - uint32 valLen = line.Size() - eqPos - 1u; - char8* valBuf = new char8[valLen + 1]; - uint32 valReadSize = valLen; - if (line.Read(valBuf, valReadSize)) { - valBuf[valLen] = '\0'; - val = valBuf; - } - delete[] valBuf; - - if (key.Size() > 0u) { - if (msgConfig.CreateRelative("Payload")) { - (void)msgConfig.Write(key.Buffer(), val.Buffer()); - (void)msgConfig.MoveToAncestor(1u); - } - } - - } - } - line = ""; - } - } - - ErrorManagement::ErrorType err = ErrorManagement::ParametersError; - if (msg->Initialise(msgConfig)) { - // Find destination object in the global database - Reference destObj = ObjectRegistryDatabase::Instance()->Find(dest.Buffer()); - if (destObj.IsValid()) { - Object* sender = this; - // Double check if we are in the registry to be a valid sender - StreamString myPath; - if (!GetFullObjectName(*this, myPath)) { - sender = NULL_PTR(Object*); - } - - if (wait) { - err = MessageI::WaitForReply(msg, TTInfiniteWait); - } else { - err = MessageI::SendMessage(msg, sender); - } - } else { - - printf(" MSG: Destination object %s not found in ORD\n", dest.Buffer()); - } - - if (err != ErrorManagement::NoError) { - printf(" MSG: MessageI dispatch failed.\n"); - } - } else { - printf(" MSG: Message initialization failed\n"); - } - - if (client) { - - if (err == ErrorManagement::NoError) { - uint32 okSize = 7; - (void)client->Write("OK MSG\n", okSize); } else { - uint32 errSize = 10; - (void)client->Write("ERROR MSG\n", errSize); + payload += c; } + } else { + payload += c; } } } - else if (token == "SERVICE_INFO") { + + ReferenceT msg( + "Message", GlobalObjectsDatabase::Instance()->GetStandardHeap()); + + ConfigurationDatabase msgConfig; + msgConfig.Write("Destination", dest.Buffer()); + msgConfig.Write("Function", func.Buffer()); + if (wait) { + msgConfig.Write("Mode", "ExpectsReply"); + } + + if (payload.Size() > 0u) { + payload.Seek(0u); + StreamString line; + while (payload.GetToken(line, "\n", term)) { + if (line.Size() > 0u) { + const char8 *eq = StringHelper::SearchChar(line.Buffer(), '='); + if (eq != NULL_PTR(const char8 *)) { + StreamString key, val; + uint32 eqPos = (uint32)(eq - line.Buffer()); + (void)line.Seek(0u); + + char8 *keyBuf = new char8[eqPos + 1]; + uint32 keyReadSize = eqPos; + if (line.Read(keyBuf, keyReadSize)) { + keyBuf[eqPos] = '\0'; + key = keyBuf; + } + delete[] keyBuf; + + (void)line.Seek(eqPos + 1u); + uint32 valLen = line.Size() - eqPos - 1u; + char8 *valBuf = new char8[valLen + 1]; + uint32 valReadSize = valLen; + if (line.Read(valBuf, valReadSize)) { + valBuf[valLen] = '\0'; + val = valBuf; + } + delete[] valBuf; + + if (key.Size() > 0u) { + if (msgConfig.CreateRelative("Payload")) { + (void)msgConfig.Write(key.Buffer(), val.Buffer()); + (void)msgConfig.MoveToAncestor(1u); + } + } + } + } + line = ""; + } + } + + ErrorManagement::ErrorType err = ErrorManagement::ParametersError; + if (msg->Initialise(msgConfig)) { + // Find destination object in the global database + Reference destObj = + ObjectRegistryDatabase::Instance()->Find(dest.Buffer()); + if (destObj.IsValid()) { + Object *sender = this; + // Double check if we are in the registry to be a valid sender + StreamString myPath; + if (!GetFullObjectName(*this, myPath)) { + sender = NULL_PTR(Object *); + } + + if (wait) { + err = MessageI::WaitForReply(msg, TTInfiniteWait); + } else { + err = MessageI::SendMessage(msg, sender); + } + } else { + + REPORT_ERROR(ErrorManagement::Warning, + "MSG: Destination object %s not found in ORD", + dest.Buffer()); + } + + if (err != ErrorManagement::NoError) { + REPORT_ERROR(ErrorManagement::Warning, + "MSG: MessageI dispatch failed."); + } + } else { + REPORT_ERROR(ErrorManagement::Warning, + "MSG: Message initialization failed"); + } + + if (client) { + + if (err == ErrorManagement::NoError) { + uint32 okSize = 7; + (void)client->Write("OK MSG\n", okSize); + } else { + uint32 errSize = 10; + (void)client->Write("ERROR MSG\n", errSize); + } + } + } + } else if (token == "SERVICE_INFO") { if (client) { StreamString resp; - resp.Printf("OK SERVICE_INFO TCP_CTRL:%u UDP_STREAM:%u TCP_LOG:%u STATE:%s\n", - controlPort, streamPort, logPort, isPaused ? "PAUSED" : "RUNNING"); + resp.Printf( + "OK SERVICE_INFO TCP_CTRL:%u UDP_STREAM:%u TCP_LOG:%u STATE:%s\n", + controlPort, streamPort, logPort, isPaused ? "PAUSED" : "RUNNING"); uint32 s = resp.Size(); (void)client->Write(resp.Buffer(), s); } @@ -717,7 +1037,8 @@ void DebugService::HandleCommand(StreamString cmd, BasicTCPSocket *client) { StreamString subToken; if (cmd.GetToken(subToken, delims, term) && subToken == "SIGNAL") { StreamString name, period; - if (cmd.GetToken(name, delims, term) && cmd.GetToken(period, delims, term)) { + if (cmd.GetToken(name, delims, term) && + cmd.GetToken(period, delims, term)) { uint32 p = 100; AnyType pVal(UnsignedInteger32Bit, 0u, &p); AnyType pStr(CharString, 0u, period.Buffer()); @@ -763,7 +1084,8 @@ void DebugService::HandleCommand(StreamString cmd, BasicTCPSocket *client) { StreamString json; json = "{\"Name\": \"Root\", \"Class\": \"ObjectRegistryDatabase\", " "\"Children\": [\n"; - (void)ExportTree(ObjectRegistryDatabase::Instance(), json, NULL_PTR(const char8 *)); + (void)ExportTree(ObjectRegistryDatabase::Instance(), json, + NULL_PTR(const char8 *)); json += "\n]}\nOK TREE\n"; uint32 s = json.Size(); if (client) @@ -851,44 +1173,18 @@ void DebugService::EnrichWithConfig(const char8 *path, StreamString &json) { } void DebugService::JsonifyDatabase(ConfigurationDatabase &db, - StreamString &json) { - json += "{"; - uint32 n = db.GetNumberOfChildren(); - for (uint32 i = 0u; i < n; i++) { - const char8 *name = db.GetChildName(i); - json += "\""; - EscapeJson(name, json); - json += "\": "; - if (db.MoveRelative(name)) { - ConfigurationDatabase child; - db.Copy(child); - JsonifyDatabase(child, json); - db.MoveToAncestor(1u); - } else { - AnyType at = db.GetType(name); - char8 buf[1024]; - AnyType st(CharString, 0u, buf); - st.SetNumberOfElements(0, 1024); - if (TypeConvert(st, at)) { - json += "\""; - EscapeJson(buf, json); - json += "\""; - } else { - json += "null"; - } - } - if (i < n - 1) - json += ", "; - } - json += "}"; + StreamString &json, + StreamString indent) { + JsonifyDatabaseInternal(db, json); } void DebugService::ServeConfig(BasicTCPSocket *client) { if (client == NULL_PTR(BasicTCPSocket *)) return; StreamString json; - fullConfig.MoveToRoot(); - JsonifyDatabase(fullConfig, json); + Reference ref = ObjectRegistryDatabase::Instance(); + Vec visited; + JsonifyObjectSafe(ref, json, 0, visited); json += "\nOK CONFIG\n"; uint32 s = json.Size(); (void)client->Write(json.Buffer(), s); @@ -1009,7 +1305,8 @@ uint32 DebugService::ExportTree(ReferenceContainer *container, (void)ds->GetSignalNumberOfElements(j, elems); StreamString signalFullPath; - signalFullPath.Printf("%s.%s", currentPath.Buffer(), sname.Buffer()); + signalFullPath.Printf("%s.%s", currentPath.Buffer(), + sname.Buffer()); bool traceable = false; bool forcable = false; (void)IsInstrumented(signalFullPath.Buffer(), traceable, forcable); @@ -1099,6 +1396,138 @@ uint32 DebugService::ExportTree(ReferenceContainer *container, return validCount; } +void DebugService::ListNodes(const char8 *path, BasicTCPSocket *client) { + if (!client) + return; + Reference ref = + (path == NULL_PTR(const char8 *) || StringHelper::Length(path) == 0 || + StringHelper::Compare(path, "/") == 0) + ? ObjectRegistryDatabase::Instance() + : ObjectRegistryDatabase::Instance()->Find(path); + if (ref.IsValid()) { + StreamString out; + out.Printf("Nodes under %s:\n", path ? path : "/"); + ReferenceContainer *container = + dynamic_cast(ref.operator->()); + if (container) { + for (uint32 i = 0; i < container->Size(); i++) { + Reference child = container->Get(i); + if (child.IsValid()) + out.Printf(" %s [%s]\n", child->GetName(), + child->GetClassProperties()->GetName()); + } + } + const char *okMsg = "OK LS\n"; + out += okMsg; + uint32 s = out.Size(); + (void)client->Write(out.Buffer(), s); + } else { + const char *msg = "ERROR: Path not found\n"; + uint32 s = StringHelper::Length(msg); + (void)client->Write(msg, s); + } +} + +void DebugService::Discover(BasicTCPSocket *client) { + if (client) { + mutex.FastLock(); + // Automatically add all state machines and realtime application states if + // not already there + DiscoverStatesRecursive(ObjectRegistryDatabase::Instance(), + monitoredStates); + + // Force immediate state push on next poll cycle + for (uint32 i = 0u; i < monitoredStates.Size(); i++) { + monitoredStates[i].lastState = ""; + } + + StreamString header = "{\n \"Signals\": [\n"; + uint32 s = header.Size(); + (void)client->Write(header.Buffer(), s); + uint32 total = 0; + for (uint32 i = 0; i < aliases.Size(); i++) { + if (total > 0) { + uint32 commaSize = 2; + (void)client->Write(",\n", commaSize); + } + StreamString line; + DebugSignalInfo *sig = signals[aliases[i].signalIndex]; + const char8 *typeName = + TypeDescriptor::GetTypeNameFromTypeDescriptor(sig->type); + line.Printf(" {\"name\": \"%s\", \"id\": %d, \"type\": \"%s\", " + "\"dimensions\": %u, \"elements\": %u", + aliases[i].name.Buffer(), sig->internalID, + typeName ? typeName : "Unknown", sig->numberOfDimensions, + sig->numberOfElements); + EnrichWithConfig(aliases[i].name.Buffer(), line); + line += "}"; + s = line.Size(); + (void)client->Write(line.Buffer(), s); + total++; + } + + // Export monitored signals not already in aliases + for (uint32 i = 0; i < monitoredSignals.Size(); i++) { + bool found = false; + for (uint32 j = 0; j < aliases.Size(); j++) { + if (aliases[j].name == monitoredSignals[i].path) { + found = true; + break; + } + } + if (!found) { + if (total > 0) { + uint32 commaSize = 2; + (void)client->Write(",\n", commaSize); + } + StreamString line; + const char8 *typeName = TypeDescriptor::GetTypeNameFromTypeDescriptor( + monitoredSignals[i].dataSource->GetSignalType( + monitoredSignals[i].signalIdx)); + + uint8 dims = 0; + uint32 elems = 1; + (void)monitoredSignals[i].dataSource->GetSignalNumberOfDimensions( + monitoredSignals[i].signalIdx, dims); + (void)monitoredSignals[i].dataSource->GetSignalNumberOfElements( + monitoredSignals[i].signalIdx, elems); + + line.Printf(" {\"name\": \"%s\", \"id\": %u, \"type\": \"%s\", " + "\"dimensions\": %u, \"elements\": %u", + monitoredSignals[i].path.Buffer(), + monitoredSignals[i].internalID, + typeName ? typeName : "Unknown", dims, elems); + EnrichWithConfig(monitoredSignals[i].path.Buffer(), line); + line += "}"; + s = line.Size(); + (void)client->Write(line.Buffer(), s); + total++; + } + } + + // Export discovered states + for (uint32 i = 0; i < monitoredStates.Size(); i++) { + if (total > 0) { + uint32 commaSize = 2; + (void)client->Write(",\n", commaSize); + } + StreamString line; + line.Printf(" {\"name\": \"%s\", \"id\": %u, \"type\": \"string\", " + "\"is_state\": true}", + monitoredStates[i].path.Buffer(), + monitoredStates[i].internalID); + s = line.Size(); + (void)client->Write(line.Buffer(), s); + total++; + } + + mutex.FastUnLock(); + StreamString footer = " ]\n}\nOK DISCOVER\n"; + s = footer.Size(); + (void)client->Write(footer.Buffer(), s); + } +} + uint32 DebugService::ForceSignal(const char8 *name, const char8 *valueStr) { mutex.FastLock(); uint32 count = 0; @@ -1139,7 +1568,6 @@ uint32 DebugService::TraceSignal(const char8 *name, bool enable, mutex.FastLock(); uint32 count = 0; for (uint32 i = 0; i < aliases.Size(); i++) { - printf("%s\n", aliases[i].name.Buffer()); if (aliases[i].name == name || SuffixMatch(aliases[i].name.Buffer(), name)) { DebugSignalInfo *s = signals[aliases[i].signalIndex]; @@ -1150,7 +1578,7 @@ uint32 DebugService::TraceSignal(const char8 *name, bool enable, } } if (count == 0) { - printf(" signal %s not found\n", name); + REPORT_ERROR(ErrorManagement::Warning, " signal %s not found"); } UpdateBrokersActiveStatus(); mutex.FastUnLock(); @@ -1158,7 +1586,7 @@ uint32 DebugService::TraceSignal(const char8 *name, bool enable, } bool DebugService::IsInstrumented(const char8 *fullPath, bool &traceable, - bool &forcable) { + bool &forcable) { mutex.FastLock(); bool found = false; for (uint32 i = 0; i < aliases.Size(); i++) { @@ -1225,7 +1653,7 @@ uint32 DebugService::RegisterMonitorSignal(const char8 *path, uint32 periodMs) { // Use high-bit for polled signals to avoid conflict with brokered ones m.internalID = 0x80000000 | monitoredSignals.Size(); - + // Re-use existing ID if signal is also instrumented via broker for (uint32 i = 0; i < aliases.Size(); i++) { if (aliases[i].name == path || @@ -1260,104 +1688,4 @@ uint32 DebugService::UnmonitorSignal(const char8 *path) { return count; } -void DebugService::Discover(BasicTCPSocket *client) { - if (client) { - StreamString header = "{\n \"Signals\": [\n"; - uint32 s = header.Size(); - (void)client->Write(header.Buffer(), s); - mutex.FastLock(); - uint32 total = 0; - for (uint32 i = 0; i < aliases.Size(); i++) { - if (total > 0) { - uint32 commaSize = 2; - (void)client->Write(",\n", commaSize); - } - StreamString line; - DebugSignalInfo *sig = signals[aliases[i].signalIndex]; - const char8 *typeName = - TypeDescriptor::GetTypeNameFromTypeDescriptor(sig->type); - line.Printf(" {\"name\": \"%s\", \"id\": %d, \"type\": \"%s\", \"dimensions\": %u, \"elements\": %u", - aliases[i].name.Buffer(), sig->internalID, - typeName ? typeName : "Unknown", sig->numberOfDimensions, sig->numberOfElements); - EnrichWithConfig(aliases[i].name.Buffer(), line); - line += "}"; - s = line.Size(); - (void)client->Write(line.Buffer(), s); - total++; - } - - // Export monitored signals not already in aliases - for (uint32 i = 0; i < monitoredSignals.Size(); i++) { - bool found = false; - for (uint32 j = 0; j < aliases.Size(); j++) { - if (aliases[j].name == monitoredSignals[i].path) { - found = true; - break; - } - } - if (!found) { - if (total > 0) { - uint32 commaSize = 2; - (void)client->Write(",\n", commaSize); - } - StreamString line; - const char8 *typeName = TypeDescriptor::GetTypeNameFromTypeDescriptor( - monitoredSignals[i].dataSource->GetSignalType( - monitoredSignals[i].signalIdx)); - - uint8 dims = 0; - uint32 elems = 1; - (void)monitoredSignals[i].dataSource->GetSignalNumberOfDimensions(monitoredSignals[i].signalIdx, dims); - (void)monitoredSignals[i].dataSource->GetSignalNumberOfElements(monitoredSignals[i].signalIdx, elems); - - line.Printf(" {\"name\": \"%s\", \"id\": %u, \"type\": \"%s\", \"dimensions\": %u, \"elements\": %u", - monitoredSignals[i].path.Buffer(), - monitoredSignals[i].internalID, - typeName ? typeName : "Unknown", dims, elems); - EnrichWithConfig(monitoredSignals[i].path.Buffer(), line); - line += "}"; - s = line.Size(); - (void)client->Write(line.Buffer(), s); - total++; - } - } - mutex.FastUnLock(); - StreamString footer = " ]\n}\nOK DISCOVER\n"; - s = footer.Size(); - (void)client->Write(footer.Buffer(), s); - } -} - -void DebugService::ListNodes(const char8 *path, BasicTCPSocket *client) { - if (!client) - return; - Reference ref = - (path == NULL_PTR(const char8 *) || StringHelper::Length(path) == 0 || - StringHelper::Compare(path, "/") == 0) - ? ObjectRegistryDatabase::Instance() - : ObjectRegistryDatabase::Instance()->Find(path); - if (ref.IsValid()) { - StreamString out; - out.Printf("Nodes under %s:\n", path ? path : "/"); - ReferenceContainer *container = - dynamic_cast(ref.operator->()); - if (container) { - for (uint32 i = 0; i < container->Size(); i++) { - Reference child = container->Get(i); - if (child.IsValid()) - out.Printf(" %s [%s]\n", child->GetName(), - child->GetClassProperties()->GetName()); - } - } - const char *okMsg = "OK LS\n"; - out += okMsg; - uint32 s = out.Size(); - (void)client->Write(out.Buffer(), s); - } else { - const char *msg = "ERROR: Path not found\n"; - uint32 s = StringHelper::Length(msg); - (void)client->Write(msg, s); - } -} - } // namespace MARTe diff --git a/Source/Components/Interfaces/DebugService/DebugService.h b/Source/Components/Interfaces/DebugService/DebugService.h index 5cc6172..6eff74e 100644 --- a/Source/Components/Interfaces/DebugService/DebugService.h +++ b/Source/Components/Interfaces/DebugService/DebugService.h @@ -46,7 +46,8 @@ public: virtual bool Initialise(StructuredDataI &data); DebugSignalInfo *RegisterSignal(void *memoryAddress, TypeDescriptor type, - const char8 *name, uint8 numberOfDimensions = 0, + const char8 *name, + uint8 numberOfDimensions = 0, uint32 numberOfElements = 1); void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size, uint64 timestamp); @@ -85,6 +86,13 @@ public: StreamString path; }; + struct MonitoredState { + Reference obj; + uint32 internalID; + StreamString path; + StreamString lastState; + }; + uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs); uint32 UnmonitorSignal(const char8 *path); @@ -92,11 +100,13 @@ private: void HandleCommand(StreamString cmd, BasicTCPSocket *client); void UpdateBrokersActiveStatus(); - uint32 ExportTree(ReferenceContainer *container, StreamString &json, const char8 *pathPrefix); + uint32 ExportTree(ReferenceContainer *container, StreamString &json, + const char8 *pathPrefix); void PatchRegistry(); void EnrichWithConfig(const char8 *path, StreamString &json); - static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json); + static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json, + StreamString indent = ""); ErrorManagement::ErrorType Server(ExecutionInfo &info); ErrorManagement::ErrorType Streamer(ExecutionInfo &info); @@ -143,6 +153,7 @@ private: Vec aliases; Vec brokers; Vec monitoredSignals; + Vec monitoredStates; FastPollingMutexSem mutex; TraceRingBuffer traceBuffer; diff --git a/Source/Components/Interfaces/DebugService/Makefile.inc b/Source/Components/Interfaces/DebugService/Makefile.inc index 6083f09..0c7c324 100644 --- a/Source/Components/Interfaces/DebugService/Makefile.inc +++ b/Source/Components/Interfaces/DebugService/Makefile.inc @@ -42,6 +42,7 @@ INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L4Messages INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L4Logger INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L4Configuration INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L5GAMs +INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L4StateMachine INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L1Portability INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L3Services INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L4Messages @@ -49,6 +50,8 @@ INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L4LoggerService INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L5GAMs INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L1Portability INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L3Streams +INCLUDES += -I$(MARTe2_Components_DIR)/Source/Components/DataSources/RealTimeThreadSynchronisation + all: $(OBJS) $(SUBPROJ) \ $(BUILD_DIR)/DebugService$(LIBEXT) \ diff --git a/Test/Configurations/debug_test.cfg b/Test/Configurations/debug_test.cfg index 139d4bc..6d33a29 100644 --- a/Test/Configurations/debug_test.cfg +++ b/Test/Configurations/debug_test.cfg @@ -5,68 +5,23 @@ +GAM1 = { Class = IOGAM InputSignals = { - Counter = { - DataSource = Timer - Type = uint32 - Frequency = 1000 - } - Time = { - DataSource = Timer - Type = uint32 - } + Counter = { DataSource = Timer Type = uint32 Frequency = 1000 } + Time = { DataSource = Timer Type = uint32 } } OutputSignals = { - Counter = { - DataSource = SyncDB - Type = uint32 - } - Time = { - DataSource = DDB - Type = uint32 - } + Counter = { DataSource = SyncDB Type = uint32 } + Time = { DataSource = DDB Type = uint32 } } } +GAM2 = { Class = IOGAM InputSignals = { - Counter = { - DataSource = TimerSlow - Frequency = 1 - } - Time = { - DataSource = TimerSlow - } + Counter = { DataSource = SyncDB Type = uint32 Samples = 1 } } OutputSignals = { - Counter = { - Type = uint32 - DataSource = Logger - } - Time = { - Type = uint32 - DataSource = Logger - } + Counter = { DataSource = Logger Type = uint32 } } } - +GAM3 = { - Class = IOGAM - InputSignals = { - Counter = { - Frequency = 1 - Samples = 100 - Type = uint32 - DataSource = SyncDB - } - } - OutputSignals = { - Counter = { - DataSource = DDB3 - NumberOfElements = 100 - Type = uint32 - } - } - - } } +Data = { Class = ReferenceContainer @@ -75,57 +30,31 @@ Class = RealTimeThreadSynchronisation Timeout = 200 Signals = { - Counter = { - Type = uint32 - } + Counter = { Type = uint32 } } } +Timer = { Class = LinuxTimer + SleepTime = 1000 Signals = { - Counter = { - Type = uint32 - } - Time = { - Type = uint32 - } - } - } - +TimerSlow = { - Class = LinuxTimer - Signals = { - Counter = { - Type = uint32 - } - Time = { - Type = uint32 - } + Counter = { Type = uint32 } + Time = { Type = uint32 } } } +Logger = { Class = LoggerDataSource Signals = { - CounterCopy = { - Type = uint32 - } - TimeCopy = { - Type = uint32 - } + CounterCopy = { Type = uint32 } } } +DDB = { AllowNoProducer = 1 Class = GAMDataSource Signals = { - Counter= { - Type = uint32 - } + Counter = { Type = uint32 } + Time = { Type = uint32 } } } - +DDB3 = { - AllowNoProducer = 1 - Class = GAMDataSource - } +DAMS = { Class = TimingDataSource } @@ -144,10 +73,6 @@ Class = RealTimeThread Functions = {GAM2} } - +Thread3 = { - Class = RealTimeThread - Functions = {GAM3} - } } } } diff --git a/Test/Integration/Makefile.inc b/Test/Integration/Makefile.inc index 32bf5da..815a25b 100644 --- a/Test/Integration/Makefile.inc +++ b/Test/Integration/Makefile.inc @@ -31,9 +31,11 @@ INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L1Portability INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L3Streams INCLUDES += -I$(MARTe2_Components_DIR)/Source/Components/GAMs/IOGAM INCLUDES += -I$(MARTe2_Components_DIR)/Source/Components/DataSources/LinuxTimer +INCLUDES += -I$(MARTe2_Components_DIR)/Source/Components/DataSources/RealTimeThreadSynchronisation LIBRARIES += -L$(MARTe2_DIR)/Build/$(TARGET)/Core -lMARTe2 LIBRARIES += -L$(MARTe2_Components_DIR)/Build/$(TARGET)/Components/DataSources/LinuxTimer -lLinuxTimer +LIBRARIES += -L$(MARTe2_Components_DIR)/Build/$(TARGET)/Components/DataSources/RealTimeThreadSynchronisation -lRealTimeThreadSynchronisation LIBRARIES += -L$(MARTe2_Components_DIR)/Build/$(TARGET)/Components/GAMs/IOGAM -lIOGAM LIBRARIES += -L$(ROOT_DIR)/Build/$(TARGET)/Components/Interfaces/DebugService -lDebugService LIBRARIES += -L$(ROOT_DIR)/Build/$(TARGET)/Components/Interfaces/TCPLogger -lTcpLogger diff --git a/Test/UnitTests/Makefile.inc b/Test/UnitTests/Makefile.inc index 845fc78..7b9546e 100644 --- a/Test/UnitTests/Makefile.inc +++ b/Test/UnitTests/Makefile.inc @@ -28,8 +28,10 @@ INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L4LoggerService INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L5GAMs INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L1Portability INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L3Streams +INCLUDES += -I$(MARTe2_Components_DIR)/Source/Components/DataSources/RealTimeThreadSynchronisation LIBRARIES += -L$(MARTe2_DIR)/Build/$(TARGET)/Core -lMARTe2 +LIBRARIES += -L$(MARTe2_Components_DIR)/Build/$(TARGET)/Components/DataSources/RealTimeThreadSynchronisation -lRealTimeThreadSynchronisation LIBRARIES += -L$(ROOT_DIR)/Build/$(TARGET)/Components/Interfaces/DebugService -lDebugService LIBRARIES += -L$(ROOT_DIR)/Build/$(TARGET)/Components/Interfaces/TCPLogger -lTcpLogger diff --git a/Tools/gui_client/src/main.rs b/Tools/gui_client/src/main.rs index 18fb21c..25ec971 100644 --- a/Tools/gui_client/src/main.rs +++ b/Tools/gui_client/src/main.rs @@ -1,222 +1,19 @@ -use arrow::array::Float64Array; -use arrow::datatypes::{DataType, Field, Schema}; -use arrow::record_batch::RecordBatch; +mod models; +mod workers; + +use crate::models::*; +use crate::workers::*; use chrono::Local; use crossbeam_channel::{unbounded, Receiver, Sender}; use eframe::egui; -use egui_plot::{Line, LineStyle, MarkerShape, Plot, PlotBounds, PlotPoints, VLine}; -use once_cell::sync::Lazy; -use parquet::arrow::arrow_writer::ArrowWriter; -use parquet::file::properties::WriterProperties; -use regex::Regex; -use rfd::FileDialog; -use serde::{Deserialize, Serialize}; -use socket2::{Domain, Protocol, Socket, Type}; +use egui_plot::{Line, Plot, PlotPoints}; use std::collections::{HashMap, VecDeque}; -use std::fs::File; -use std::io::{BufRead, BufReader, Write}; -use std::net::{TcpStream, UdpSocket}; use std::sync::{Arc, Mutex}; use std::thread; +use rfd::FileDialog; +use regex::Regex; -static BASE_TELEM_TS: Lazy>> = Lazy::new(|| Mutex::new(None)); - -// --- Models --- - -#[derive(Serialize, Deserialize, Clone, Debug)] -struct Signal { - name: String, - id: u32, - #[serde(rename = "type")] - sig_type: String, - #[serde(default)] - dimensions: u8, - #[serde(default = "default_elements")] - elements: u32, -} - -fn default_elements() -> u32 { 1 } - -#[derive(Deserialize)] -struct DiscoverResponse { - #[serde(rename = "Signals")] - signals: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -struct TreeItem { - #[serde(rename = "Name")] - name: String, - #[serde(rename = "Class")] - class: String, - #[serde(rename = "Children")] - children: Option>, - #[serde(rename = "Type")] - sig_type: Option, - #[serde(rename = "Dimensions")] - dimensions: Option, - #[serde(rename = "Elements")] - elements: Option, - #[serde(rename = "IsTraceable")] - is_traceable: Option, - #[serde(rename = "IsForcable")] - is_forcable: Option, -} - -#[derive(Clone)] -struct LogEntry { - time: String, - level: String, - message: String, -} - -struct TraceData { - values: VecDeque<[f64; 2]>, - last_value: f64, - recording_tx: Option>, - recording_path: Option, - is_monitored: bool, -} - -struct SignalMetadata { - names: Vec, - sig_type: String, - dimensions: u8, - elements: u32, -} - -#[derive(Clone)] -struct ConnectionConfig { - ip: String, - tcp_port: String, - udp_port: String, - log_port: String, - version: u64, -} - -#[derive(Clone, Copy, PartialEq, Debug)] -enum PlotType { - Normal, - LogicAnalyzer, -} - -#[derive(Clone, Copy, PartialEq, Debug)] -enum AcquisitionMode { - FreeRun, - Triggered, -} - -#[derive(Clone, Copy, PartialEq, Debug)] -enum TriggerEdge { - Rising, - Falling, - Both, -} - -#[derive(Clone, Copy, PartialEq, Debug)] -enum TriggerType { - Single, - Continuous, -} - -#[derive(Clone, Copy, PartialEq, Debug)] -enum MarkerType { - None, - Circle, - Square, -} - -impl MarkerType { - fn to_shape(&self) -> Option { - match self { - MarkerType::None => None, - MarkerType::Circle => Some(MarkerShape::Circle), - MarkerType::Square => Some(MarkerShape::Square), - } - } -} - -#[derive(Clone)] -struct SignalPlotConfig { - source_name: String, - label: String, - unit: String, - color: egui::Color32, - line_style: LineStyle, - marker_type: MarkerType, - gain: f64, - offset: f64, -} - -struct PlotInstance { - id: String, - plot_type: PlotType, - signals: Vec, - auto_bounds: bool, -} - -enum InternalEvent { - Log(LogEntry), - Discovery(Vec), - Tree(TreeItem), - CommandResponse(String), - NodeInfo(String), - Connected, - Disconnected, - InternalLog(String), - TraceRequested(String, bool), // Name, IsMonitored - ClearTrace(String), - UdpStats(u64), - UdpDropped(u32), - RecordPathChosen(String, String), // SignalName, FilePath - RecordingError(String, String), // SignalName, ErrorMessage - TelemMatched(u32), // Signal ID - ServiceConfig { udp_port: String, log_port: String }, -} - -// --- App State --- - -struct ForcingDialog { - signal_path: String, - value: String, -} - -struct MonitorDialog { - signal_path: String, - period_ms: String, -} - -struct MessageDialog { - destination: String, - function: String, - payload: String, - expect_reply: bool, -} - -struct LogFilters { - show_debug: bool, - show_info: bool, - show_warning: bool, - show_error: bool, - paused: bool, - content_regex: String, -} - -struct ScopeSettings { - enabled: bool, - window_ms: f64, - mode: AcquisitionMode, - paused: bool, - trigger_type: TriggerType, - trigger_source: String, - trigger_edge: TriggerEdge, - trigger_threshold: f64, - pre_trigger_percent: f64, - trigger_active: bool, - last_trigger_time: f64, - is_armed: bool, -} - +#[allow(dead_code)] struct MarteDebugApp { connected: bool, is_breaking: bool, @@ -229,12 +26,13 @@ struct MarteDebugApp { forced_signals: HashMap, logs: VecDeque, log_filters: LogFilters, + active_bottom_tab: BottomTab, + app_states: HashMap, // ObjectPath -> StateName show_left_panel: bool, show_right_panel: bool, show_bottom_panel: bool, selected_node: String, node_info: String, - udp_packets: u64, udp_dropped: u64, telem_match_count: HashMap, forcing_dialog: Option, @@ -246,6 +44,11 @@ struct MarteDebugApp { internal_tx: Sender, shared_x_range: Option<[f64; 2]>, scope: ScopeSettings, + active_main_tab: MainTab, + full_config: Option, + raw_config_str: String, + state_configs: HashMap, + bottom_panel_height: f32, } impl MarteDebugApp { @@ -265,17 +68,20 @@ impl MarteDebugApp { let traced_signals = Arc::new(Mutex::new(HashMap::new())); let id_to_meta_clone = id_to_meta.clone(); let traced_signals_clone = traced_signals.clone(); + let shared_config_cmd = shared_config.clone(); - let shared_config_log = shared_config.clone(); - let shared_config_udp = shared_config.clone(); let tx_events_c = tx_events.clone(); thread::spawn(move || { tcp_command_worker(shared_config_cmd, rx_cmd_internal, tx_events_c); }); + + let shared_config_log = shared_config.clone(); let tx_events_log = tx_events.clone(); thread::spawn(move || { tcp_log_worker(shared_config_log, tx_events_log); }); + + let shared_config_udp = shared_config.clone(); let tx_events_udp = tx_events.clone(); thread::spawn(move || { udp_worker( @@ -310,12 +116,13 @@ impl MarteDebugApp { paused: false, content_regex: "".to_string(), }, + active_bottom_tab: BottomTab::Logs, + app_states: HashMap::new(), show_left_panel: true, show_right_panel: true, show_bottom_panel: true, selected_node: "".to_string(), node_info: "".to_string(), - udp_packets: 0, udp_dropped: 0, telem_match_count: HashMap::new(), forcing_dialog: None, @@ -340,6 +147,11 @@ impl MarteDebugApp { last_trigger_time: 0.0, is_armed: true, }, + active_main_tab: MainTab::Plots, + full_config: None, + raw_config_str: "".to_string(), + state_configs: HashMap::new(), + bottom_panel_height: 250.0, } } @@ -414,11 +226,7 @@ impl MarteDebugApp { if let Some(tree) = &self.app_tree { fn collect(item: &TreeItem, path: String, objects: &mut Vec) { let current_path = if path.is_empty() { - if item.name == "Root" { - "".to_string() - } else { - item.name.clone() - } + if item.name == "Root" { "".to_string() } else { item.name.clone() } } else { format!("{}.{}", path, item.name) }; @@ -437,19 +245,72 @@ impl MarteDebugApp { objects } + fn marte_config_to_string(val: &serde_json::Value, indent: usize) -> String { + let mut s = String::new(); + let pad = " ".repeat(indent); + if let Some(obj) = val.as_object() { + let mut keys: Vec<_> = obj.keys().collect(); + keys.sort_by(|a, b| { + if *a == "Class" { return std::cmp::Ordering::Less; } + if *b == "Class" { return std::cmp::Ordering::Greater; } + a.cmp(b) + }); + + for key in keys { + if key == "Name" && indent > 0 { continue; } // Usually name is in the parent key prefix + let v = obj.get(key).unwrap(); + + let mut display_key = key.clone(); + if (key.parse::().is_ok() || key == "unnamed") && v.is_object() { + if let Some(name) = v.get("Name").and_then(|n| n.as_str()) { + display_key = format!("+{}", name); + } + } + + if display_key.starts_with('+') { + s.push_str(&format!("{}{} = ", pad, display_key)); + s.push_str(&Self::marte_config_to_string(v, indent + 1)); + } else if v.is_object() { + s.push_str(&format!("{}{} = ", pad, display_key)); + s.push_str(&Self::marte_config_to_string(v, indent + 1)); + } else { + let val_str = v.as_str().unwrap_or(&v.to_string()).to_string(); + if !val_str.is_empty() { + s.push_str(&format!("{}{} = {}\n", pad, display_key, val_str)); + } + } + } + } + format!("{{\n{}{}}}\n", s, " ".repeat(indent.saturating_sub(1))) + } + + fn get_config_at_path<'a>(config: &'a serde_json::Value, path: &str) -> Option<&'a serde_json::Value> { + let mut current = config; + for (i, part) in path.split('.').enumerate() { + if part.is_empty() { continue; } + if let Some(next) = Self::get_cfg(current, part) { + current = next; + } else if i == 0 { + // If it's the first part, it might be the root itself (exported by ORD) + // or we are ALREADY inside it. But usually ORD export includes the root name. + // Let's just return None if not found. + return None; + } else { + return None; + } + } + Some(current) + } + + fn get_cfg<'a>(val: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> { + val.get(key).or_else(|| val.get(format!("+{}", key))) + } + fn render_tree(&mut self, ui: &mut egui::Ui, item: &TreeItem, path: String) { let current_path = if path.is_empty() { - if item.name == "Root" { - "".to_string() - } else { - item.name.clone() - } + if item.name == "Root" { "".to_string() } else { item.name.clone() } } else { - if path.is_empty() { - item.name.clone() - } else { - format!("{}.{}", path, item.name) - } + format!("{}.{}", path, item.name) }; let label = if item.class == "Signal" { format!("📈 {}", item.name) @@ -462,10 +323,7 @@ impl MarteDebugApp { header.show(ui, |ui| { ui.horizontal(|ui| { if !current_path.is_empty() { - if ui - .selectable_label(self.selected_node == current_path, "ℹ Info") - .clicked() - { + if ui.selectable_label(self.selected_node == current_path, "ℹ Info").clicked() { self.selected_node = current_path.clone(); let _ = self.tx_cmd.send(format!("INFO {}", current_path)); } @@ -477,13 +335,7 @@ impl MarteDebugApp { }); } else { ui.horizontal(|ui| { - if ui - .selectable_label( - self.selected_node == current_path, - format!("{} [{}]", label, item.class), - ) - .clicked() - { + if ui.selectable_label(self.selected_node == current_path, format!("{} [{}]", label, item.class)).clicked() { self.selected_node = current_path.clone(); let _ = self.tx_cmd.send(format!("INFO {}", current_path)); } @@ -507,7 +359,6 @@ impl MarteDebugApp { signal_path: current_path.clone(), period_ms: "100".to_string(), }); - // Note: internal monitoring logic will handle individual elements via naming convention let _ = self.internal_tx.send(InternalEvent::TraceRequested(elem_path.clone(), true)); } } @@ -520,9 +371,7 @@ impl MarteDebugApp { if traceable && ui.button("Trace").clicked() { let _ = self.tx_cmd.send(format!("TRACE {} 1", current_path)); - let _ = self - .internal_tx - .send(InternalEvent::TraceRequested(current_path.clone(), false)); + let _ = self.internal_tx.send(InternalEvent::TraceRequested(current_path.clone(), false)); } if item.class == "Signal" { if ui.button("Monitor").clicked() { @@ -545,462 +394,6 @@ impl MarteDebugApp { } } -fn tcp_command_worker( - shared_config: Arc>, - rx_cmd: Receiver, - tx_events: Sender, -) { - let mut current_version = 0; - let mut current_addr = String::new(); - loop { - { - let config = shared_config.lock().unwrap(); - if config.version != current_version { - current_version = config.version; - current_addr = format!("{}:{}", config.ip, config.tcp_port); - } - } - if current_addr.is_empty() || current_addr.starts_with(":") { - thread::sleep(std::time::Duration::from_secs(1)); - continue; - } - if let Ok(mut stream) = TcpStream::connect(¤t_addr) { - let _ = stream.set_nodelay(true); - let mut reader = BufReader::new(stream.try_clone().unwrap()); - let _ = tx_events.send(InternalEvent::Connected); - let stop_flag = Arc::new(Mutex::new(false)); - let stop_flag_reader = stop_flag.clone(); - let tx_events_inner = tx_events.clone(); - thread::spawn(move || { - let mut line = String::new(); - let mut json_acc = String::new(); - let mut in_json = false; - while reader.read_line(&mut line).is_ok() { - if *stop_flag_reader.lock().unwrap() { - break; - } - let trimmed = line.trim(); - if trimmed.is_empty() { - line.clear(); - continue; - } - - if !in_json && trimmed.starts_with("{") { - in_json = true; - json_acc.clear(); - } - - if in_json { - json_acc.push_str(trimmed); - if trimmed.contains("OK DISCOVER") { - in_json = false; - let json_clean = - json_acc.split("OK DISCOVER").next().unwrap_or("").trim(); - match serde_json::from_str::(json_clean) { - Ok(resp) => { - let _ = tx_events_inner - .send(InternalEvent::Discovery(resp.signals)); - } - Err(e) => { - let _ = - tx_events_inner.send(InternalEvent::InternalLog(format!( - "Discovery JSON Error: {} | Payload: {}", - e, json_clean - ))); - } - } - json_acc.clear(); - } else if trimmed.contains("OK TREE") { - in_json = false; - let json_clean = json_acc.split("OK TREE").next().unwrap_or("").trim(); - match serde_json::from_str::(json_clean) { - Ok(resp) => { - let _ = tx_events_inner.send(InternalEvent::Tree(resp)); - } - Err(e) => { - let _ = tx_events_inner.send(InternalEvent::InternalLog( - format!("Tree JSON Error: {}", e), - )); - } - } - json_acc.clear(); - } else if trimmed.contains("OK INFO") { - in_json = false; - let json_clean = json_acc.split("OK INFO").next().unwrap_or("").trim(); - let _ = tx_events_inner - .send(InternalEvent::NodeInfo(json_clean.to_string())); - json_acc.clear(); - } - } else { - if trimmed.starts_with("OK SERVICE_INFO") { - // OK SERVICE_INFO TCP_CTRL:8110 UDP_STREAM:8111 TCP_LOG:8082 STATE:RUNNING - let parts: Vec<&str> = trimmed.split_whitespace().collect(); - let mut udp = String::new(); - let mut log = String::new(); - for p in parts { - if p.starts_with("UDP_STREAM:") { - udp = p.split(':').nth(1).unwrap_or("").to_string(); - } - if p.starts_with("TCP_LOG:") { - log = p.split(':').nth(1).unwrap_or("").to_string(); - } - } - if !udp.is_empty() || !log.is_empty() { - let _ = tx_events_inner.send(InternalEvent::ServiceConfig { - udp_port: udp, - log_port: log, - }); - } - } - let _ = tx_events_inner - .send(InternalEvent::CommandResponse(trimmed.to_string())); - } - line.clear(); - } - }); - while let Ok(cmd) = rx_cmd.recv() { - { - let config = shared_config.lock().unwrap(); - if config.version != current_version { - *stop_flag.lock().unwrap() = true; - let _ = tx_events.send(InternalEvent::Disconnected); - break; - } - } - if stream.write_all(format!("{}\n", cmd).as_bytes()).is_err() { - let _ = tx_events.send(InternalEvent::Disconnected); - break; - } - // Small delay to allow server to process and send response - thread::sleep(std::time::Duration::from_millis(100)); - } - let _ = tx_events.send(InternalEvent::Disconnected); - } - thread::sleep(std::time::Duration::from_secs(2)); - } -} - -fn tcp_log_worker(shared_config: Arc>, tx_events: Sender) { - let mut current_version = 0; - let mut current_addr = String::new(); - loop { - { - let config = shared_config.lock().unwrap(); - if config.version != current_version { - current_version = config.version; - current_addr = format!("{}:{}", config.ip, config.log_port); - } - } - if current_addr.is_empty() || current_addr.starts_with(":") { - thread::sleep(std::time::Duration::from_secs(1)); - continue; - } - if let Ok(stream) = TcpStream::connect(¤t_addr) { - let mut reader = BufReader::new(stream); - let mut line = String::new(); - while reader.read_line(&mut line).is_ok() { - if shared_config.lock().unwrap().version != current_version { - break; - } - let trimmed = line.trim(); - if trimmed.starts_with("LOG ") { - let parts: Vec<&str> = trimmed[4..].splitn(2, ' ').collect(); - if parts.len() == 2 { - let _ = tx_events.send(InternalEvent::Log(LogEntry { - time: Local::now().format("%H:%M:%S%.3f").to_string(), - level: parts[0].to_string(), - message: parts[1].to_string(), - })); - } - } - line.clear(); - } - } - thread::sleep(std::time::Duration::from_secs(2)); - } -} - -fn recording_worker( - rx: Receiver<[f64; 2]>, - path: String, - signal_name: String, - tx_events: Sender, -) { - let file = match File::create(&path) { - Ok(f) => f, - Err(e) => { - let _ = tx_events.send(InternalEvent::RecordingError( - signal_name, - format!("File Error: {}", e), - )); - return; - } - }; - let schema = Arc::new(Schema::new(vec![ - Field::new("timestamp", DataType::Float64, false), - Field::new("value", DataType::Float64, false), - ])); - let mut writer = match ArrowWriter::try_new( - file, - schema.clone(), - Some(WriterProperties::builder().build()), - ) { - Ok(w) => w, - Err(e) => { - let _ = tx_events.send(InternalEvent::RecordingError( - signal_name, - format!("Parquet Error: {}", e), - )); - return; - } - }; - let (mut t_acc, mut v_acc) = (Vec::with_capacity(1000), Vec::with_capacity(1000)); - while let Ok([t, v]) = rx.recv() { - t_acc.push(t); - v_acc.push(v); - if t_acc.len() >= 1000 { - let batch = RecordBatch::try_new( - schema.clone(), - vec![ - Arc::new(Float64Array::from(t_acc.clone())), - Arc::new(Float64Array::from(v_acc.clone())), - ], - ) - .unwrap(); - let _ = writer.write(&batch); - t_acc.clear(); - v_acc.clear(); - } - } - if !t_acc.is_empty() { - let batch = RecordBatch::try_new( - schema.clone(), - vec![ - Arc::new(Float64Array::from(t_acc)), - Arc::new(Float64Array::from(v_acc)), - ], - ) - .unwrap(); - let _ = writer.write(&batch); - } - let _ = writer.close(); -} - -fn udp_worker( - shared_config: Arc>, - id_to_meta: Arc>>, - traced_data: Arc>>, - tx_events: Sender, -) { - let mut current_version = 0; - let mut socket: Option = None; - let mut last_seq: Option = None; - let mut last_warning_time = std::time::Instant::now(); - - loop { - let (ver, port) = { - let config = shared_config.lock().unwrap(); - (config.version, config.udp_port.clone()) - }; - if ver != current_version || socket.is_none() { - current_version = ver; - { - let mut base = BASE_TELEM_TS.lock().unwrap(); - *base = None; - } - if port.is_empty() { - socket = None; - continue; - } - let port_num: u16 = port.parse().unwrap_or(8081); - let s = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP)).ok(); - let mut bound = false; - if let Some(sock) = s { - let _ = sock.set_reuse_address(true); - #[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))] - let _ = sock.set_reuse_port(true); - let _ = sock.set_recv_buffer_size(10 * 1024 * 1024); - let addr = format!("0.0.0.0:{}", port_num) - .parse::() - .unwrap(); - if sock.bind(&addr.into()).is_ok() { - socket = Some(sock.into()); - bound = true; - } - } - if !bound { - thread::sleep(std::time::Duration::from_secs(5)); - continue; - } - let _ = socket - .as_ref() - .unwrap() - .set_read_timeout(Some(std::time::Duration::from_millis(500))); - last_seq = None; - } - let s = if let Some(sock) = socket.as_ref() { - sock - } else { - thread::sleep(std::time::Duration::from_secs(1)); - continue; - }; - let mut buf = [0u8; 4096]; - let mut total_packets = 0u64; - loop { - if shared_config.lock().unwrap().version != current_version { - break; - } - if let Ok(n) = s.recv(&mut buf) { - total_packets += 1; - if (total_packets % 500) == 0 { - let _ = tx_events.send(InternalEvent::UdpStats(total_packets)); - } - if n < 20 { - continue; - } - if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0xDA7A57AD { - continue; - } - let seq = u32::from_le_bytes(buf[4..8].try_into().unwrap()); - if let Some(last) = last_seq { - if seq != last + 1 && seq > last { - let _ = tx_events.send(InternalEvent::UdpDropped(seq - last - 1)); - } - } - last_seq = Some(seq); - let count = u32::from_le_bytes(buf[16..20].try_into().unwrap()); - - let mut offset = 20; - let mut local_updates: HashMap> = HashMap::new(); - let mut last_values: HashMap = HashMap::new(); - let metas = id_to_meta.lock().unwrap(); - - if metas.is_empty() && count > 0 && last_warning_time.elapsed().as_secs() > 5 { - let _ = tx_events.send(InternalEvent::InternalLog( - "UDP received but Metadata empty. Still discovering?".to_string(), - )); - last_warning_time = std::time::Instant::now(); - } - - for _ in 0..count { - if offset + 16 > n { - break; - } - let id = u32::from_le_bytes(buf[offset..offset + 4].try_into().unwrap()); - let ts_raw = - u64::from_le_bytes(buf[offset + 4..offset + 12].try_into().unwrap()); - let size = - u32::from_le_bytes(buf[offset + 12..offset + 16].try_into().unwrap()); - offset += 16; - - if offset + size as usize > n { - break; - } - let data_slice = &buf[offset..offset + size as usize]; - - let mut base_ts_guard = BASE_TELEM_TS.lock().unwrap(); - if base_ts_guard.is_none() { - *base_ts_guard = Some(ts_raw); - } - - let base = base_ts_guard.unwrap(); - let ts_s = if ts_raw >= base { - (ts_raw - base) as f64 / 1000000.0 - } else { - 0.0 // Avoid huge jitter wrap-around - }; - drop(base_ts_guard); - - if let Some(meta) = metas.get(&id) { - let _ = tx_events.send(InternalEvent::TelemMatched(id)); - let t = meta.sig_type.as_str(); - let type_size = if meta.elements > 0 { size / meta.elements } else { size }; - - for i in 0..meta.elements { - let elem_offset = (i * type_size) as usize; - if elem_offset + type_size as usize > data_slice.len() { break; } - let elem_data = &data_slice[elem_offset..elem_offset + type_size as usize]; - - let val = match type_size { - 1 => { - if t.contains('u') { - elem_data[0] as f64 - } else { - (elem_data[0] as i8) as f64 - } - } - 2 => { - let b = elem_data[0..2].try_into().unwrap(); - if t.contains('u') { - u16::from_le_bytes(b) as f64 - } else { - i16::from_le_bytes(b) as f64 - } - } - 4 => { - let b = elem_data[0..4].try_into().unwrap(); - if t.contains("float") { - f32::from_le_bytes(b) as f64 - } else if t.contains('u') { - u32::from_le_bytes(b) as f64 - } else { - i32::from_le_bytes(b) as f64 - } - } - 8 => { - let b = elem_data[0..8].try_into().unwrap(); - if t.contains("float") { - f64::from_le_bytes(b) - } else if t.contains('u') { - u64::from_le_bytes(b) as f64 - } else { - i64::from_le_bytes(b) as f64 - } - } - _ => 0.0, - }; - - for name in &meta.names { - let target_name = if meta.elements > 1 { - format!("{}[{}]", name, i) - } else { - name.clone() - }; - local_updates - .entry(target_name.clone()) - .or_default() - .push([ts_s, val]); - last_values.insert(target_name, val); - } - } - } - offset += size as usize; - } - drop(metas); - if !local_updates.is_empty() { - let mut data_map = traced_data.lock().unwrap(); - for (name, new_points) in local_updates { - if let Some(entry) = data_map.get_mut(&name) { - for point in new_points { - entry.values.push_back(point); - if let Some(tx) = &entry.recording_tx { - let _ = tx.send(point); - } - } - if let Some(lv) = last_values.get(&name) { - entry.last_value = *lv; - } - while entry.values.len() > 100000 { - entry.values.pop_front(); - } - } - } - } - } - } - } -} - impl eframe::App for MarteDebugApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { while let Ok(event) = self.rx_events.try_recv() { @@ -1008,20 +401,33 @@ impl eframe::App for MarteDebugApp { InternalEvent::Log(log) => { if !self.log_filters.paused { self.logs.push_back(log); - if self.logs.len() > 2000 { - self.logs.pop_front(); - } + if self.logs.len() > 2000 { self.logs.pop_front(); } } } + InternalEvent::InternalLog(msg) => { + self.logs.push_back(LogEntry { + time: Local::now().format("%H:%M:%S.%3f").to_string(), + level: "GUI_INFO".to_string(), + message: msg, + }); + if self.logs.len() > 2000 { self.logs.pop_front(); } + } InternalEvent::Discovery(signals) => { let mut metas = self.id_to_meta.lock().unwrap(); metas.clear(); for s in &signals { + if s.is_state { + let _ = self.internal_tx.send(InternalEvent::InternalLog(format!("Discovered stateful object: {}", s.name))); + if let Some(cfg) = &s.config { + self.state_configs.insert(s.name.clone(), cfg.clone()); + } + } let meta = metas.entry(s.id).or_insert_with(|| SignalMetadata { names: Vec::new(), sig_type: s.sig_type.clone(), dimensions: s.dimensions, elements: s.elements, + is_state: s.is_state, }); if !meta.names.contains(&s.name) { meta.names.push(s.name.clone()); @@ -1033,12 +439,8 @@ impl eframe::App for MarteDebugApp { message: format!("Discovery complete: {} signals mapped", signals.len()), }); } - InternalEvent::Tree(tree) => { - self.app_tree = Some(tree); - } - InternalEvent::NodeInfo(info) => { - self.node_info = info; - } + InternalEvent::Tree(tree) => { self.app_tree = Some(tree); } + InternalEvent::NodeInfo(info) => { self.node_info = info; } InternalEvent::TraceRequested(name, is_monitored) => { let mut data_map = self.traced_signals.lock().unwrap(); let entry = data_map.entry(name.clone()).or_insert_with(|| TraceData { @@ -1049,11 +451,6 @@ impl eframe::App for MarteDebugApp { is_monitored, }); entry.is_monitored = is_monitored; - self.logs.push_back(LogEntry { - time: Local::now().format("%H:%M:%S").to_string(), - level: "GUI_INFO".to_string(), - message: format!("Trace requested for: {}", name), - }); } InternalEvent::ClearTrace(name) => { let mut data_map = self.traced_signals.lock().unwrap(); @@ -1062,291 +459,69 @@ impl eframe::App for MarteDebugApp { plot.signals.retain(|s| s.source_name != name); } } - InternalEvent::UdpStats(count) => { - self.udp_packets = count; - } - InternalEvent::UdpDropped(dropped) => { - self.udp_dropped += dropped as u64; - } InternalEvent::Connected => { self.connected = true; - // Wait for connection to stabilize before sending commands - std::thread::sleep(std::time::Duration::from_millis(200)); + let _ = self.internal_tx.send(InternalEvent::InternalLog("Connected to server".to_string())); let _ = self.tx_cmd.send("SERVICE_INFO".to_string()); - std::thread::sleep(std::time::Duration::from_millis(100)); + let _ = self.tx_cmd.send("CONFIG".to_string()); let _ = self.tx_cmd.send("TREE".to_string()); - // Wait for TREE response before sending next command - std::thread::sleep(std::time::Duration::from_millis(500)); let _ = self.tx_cmd.send("DISCOVER".to_string()); } + InternalEvent::Disconnected => { + self.connected = false; + self.full_config = None; + self.app_states.clear(); + self.state_configs.clear(); + let _ = self.internal_tx.send(InternalEvent::InternalLog("Disconnected from server".to_string())); + } InternalEvent::ServiceConfig { udp_port, log_port } => { let mut changed = false; - if !udp_port.is_empty() && self.config.udp_port != udp_port { - self.config.udp_port = udp_port; - changed = true; - } - if !log_port.is_empty() && self.config.log_port != log_port { - self.config.log_port = log_port; - changed = true; - } + if !udp_port.is_empty() && self.config.udp_port != udp_port { self.config.udp_port = udp_port; changed = true; } + if !log_port.is_empty() && self.config.log_port != log_port { self.config.log_port = log_port; changed = true; } if changed { self.config.version += 1; *self.shared_config.lock().unwrap() = self.config.clone(); - self.logs.push_back(LogEntry { - time: Local::now().format("%H:%M:%S").to_string(), - level: "GUI_INFO".to_string(), - message: format!("Config updated from server: UDP={}, LOG={}", self.config.udp_port, self.config.log_port), - }); } } - InternalEvent::Disconnected => { - self.connected = false; + InternalEvent::StateUpdate(path, state) => { + let changed = if let Some(old) = self.app_states.get(&path) { + old != &state + } else { + true + }; + if changed { + let _ = self.internal_tx.send(InternalEvent::InternalLog(format!("⭐⭐ STATE CHANGE: {} -> {} ⭐⭐", path, state))); + self.app_states.insert(path, state); + } } - InternalEvent::InternalLog(msg) => { - self.logs.push_back(LogEntry { - time: Local::now().format("%H:%M:%S").to_string(), - level: "GUI_ERROR".to_string(), - message: msg, - }); - } - InternalEvent::CommandResponse(resp) => { - self.logs.push_back(LogEntry { - time: Local::now().format("%H:%M:%S").to_string(), - level: "CMD_RESP".to_string(), - message: resp, - }); + InternalEvent::Config(val) => { + self.full_config = Some(val.clone()); + self.raw_config_str = Self::marte_config_to_string(&val, 1); + // Remove first { and last } since it's the root ORD which usually doesn't have them in MARTe cfg files + self.raw_config_str = self.raw_config_str.trim().trim_start_matches('{').trim_end_matches('}').trim().to_string(); + let _ = self.internal_tx.send(InternalEvent::InternalLog("Full configuration received".to_string())); } InternalEvent::TelemMatched(id) => { *self.telem_match_count.entry(id).or_insert(0) += 1; } - InternalEvent::RecordPathChosen(name, path) => { - let mut data_map = self.traced_signals.lock().unwrap(); - if let Some(entry) = data_map.get_mut(&name) { - let (tx, rx) = unbounded(); - entry.recording_tx = Some(tx); - entry.recording_path = Some(path.clone()); - let tx_err = self.internal_tx.clone(); - thread::spawn(move || { - recording_worker(rx, path, name, tx_err); - }); - } - } - InternalEvent::RecordingError(name, err) => { - self.logs.push_back(LogEntry { - time: Local::now().format("%H:%M:%S").to_string(), - level: "REC_ERROR".to_string(), - message: format!("{}: {}", name, err), - }); + InternalEvent::UdpStats(_) => {} + InternalEvent::UdpDropped(count) => { + self.udp_dropped += count as u64; } + _ => {} } + ctx.request_repaint(); } - if self.scope.enabled { - self.apply_trigger_logic(); - } + if self.scope.enabled { self.apply_trigger_logic(); } - if let Some(dragged_name) = - ctx.data_mut(|d| d.get_temp::(egui::Id::new("drag_signal"))) - { - egui::Area::new(egui::Id::new("drag_ghost")) - .fixed_pos(ctx.input(|i| i.pointer.hover_pos().unwrap_or(egui::Pos2::ZERO))) - .order(egui::Order::Tooltip) - .show(ctx, |ui| { - ui.group(|ui| { - ui.label(format!("📈 {}", dragged_name)); - }); - }); - } - - if let Some(dialog) = &mut self.forcing_dialog { - let mut close = false; - egui::Window::new("Force Signal").show(ctx, |ui| { - ui.label(&dialog.signal_path); - ui.text_edit_singleline(&mut dialog.value); - ui.horizontal(|ui| { - if ui.button("Apply").clicked() { - let _ = self - .tx_cmd - .send(format!("FORCE {} {}", dialog.signal_path, dialog.value)); - self.forced_signals - .insert(dialog.signal_path.clone(), dialog.value.clone()); - close = true; - } - if ui.button("Cancel").clicked() { - close = true; - } - }); - }); - if close { - self.forcing_dialog = None; - } - } - - if let Some(dialog) = &mut self.monitoring_dialog { - let mut close = false; - egui::Window::new("Monitor Signal").show(ctx, |ui| { - ui.label(&dialog.signal_path); - ui.horizontal(|ui| { - ui.label("Period (ms):"); - ui.text_edit_singleline(&mut dialog.period_ms); - }); - ui.horizontal(|ui| { - if ui.button("Apply").clicked() { - let period = dialog.period_ms.parse::().unwrap_or(100); - let _ = self - .tx_cmd - .send(format!("MONITOR SIGNAL {} {}", dialog.signal_path, period)); - let _ = self.tx_cmd.send("DISCOVER".to_string()); - - // Check if it's an array signal to add all elements to view - let mut elements = 1; - if let Some(tree) = &self.app_tree { - // Helper to find item in tree - fn find_item<'a>(item: &'a TreeItem, target: &str, current: &str) -> Option<&'a TreeItem> { - let path = if current.is_empty() { item.name.clone() } else { format!("{}.{}", current, item.name) }; - if path == target || (current.is_empty() && item.name == "Root" && target.is_empty()) { return Some(item); } - if let Some(children) = &item.children { - for child in children { - if let Some(found) = find_item(child, target, &path) { return Some(found); } - } - } - None - } - if let Some(found) = find_item(tree, &dialog.signal_path, "") { - elements = found.elements.unwrap_or(1); - } - } - - if elements > 1 { - for i in 0..elements { - let elem_path = format!("{}[{}]", dialog.signal_path, i); - let _ = self.internal_tx.send(InternalEvent::TraceRequested(elem_path, true)); - } - } else { - let _ = self - .internal_tx - .send(InternalEvent::TraceRequested(dialog.signal_path.clone(), true)); - } - close = true; - } - if ui.button("Cancel").clicked() { - close = true; - } - }); - }); - if close { - self.monitoring_dialog = None; - } - } - - if self.message_dialog.is_some() { - let mut dialog = self.message_dialog.take().unwrap(); - let mut close = false; - let objects = self.get_all_objects(); - egui::Window::new("Send MARTe Message").show(ctx, |ui| { - egui::Grid::new("msg_grid").num_columns(2).show(ui, |ui| { - ui.label("Destination:"); - egui::ComboBox::from_id_salt("dest_combo") - .selected_text(&dialog.destination) - .width(200.0) - .show_ui(ui, |ui| { - for obj in objects { - ui.selectable_value(&mut dialog.destination, obj.clone(), obj); - } - }); - ui.end_row(); - - ui.label("Function:"); - ui.text_edit_singleline(&mut dialog.function); - ui.end_row(); - - ui.label("Payload:"); - ui.vertical(|ui| { - ui.text_edit_multiline(&mut dialog.payload); - ui.label(egui::RichText::new("Format: Key = Value (one per line)").small().weak()); - }); - ui.end_row(); - - ui.label("Wait Reply:"); - ui.checkbox(&mut dialog.expect_reply, ""); - ui.end_row(); - }); - - ui.horizontal(|ui| { - if ui.button("🚀 Send").clicked() { - let wait = if dialog.expect_reply { "1" } else { "0" }; - // Replace actual newlines with literal '\n' for the server-side tokenizer if needed, - // or ensure the server handles the raw multi-line stream if the protocol allows it. - // Given HandleCommand reads line-by-line, we must send it carefully. - // Actually, our Server loop reads up to \n. - // So we should encode newlines in payload if we want to send them in one go. - let encoded_payload = dialog.payload.replace('\n', "\\n"); - let cmd = format!( - "MSG {} {} {} {}", - dialog.destination, dialog.function, wait, encoded_payload - ); - let _ = self.tx_cmd.send(cmd); - close = true; - } - if ui.button("Cancel").clicked() { - close = true; - } - }); - }); - if !close { - self.message_dialog = Some(dialog); - } - } - - if let Some((p_idx, s_idx)) = self.style_editor { - let mut close = false; - egui::Window::new("Signal Style").show(ctx, |ui| { - if let Some(plot) = self.plots.get_mut(p_idx) { - if let Some(sig) = plot.signals.get_mut(s_idx) { - ui.horizontal(|ui| { - ui.label("Label:"); - ui.text_edit_singleline(&mut sig.label); - }); - ui.horizontal(|ui| { - ui.label("Unit:"); - ui.text_edit_singleline(&mut sig.unit); - }); - ui.horizontal(|ui| { - ui.label("Color:"); - let mut color = sig.color.to_array(); - if ui - .color_edit_button_srgba_unmultiplied(&mut color) - .changed() - { - sig.color = egui::Color32::from_rgba_unmultiplied( - color[0], color[1], color[2], color[3], - ); - } - }); - ui.horizontal(|ui| { - ui.label("Gain:"); - ui.add(egui::DragValue::new(&mut sig.gain).speed(0.1)); - }); - ui.horizontal(|ui| { - ui.label("Offset:"); - ui.add(egui::DragValue::new(&mut sig.offset).speed(1.0)); - }); - if ui.button("Close").clicked() { - close = true; - } - } - } - }); - if close { - self.style_editor = None; - } - } + // --- UI Rendering --- egui::TopBottomPanel::top("top").show(ctx, |ui| { ui.horizontal(|ui| { ui.toggle_value(&mut self.show_left_panel, "🗂 Tree"); ui.toggle_value(&mut self.show_right_panel, "📊 Signals"); - ui.toggle_value(&mut self.show_bottom_panel, "📜 Logs"); + ui.toggle_value(&mut self.show_bottom_panel, "📜 Bottom"); ui.separator(); if ui.button("➕ Plot").clicked() { self.plots.push(PlotInstance { @@ -1357,151 +532,14 @@ impl eframe::App for MarteDebugApp { }); } ui.separator(); - let (btn_text, btn_color) = if self.is_breaking { - ("▶ Resume App", egui::Color32::GREEN) - } else { - ("⏸ Pause App", egui::Color32::YELLOW) - }; - if ui - .button(egui::RichText::new(btn_text).color(btn_color)) - .clicked() - { + let (btn_text, btn_color) = if self.is_breaking { ("▶ Resume App", egui::Color32::GREEN) } else { ("⏸ Pause App", egui::Color32::YELLOW) }; + if ui.button(egui::RichText::new(btn_text).color(btn_color)).clicked() { self.is_breaking = !self.is_breaking; - let _ = self.tx_cmd.send(if self.is_breaking { - "PAUSE".to_string() - } else { - "RESUME".to_string() - }); + let _ = self.tx_cmd.send(if self.is_breaking { "PAUSE".to_string() } else { "RESUME".to_string() }); } ui.separator(); ui.checkbox(&mut self.scope.enabled, "🔭 Scope"); - if self.scope.enabled { - egui::ComboBox::from_id_salt("window_size") - .selected_text(format!("{}ms", self.scope.window_ms)) - .show_ui(ui, |ui| { - for ms in [ - 10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 2000.0, 5000.0, - 10000.0, - ] { - ui.selectable_value( - &mut self.scope.window_ms, - ms, - format!("{}ms", ms), - ); - } - }); - ui.selectable_value(&mut self.scope.mode, AcquisitionMode::FreeRun, "Free"); - ui.selectable_value(&mut self.scope.mode, AcquisitionMode::Triggered, "Trig"); - if self.scope.mode == AcquisitionMode::FreeRun { - if ui - .button(if self.scope.paused { - "▶ Resume" - } else { - "⏸ Pause" - }) - .clicked() - { - self.scope.paused = !self.scope.paused; - } - } else { - if ui - .button(if self.scope.is_armed { - "🔴 Armed" - } else { - "⚪ Single" - }) - .clicked() - { - self.scope.is_armed = true; - self.scope.trigger_active = false; - } - ui.menu_button("⚙ Trig", |ui| { - egui::Grid::new("trig").num_columns(2).show(ui, |ui| { - ui.label("Source:"); - ui.text_edit_singleline(&mut self.scope.trigger_source); - ui.end_row(); - ui.label("Edge:"); - egui::ComboBox::from_id_salt("edge") - .selected_text(format!("{:?}", self.scope.trigger_edge)) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.scope.trigger_edge, - TriggerEdge::Rising, - "Rising", - ); - ui.selectable_value( - &mut self.scope.trigger_edge, - TriggerEdge::Falling, - "Falling", - ); - ui.selectable_value( - &mut self.scope.trigger_edge, - TriggerEdge::Both, - "Both", - ); - }); - ui.end_row(); - ui.label("Thresh:"); - ui.add( - egui::DragValue::new(&mut self.scope.trigger_threshold) - .speed(0.1), - ); - ui.end_row(); - ui.label("Pre %:"); - ui.add(egui::Slider::new( - &mut self.scope.pre_trigger_percent, - 0.0..=100.0, - )); - ui.end_row(); - ui.label("Type:"); - ui.selectable_value( - &mut self.scope.trigger_type, - TriggerType::Single, - "Single", - ); - ui.selectable_value( - &mut self.scope.trigger_type, - TriggerType::Continuous, - "Cont", - ); - ui.end_row(); - }); - }); - } - } - ui.separator(); - ui.menu_button("🔌 Conn", |ui| { - egui::Grid::new("conn_grid").num_columns(2).show(ui, |ui| { - ui.label("IP:"); - ui.text_edit_singleline(&mut self.config.ip); - ui.end_row(); - ui.label("Control:"); - ui.text_edit_singleline(&mut self.config.tcp_port); - ui.end_row(); - ui.label("Telemetry (Auto):"); - ui.label(&self.config.udp_port); - ui.end_row(); - ui.label("Logs (Auto):"); - ui.label(&self.config.log_port); - ui.end_row(); - }); - if ui.button("🔄 Apply").clicked() { - self.config.version += 1; - *self.shared_config.lock().unwrap() = self.config.clone(); - ui.close_menu(); - } - if ui.button("📡 Re-Discover").clicked() { - let _ = self.tx_cmd.send("DISCOVER".to_string()); - ui.close_menu(); - } - if ui.button("❌ Off").clicked() { - self.config.version += 1; - let mut cfg = self.config.clone(); - cfg.ip = "".to_string(); - *self.shared_config.lock().unwrap() = cfg; - ui.close_menu(); - } - }); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui.button("✉ Send Msg").clicked() { self.message_dialog = Some(MessageDialog { @@ -1512,411 +550,486 @@ impl eframe::App for MarteDebugApp { }); } ui.separator(); - ui.label(format!( - "UDP: OK[{}] DROP[{}]", - self.udp_packets, self.udp_dropped - )); + ui.menu_button("🔌 Conn", |ui| { + egui::Grid::new("conn_grid").num_columns(2).show(ui, |ui| { + ui.label("IP:"); ui.text_edit_singleline(&mut self.config.ip); ui.end_row(); + ui.label("Control:"); ui.text_edit_singleline(&mut self.config.tcp_port); ui.end_row(); + }); + if ui.button("🔄 Apply").clicked() { + self.config.version += 1; + *self.shared_config.lock().unwrap() = self.config.clone(); + ui.close_menu(); + } + }); }); }); }); + // Always show Status Bar + egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| { + ui.horizontal(|ui| { + if self.connected { + ui.label(egui::RichText::new("✔ Connected").color(egui::Color32::GREEN)); + if let Some(state) = self.app_states.get("App") { + ui.separator(); + ui.label(format!("Active State: {}", state)); + } + } else { + ui.label(egui::RichText::new("✘ Disconnected").color(egui::Color32::RED)); + } + }); + }); + if self.show_left_panel { - egui::SidePanel::left("left") - .resizable(true) - .width_range(200.0..=500.0) - .show(ctx, |ui| { - egui::ScrollArea::vertical().show(ui, |ui| { - if let Some(tree) = self.app_tree.clone() { - self.render_tree(ui, &tree, "".to_string()); - } - }); + egui::SidePanel::left("left").resizable(true).show(ctx, |ui| { + ui.horizontal(|ui| { + ui.heading("MARTe Tree"); + if ui.button("🔄").clicked() { + let _ = self.tx_cmd.send("TREE".to_string()); + let _ = self.tx_cmd.send("DISCOVER".to_string()); + } }); + ui.separator(); + egui::ScrollArea::vertical().show(ui, |ui| { + if let Some(tree) = self.app_tree.clone() { + self.render_tree(ui, &tree, "".to_string()); + } else { + ui.label("No tree data. Connect or Refresh."); + } + }); + }); } if self.show_right_panel { - egui::SidePanel::right("right") - .resizable(true) - .width_range(250.0..=400.0) - .show(ctx, |ui| { - ui.heading("Traced Signals"); - let mut names: Vec<_> = { - let data_map = self.traced_signals.lock().unwrap(); - data_map.keys().cloned().collect() - }; - names.sort(); - egui::ScrollArea::vertical() - .id_salt("traced_scroll") - .show(ui, |ui| { - for key in names { - let mut data_map = self.traced_signals.lock().unwrap(); - if let Some(entry) = data_map.get_mut(&key) { - let last_val = entry.last_value; - let is_recording = entry.recording_tx.is_some(); - ui.horizontal(|ui| { - if is_recording { - ui.label( - egui::RichText::new("●").color(egui::Color32::RED), - ); - } - let response = ui.add( - egui::Label::new(format!("{}: {:.2}", key, last_val)) - .sense( - egui::Sense::drag().union(egui::Sense::click()), - ), - ); - if response.drag_started() { - ctx.data_mut(|d| { - d.insert_temp( - egui::Id::new("drag_signal"), - key.clone(), - ) - }); - } - response.context_menu(|ui| { - if !is_recording { - if ui.button("⏺ Record to Parquet").clicked() { - let tx = self.internal_tx.clone(); - let name_clone = key.clone(); - thread::spawn(move || { - if let Some(path) = FileDialog::new() - .add_filter("Parquet", &["parquet"]) - .save_file() - { - let _ = tx.send( - InternalEvent::RecordPathChosen( - name_clone, - path.to_string_lossy() - .to_string(), - ), - ); - } - }); - ui.close_menu(); - } - } else { - if ui.button("⏹ Stop").clicked() { - entry.recording_tx = None; - ui.close_menu(); - } - } - }); - if ui.button("❌").clicked() { - if entry.is_monitored { - let _ = self.tx_cmd.send(format!("UNMONITOR SIGNAL {}", key)); - } else { - let _ = self.tx_cmd.send(format!("TRACE {} 0", key)); - } - let _ = self - .internal_tx - .send(InternalEvent::ClearTrace(key.clone())); - } - }); + egui::SidePanel::right("right").resizable(true).show(ctx, |ui| { + ui.heading("Signals"); + let mut names: Vec<_> = { + let data_map = self.traced_signals.lock().unwrap(); + data_map.keys().cloned().collect() + }; + names.sort(); + egui::ScrollArea::vertical().show(ui, |ui| { + for key in names { + let mut data_map = self.traced_signals.lock().unwrap(); + if let Some(entry) = data_map.get_mut(&key) { + ui.horizontal(|ui| { + let is_recording = entry.recording_tx.is_some(); + if is_recording { + ui.label(egui::RichText::new("●").color(egui::Color32::RED)); } - } - }); - ui.separator(); - ui.heading("Forced Signals"); - let mut to_delete = Vec::new(); - for (path, val) in &self.forced_signals { - ui.horizontal(|ui| { - ui.label(format!("{}: {}", path, val)); - if ui.button("❌").clicked() { - let _ = self.tx_cmd.send(format!("UNFORCE {}", path)); - to_delete.push(path.to_owned()); - } - }); - } - for key in to_delete.iter() { - self.forced_signals.remove(key); + + let label = format!("{}: {:.2}", key, entry.last_value); + let resp = ui.add(egui::Label::new(label).sense(egui::Sense::drag().union(egui::Sense::click()))); + if resp.drag_started() { + ctx.data_mut(|d| d.insert_temp(egui::Id::new("drag_signal"), key.clone())); + } + + resp.context_menu(|ui| { + if !is_recording { + if ui.button("⏺ Record to Parquet").clicked() { + let tx = self.internal_tx.clone(); + let name_clone = key.clone(); + thread::spawn(move || { + if let Some(path) = FileDialog::new() + .add_filter("Parquet", &["parquet"]) + .save_file() + { + let _ = tx.send(InternalEvent::RecordPathChosen( + name_clone, + path.to_string_lossy().to_string(), + )); + } + }); + ui.close_menu(); + } + } else { + if ui.button("⏹ Stop").clicked() { + entry.recording_tx = None; + ui.close_menu(); + } + } + }); + + if ui.button("❌").clicked() { + if entry.is_monitored { + let _ = self.tx_cmd.send(format!("UNMONITOR SIGNAL {}", key)); + } else { + let _ = self.tx_cmd.send(format!("TRACE {} 0", key)); + } + let _ = self.internal_tx.send(InternalEvent::ClearTrace(key.clone())); + } + }); + } } }); + }); } if self.show_bottom_panel { - egui::TopBottomPanel::bottom("log_panel") + let panel_resp = egui::TopBottomPanel::bottom("bottom_tab_panel") .resizable(true) - .default_height(150.0) + .default_height(self.bottom_panel_height) .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.heading("Logs"); + ui.horizontal(|ui| { + ui.selectable_value(&mut self.active_bottom_tab, BottomTab::Logs, "📜 Logs"); + ui.selectable_value(&mut self.active_bottom_tab, BottomTab::State, "⚙ State"); + }); + ui.separator(); + match self.active_bottom_tab { + BottomTab::Logs => { + ui.horizontal(|ui| { + ui.checkbox(&mut self.log_filters.show_debug, "Debug"); + ui.checkbox(&mut self.log_filters.show_info, "Info"); + ui.checkbox(&mut self.log_filters.show_warning, "Warning"); + ui.checkbox(&mut self.log_filters.show_error, "Error"); + ui.separator(); + ui.checkbox(&mut self.log_filters.paused, "⏸ Pause"); + if ui.button("🗑 Clear").clicked() { + self.logs.clear(); + } + ui.separator(); + ui.label("Filter:"); + ui.text_edit_singleline(&mut self.log_filters.content_regex); + }); ui.separator(); - ui.checkbox(&mut self.log_filters.show_debug, "Debug"); - ui.checkbox(&mut self.log_filters.show_info, "Info"); - ui.checkbox(&mut self.log_filters.show_warning, "Warn"); - ui.checkbox(&mut self.log_filters.show_error, "Error"); - ui.separator(); - ui.label("Filter:"); - ui.text_edit_singleline(&mut self.log_filters.content_regex); - if ui.button("🗑 Clear").clicked() { - self.logs.clear(); - } - }); - ui.separator(); - let regex = if !self.log_filters.content_regex.is_empty() { - Regex::new(&self.log_filters.content_regex).ok() - } else { - None - }; - egui::ScrollArea::vertical() - .stick_to_bottom(true) - .auto_shrink([false, false]) - .show(ui, |ui| { + egui::ScrollArea::vertical() + .stick_to_bottom(true) + .auto_shrink([false, false]) + .show(ui, |ui| { + let regex = if !self.log_filters.content_regex.is_empty() { + Regex::new(&self.log_filters.content_regex).ok() + } else { + None + }; for log in &self.logs { let show = match log.level.as_str() { "Debug" => self.log_filters.show_debug, - "Information" | "GUI_INFO" | "GUI_WARN" | "CMD_RESP" => { - self.log_filters.show_info - } + "Information" | "GUI_INFO" | "GUI_WARN" | "CMD_RESP" => self.log_filters.show_info, "Warning" => self.log_filters.show_warning, - "FatalError" | "OSError" | "ParametersError" | "GUI_ERROR" - | "REC_ERROR" => self.log_filters.show_error, + "FatalError" | "OSError" | "ParametersError" | "GUI_ERROR" | "REC_ERROR" | "STATE_CHG" => self.log_filters.show_error, _ => true, }; - if !show { - continue; - } + if !show { continue; } if let Some(re) = ®ex { - if !re.is_match(&log.message) && !re.is_match(&log.level) { - continue; - } + if !re.is_match(&log.message) && !re.is_match(&log.level) { continue; } } - let color = match log.level.as_str() { - "FatalError" | "OSError" | "ParametersError" | "GUI_ERROR" - | "REC_ERROR" => egui::Color32::from_rgb(255, 100, 100), - "Warning" | "GUI_WARN" => { - egui::Color32::from_rgb(255, 255, 100) - } - "Information" | "GUI_INFO" => { - egui::Color32::from_rgb(100, 255, 100) - } - "Debug" => egui::Color32::from_rgb(100, 100, 255), - "CMD_RESP" => egui::Color32::from_rgb(255, 255, 255), - _ => egui::Color32::WHITE, - }; - ui.horizontal_wrapped(|ui| { - ui.label( - egui::RichText::new(&log.time) - .color(egui::Color32::GRAY) - .monospace(), - ); - ui.label( - egui::RichText::new(format!("[{}]", log.level)) - .color(color) - .strong(), - ); + + ui.horizontal(|ui| { + ui.label(egui::RichText::new(&log.time).weak()); + ui.label(egui::RichText::new(format!("[{}]", log.level)).strong()); ui.add(egui::Label::new(&log.message).wrap()); }); } }); - }); + } + BottomTab::State => { + ui.horizontal(|ui| { + if ui.button("🔄 Refresh Config").clicked() { + let _ = self.tx_cmd.send("CONFIG".to_string()); + } + if let Some(_) = &self.full_config { + ui.label(egui::RichText::new("✅ Config Loaded").color(egui::Color32::GREEN)); + } else { + ui.label(egui::RichText::new("❌ No Config").color(egui::Color32::RED)); + } + }); + ui.separator(); + egui::ScrollArea::vertical() + .auto_shrink([false, false]) + .show(ui, |ui| { + if self.app_states.is_empty() { + ui.label("No state updates received yet."); + ui.label(format!("Telemetry matches: {:?}", self.telem_match_count)); + } + let mut paths: Vec<_> = self.app_states.keys().cloned().collect(); + paths.sort(); + for path in paths { + let state = self.app_states.get(&path).unwrap(); + ui.group(|ui| { + ui.horizontal(|ui| { + ui.heading(&path); + ui.separator(); + ui.label(egui::RichText::new(format!("State: {}", state)).color(egui::Color32::GREEN).strong()); + }); + + let obj_cfg_opt = self.state_configs.get(&path).or_else(|| { + self.full_config.as_ref().and_then(|config| Self::get_config_at_path(config, &path)) + }); + + if let Some(obj_cfg) = obj_cfg_opt { + if let Some(states_cfg) = Self::get_cfg(obj_cfg, "States") { + if let Some(cur_state_cfg) = Self::get_cfg(states_cfg, state) { + if let Some(threads_cfg) = Self::get_cfg(cur_state_cfg, "Threads") { + if let Some(threads_obj) = threads_cfg.as_object() { + for (th_name, th_cfg) in threads_obj { + let clean_th_name = th_name.trim_start_matches('+'); + ui.horizontal(|ui| { + let mut th_label = egui::RichText::new(clean_th_name).strong(); + if let Some(cpus) = th_cfg.get("CPUs").and_then(|v| v.as_str()) { + th_label = egui::RichText::new(format!("{} (CPUs: {})", clean_th_name, cpus)).strong(); + } + ui.label(th_label); + }); + + if let Some(funcs) = th_cfg.get("Functions").and_then(|v| v.as_str()) { + ui.horizontal(|ui| { + ui.label(" GAMs:"); + // Split by comma or space + let gam_list: Vec<&str> = if funcs.contains(',') { + funcs.split(',').map(|s| s.trim()).collect() + } else { + funcs.split_whitespace().collect() + }; + + for (i, gam) in gam_list.iter().enumerate() { + if i > 0 { ui.label("➡"); } + let _ = ui.button(*gam); + } + }); + } + ui.separator(); + } + } else { + ui.label(egui::RichText::new("Threads node is not an object.").color(egui::Color32::RED)); + } + } else { + ui.label(egui::RichText::new(format!("Could not find 'Threads' node in state '{}'.", state)).weak()); + } + } else { + ui.label(egui::RichText::new(format!("Could not find configuration for state '{}'.", state)).weak()); + if let Some(obj) = states_cfg.as_object() { + ui.label(format!("Available states: {:?}", obj.keys().collect::>())); + } + } + } else { + ui.label(egui::RichText::new("Could not find 'States' node in application config.").weak()); + if let Some(obj) = obj_cfg.as_object() { + ui.label(format!("Available nodes: {:?}", obj.keys().collect::>())); + } + } + } else { + ui.label(egui::RichText::new("Configuration not available for this object.").weak()); + } + }); + } + }); + } + } + }); + self.bottom_panel_height = panel_resp.response.rect.height(); } egui::CentralPanel::default().show(ctx, |ui| { - let n_plots = self.plots.len(); - if n_plots > 0 { - let plot_height = ui.available_height() / n_plots as f32; - let mut to_remove = None; - let mut current_range = None; - for (p_idx, plot_inst) in self.plots.iter_mut().enumerate() { - ui.group(|ui| { - ui.horizontal(|ui| { - ui.label(egui::RichText::new(&plot_inst.id).strong()); - ui.selectable_value( - &mut plot_inst.plot_type, - PlotType::Normal, - "Series", - ); - ui.selectable_value( - &mut plot_inst.plot_type, - PlotType::LogicAnalyzer, - "Logic", - ); - if ui.button("🗑").clicked() { - to_remove = Some(p_idx); - } - }); - let mut plot = Plot::new(&plot_inst.id) - .height(plot_height - 40.0) - .show_axes([true, true]); + ui.horizontal(|ui| { + ui.selectable_value(&mut self.active_main_tab, MainTab::Plots, "📈 Plots"); + ui.selectable_value(&mut self.active_main_tab, MainTab::Config, "📝 Config"); + }); + ui.separator(); - plot = plot.x_axis_formatter(|mark, _range| { - let val = mark.value; - let hours = (val / 3600.0) as u32; - let mins = ((val % 3600.0) / 60.0) as u32; - let secs = val % 60.0; - format!("{:02}:{:02}:{:05.2}", hours, mins, secs) - }); - - let data_map = self.traced_signals.lock().unwrap(); - let mut latest_t = 0.0; - for sig_cfg in &plot_inst.signals { - if let Some(data) = data_map.get(&sig_cfg.source_name) { - if let Some(last) = data.values.back() { - if last[0] > latest_t { - latest_t = last[0]; + match self.active_main_tab { + MainTab::Plots => { + let n_plots = self.plots.len(); + let mut to_remove = None; + for (p_idx, plot_inst) in self.plots.iter_mut().enumerate() { + ui.group(|ui| { + ui.horizontal(|ui| { + ui.label(egui::RichText::new(&plot_inst.id).strong()); + if ui.button("🗑").clicked() { + to_remove = Some(p_idx); + } + }); + let mut plot = Plot::new(&plot_inst.id).height(ui.available_height() / (n_plots - p_idx) as f32 - 30.0); + + if self.scope.enabled { + let data_map = self.traced_signals.lock().unwrap(); + let mut latest_t = 0.0; + for sig_cfg in &plot_inst.signals { + if let Some(data) = data_map.get(&sig_cfg.source_name) { + if let Some(last) = data.values.back() { + if last[0] > latest_t { latest_t = last[0]; } + } } } - } - } - - if self.scope.enabled { - let window_s = self.scope.window_ms / 1000.0; - let center_t = if self.scope.mode == AcquisitionMode::Triggered { - if self.scope.trigger_active { - self.scope.last_trigger_time - } else { - latest_t - } - } else { - latest_t - }; - let x_min = - center_t - (self.scope.pre_trigger_percent / 100.0) * window_s; - plot = plot.include_x(x_min).include_x(x_min + window_s); - if !self.scope.paused { - plot = plot.auto_bounds(egui::Vec2b::new(true, true)); - } - } else { - if let Some(range) = self.shared_x_range { - if !plot_inst.auto_bounds { - plot = plot.include_x(range[0]).include_x(range[1]); - } - } - if plot_inst.auto_bounds { - plot = plot.auto_bounds(egui::Vec2b::new(true, true)); - } - } - - let plot_resp = plot.show(ui, |plot_ui| { - if !self.scope.enabled && !plot_inst.auto_bounds { - if let Some(range) = self.shared_x_range { - let bounds = plot_ui.plot_bounds(); - plot_ui.set_plot_bounds(PlotBounds::from_min_max( - [range[0], bounds.min()[1]], - [range[1], bounds.max()[1]], - )); - } - } - if self.scope.enabled - && self.scope.mode == AcquisitionMode::Triggered - && self.scope.trigger_active - { - plot_ui.vline( - VLine::new(self.scope.last_trigger_time) - .color(egui::Color32::YELLOW) - .style(LineStyle::Dashed { length: 5.0 }), - ); + let window_s = self.scope.window_ms / 1000.0; + let x_min = latest_t - window_s; + plot = plot.include_x(x_min).include_x(latest_t); } - for (s_idx, sig_cfg) in plot_inst.signals.iter().enumerate() { - if let Some(data) = data_map.get(&sig_cfg.source_name) { - let points_iter = - data.values.iter().rev().take(5000).rev().map(|[t, v]| { - let mut final_v = *v * sig_cfg.gain + sig_cfg.offset; - if plot_inst.plot_type == PlotType::LogicAnalyzer { - final_v = (s_idx as f64 * 1.5) - + (if final_v > 0.5 { 1.0 } else { 0.0 }); - } - [*t, final_v] - }); - plot_ui.line( - Line::new(PlotPoints::from_iter(points_iter)) - .name(&sig_cfg.label) - .color(sig_cfg.color), - ); + let plot_resp = plot.show(ui, |plot_ui| { + let data_map = self.traced_signals.lock().unwrap(); + for sig_cfg in &plot_inst.signals { + if let Some(data) = data_map.get(&sig_cfg.source_name) { + let points: Vec<_> = data.values.iter().rev().take(5000).rev().map(|p| [p[0], p[1]]).collect(); + plot_ui.line(Line::new(PlotPoints::from(points)).color(sig_cfg.color).name(&sig_cfg.label)); + } + } + }); + + if plot_resp.response.hovered() && ctx.input(|i| i.pointer.any_released()) { + if let Some(dropped) = ctx.data_mut(|d| d.get_temp::(egui::Id::new("drag_signal"))) { + let color = Self::next_color(plot_inst.signals.len()); + plot_inst.signals.push(SignalPlotConfig { + source_name: dropped.clone(), + label: dropped.clone(), + unit: "".to_string(), + color, + line_style: egui_plot::LineStyle::Solid, + marker_type: MarkerType::None, + gain: 1.0, + offset: 0.0, + }); + ctx.data_mut(|d| d.remove_temp::(egui::Id::new("drag_signal"))); } - } - if p_idx == 0 || current_range.is_none() { - let b = plot_ui.plot_bounds(); - current_range = Some([b.min()[0], b.max()[0]]); } }); - drop(data_map); - - if plot_resp.response.hovered() && ctx.input(|i| i.pointer.any_released()) { - if let Some(dropped) = - ctx.data_mut(|d| d.get_temp::(egui::Id::new("drag_signal"))) - { - let color = Self::next_color(plot_inst.signals.len()); - plot_inst.signals.push(SignalPlotConfig { - source_name: dropped.clone(), - label: dropped.clone(), - unit: "".to_string(), - color, - line_style: LineStyle::Solid, - marker_type: MarkerType::None, - gain: 1.0, - offset: 0.0, - }); - ctx.data_mut(|d| { - d.remove_temp::(egui::Id::new("drag_signal")) - }); - } - } - if plot_resp.response.dragged() - || ctx.input(|i| i.smooth_scroll_delta.y != 0.0) - { - if plot_resp.response.hovered() { - plot_inst.auto_bounds = false; - let b = plot_resp.transform.bounds(); - self.shared_x_range = Some([b.min()[0], b.max()[0]]); - } - } - plot_resp.response.context_menu(|ui| { - if ui.button("🔍 Fit View").clicked() { - plot_inst.auto_bounds = true; - self.shared_x_range = None; - ui.close_menu(); - } - ui.separator(); - let mut sig_to_remove = None; - for (s_idx, sig) in plot_inst.signals.iter().enumerate() { - ui.horizontal(|ui| { - ui.label(&sig.label); - if ui.button("🎨 Style").clicked() { - self.style_editor = Some((p_idx, s_idx)); - ui.close_menu(); - } - if ui.button("❌ Remove").clicked() { - sig_to_remove = Some(s_idx); - ui.close_menu(); - } - }); - } - if let Some(idx) = sig_to_remove { - plot_inst.signals.remove(idx); - } - }); - }); - } - if let Some(idx) = to_remove { - self.plots.remove(idx); - } - if !self.scope.enabled { - if let Some(range) = current_range { - if self.shared_x_range.is_none() { - self.shared_x_range = Some(range); - } + } + if let Some(idx) = to_remove { + self.plots.remove(idx); } } - } else { - ui.centered_and_justified(|ui| { - ui.label("Add a plot panel to begin analysis"); - }); + MainTab::Config => { + egui::ScrollArea::vertical().show(ui, |ui| { + let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| { + let mut job = egui::text::LayoutJob::default(); + for line in string.lines() { + if let Some(eq_idx) = line.find('=') { + let key = &line[..eq_idx]; + let val = &line[eq_idx..]; + + let key_color = if key.trim().starts_with('+') { + egui::Color32::from_rgb(255, 165, 0) // Orange for nodes + } else { + egui::Color32::from_rgb(100, 200, 255) // Blue for properties + }; + + job.append(key, 0.0, egui::TextFormat { + font_id: egui::FontId::monospace(14.0), + color: key_color, + ..Default::default() + }); + + if val.contains('{') || val.contains('}') { + job.append(val, 0.0, egui::TextFormat { + font_id: egui::FontId::monospace(14.0), + color: egui::Color32::GRAY, + ..Default::default() + }); + } else { + let val_color = if key.trim() == "Class" { + egui::Color32::from_rgb(255, 100, 255) // Pink for classes + } else { + egui::Color32::from_rgb(150, 255, 150) // Green for values + }; + job.append(val, 0.0, egui::TextFormat { + font_id: egui::FontId::monospace(14.0), + color: val_color, + ..Default::default() + }); + } + } else { + job.append(line, 0.0, egui::TextFormat { + font_id: egui::FontId::monospace(14.0), + color: egui::Color32::GRAY, + ..Default::default() + }); + } + job.append("\n", 0.0, Default::default()); + } + ui.fonts(|f| f.layout_job(job)) + }; + + ui.add( + egui::TextEdit::multiline(&mut self.raw_config_str) + .font(egui::FontId::monospace(14.0)) + .code_editor() + .lock_focus(true) + .desired_width(f32::INFINITY) + .layouter(&mut layouter) + ); + }); + } } }); - ctx.request_repaint_after(std::time::Duration::from_millis(16)); + + // Dialogs handling + if self.forcing_dialog.is_some() { + let mut dialog = self.forcing_dialog.take().unwrap(); + let mut close = false; + egui::Window::new("Force Signal").show(ctx, |ui| { + ui.label(format!("Signal: {}", dialog.signal_path)); + ui.horizontal(|ui| { + ui.label("Value:"); + ui.text_edit_singleline(&mut dialog.value); + }); + ui.horizontal(|ui| { + if ui.button("⚡ Force").clicked() { + let _ = self.tx_cmd.send(format!("FORCE {} {}", dialog.signal_path, dialog.value)); + close = true; + } + if ui.button("🔓 Unforce").clicked() { + let _ = self.tx_cmd.send(format!("UNFORCE {}", dialog.signal_path)); + close = true; + } + if ui.button("Cancel").clicked() { close = true; } + }); + }); + if !close { self.forcing_dialog = Some(dialog); } + } + + if self.monitoring_dialog.is_some() { + let mut dialog = self.monitoring_dialog.take().unwrap(); + let mut close = false; + egui::Window::new("Monitor Signal").show(ctx, |ui| { + ui.label(format!("Signal: {}", dialog.signal_path)); + ui.horizontal(|ui| { + ui.label("Period (ms):"); + ui.text_edit_singleline(&mut dialog.period_ms); + }); + ui.horizontal(|ui| { + if ui.button("📈 Monitor").clicked() { + let _ = self.tx_cmd.send(format!("MONITOR SIGNAL {} {}", dialog.signal_path, dialog.period_ms)); + let _ = self.internal_tx.send(InternalEvent::TraceRequested(dialog.signal_path.clone(), true)); + close = true; + } + if ui.button("⏹ Stop").clicked() { + let _ = self.tx_cmd.send(format!("UNMONITOR SIGNAL {}", dialog.signal_path)); + let _ = self.internal_tx.send(InternalEvent::ClearTrace(dialog.signal_path.clone())); + close = true; + } + if ui.button("Cancel").clicked() { close = true; } + }); + }); + if !close { self.monitoring_dialog = Some(dialog); } + } + + if self.message_dialog.is_some() { + let mut dialog = self.message_dialog.take().unwrap(); + let mut close = false; + egui::Window::new("Send Message").show(ctx, |ui| { + let objects = self.get_all_objects(); + egui::ComboBox::from_label("Target").selected_text(&dialog.destination).show_ui(ui, |ui| { + for obj in objects { ui.selectable_value(&mut dialog.destination, obj.clone(), obj); } + }); + ui.text_edit_singleline(&mut dialog.function); + ui.text_edit_multiline(&mut dialog.payload); + if ui.button("Send").clicked() { + let cmd = format!("MSG {} {} {} {}", dialog.destination, dialog.function, if dialog.expect_reply { "1" } else { "0" }, dialog.payload.replace('\n', "\\n")); + let _ = self.tx_cmd.send(cmd); + close = true; + } + if ui.button("Cancel").clicked() { close = true; } + }); + if !close { self.message_dialog = Some(dialog); } + } + + ctx.request_repaint(); } } fn main() -> Result<(), eframe::Error> { - let options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default().with_inner_size([1280.0, 800.0]), - ..Default::default() - }; eframe::run_native( "MARTe2 Debug Explorer", - options, + eframe::NativeOptions::default(), Box::new(|cc| Ok(Box::new(MarteDebugApp::new(cc)))), ) } diff --git a/Tools/gui_client/src/models.rs b/Tools/gui_client/src/models.rs new file mode 100644 index 0000000..4c1ccf3 --- /dev/null +++ b/Tools/gui_client/src/models.rs @@ -0,0 +1,226 @@ +use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; +use crossbeam_channel::Sender; +use eframe::egui; +use egui_plot::LineStyle; + +#[allow(dead_code)] +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Signal { + #[serde(alias = "Name")] + pub name: String, + #[serde(alias = "ID")] + pub id: u32, + #[serde(rename = "type", alias = "Type")] + pub sig_type: String, + #[serde(default, alias = "Dimensions")] + pub dimensions: u8, + #[serde(default = "default_elements", alias = "Elements")] + pub elements: u32, + #[serde(default, alias = "IsState")] + pub is_state: bool, + #[serde(default)] + pub config: Option, +} + +pub fn default_elements() -> u32 { 1 } + +#[derive(Deserialize)] +pub struct DiscoverResponse { + #[serde(rename = "Signals")] + pub signals: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct TreeItem { + pub name: String, + pub class: String, + pub children: Option>, + #[serde(rename = "Type")] + pub sig_type: Option, + pub dimensions: Option, + pub elements: Option, + pub is_traceable: Option, + pub is_forcable: Option, +} + +#[derive(Clone)] +pub struct LogEntry { + pub time: String, + pub level: String, + pub message: String, +} + +#[allow(dead_code)] +pub struct TraceData { + pub values: VecDeque<[f64; 2]>, + pub last_value: f64, + pub recording_tx: Option>, + pub recording_path: Option, + pub is_monitored: bool, +} + +#[allow(dead_code)] +pub struct SignalMetadata { + pub names: Vec, + pub sig_type: String, + pub dimensions: u8, + pub elements: u32, + pub is_state: bool, +} + +#[derive(Clone)] +pub struct ConnectionConfig { + pub ip: String, + pub tcp_port: String, + pub udp_port: String, + pub log_port: String, + pub version: u64, +} + +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum PlotType { + Normal, + LogicAnalyzer, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum AcquisitionMode { + FreeRun, + Triggered, +} + +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum TriggerEdge { + Rising, + Falling, + Both, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum TriggerType { + Single, + Continuous, +} + +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum MarkerType { + None, + Circle, + Square, +} + +#[allow(dead_code)] +impl MarkerType { + pub fn to_shape(&self) -> Option { + match self { + MarkerType::None => None, + MarkerType::Circle => Some(egui_plot::MarkerShape::Circle), + MarkerType::Square => Some(egui_plot::MarkerShape::Square), + } + } +} + +#[allow(dead_code)] +#[derive(Clone)] +pub struct SignalPlotConfig { + pub source_name: String, + pub label: String, + pub unit: String, + pub color: egui::Color32, + pub line_style: LineStyle, + pub marker_type: MarkerType, + pub gain: f64, + pub offset: f64, +} + +#[allow(dead_code)] +pub struct PlotInstance { + pub id: String, + pub plot_type: PlotType, + pub signals: Vec, + pub auto_bounds: bool, +} + +#[allow(dead_code)] +pub enum InternalEvent { + Log(LogEntry), + Discovery(Vec), + Tree(TreeItem), + CommandResponse(String), + NodeInfo(String), + Connected, + Disconnected, + InternalLog(String), + TraceRequested(String, bool), // Name, IsMonitored + ClearTrace(String), + UdpStats(u64), + UdpDropped(u32), + RecordPathChosen(String, String), // SignalName, FilePath + RecordingError(String, String), // SignalName, ErrorMessage + TelemMatched(u32), // Signal ID + ServiceConfig { udp_port: String, log_port: String }, + StateUpdate(String, String), // Path, StateName + Config(serde_json::Value), +} + +#[allow(dead_code)] +pub struct ForcingDialog { + pub signal_path: String, + pub value: String, +} + +#[allow(dead_code)] +pub struct MonitorDialog { + pub signal_path: String, + pub period_ms: String, +} + +pub struct MessageDialog { + pub destination: String, + pub function: String, + pub payload: String, + pub expect_reply: bool, +} + +#[allow(dead_code)] +pub struct LogFilters { + pub show_debug: bool, + pub show_info: bool, + pub show_warning: bool, + pub show_error: bool, + pub paused: bool, + pub content_regex: String, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum BottomTab { + Logs, + State, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum MainTab { + Plots, + Config, +} + +#[allow(dead_code)] +pub struct ScopeSettings { + pub enabled: bool, + pub window_ms: f64, + pub mode: AcquisitionMode, + pub paused: bool, + pub trigger_type: TriggerType, + pub trigger_source: String, + pub trigger_edge: TriggerEdge, + pub trigger_threshold: f64, + pub pre_trigger_percent: f64, + pub trigger_active: bool, + pub last_trigger_time: f64, + pub is_armed: bool, +} diff --git a/Tools/gui_client/src/workers.rs b/Tools/gui_client/src/workers.rs new file mode 100644 index 0000000..463b862 --- /dev/null +++ b/Tools/gui_client/src/workers.rs @@ -0,0 +1,498 @@ +use crate::models::*; +use arrow::array::Float64Array; +use arrow::datatypes::{DataType, Field, Schema}; +use arrow::record_batch::RecordBatch; +use chrono::Local; +use crossbeam_channel::{Receiver, Sender}; +use once_cell::sync::Lazy; +use parquet::arrow::arrow_writer::ArrowWriter; +use parquet::file::properties::WriterProperties; +use socket2::{Domain, Protocol, Socket, Type}; +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; +use std::net::{TcpStream, UdpSocket}; +use std::sync::{Arc, Mutex}; +use std::thread; + +pub static BASE_TELEM_TS: Lazy>> = Lazy::new(|| Mutex::new(None)); + +pub fn tcp_command_worker( + shared_config: Arc>, + rx_cmd: Receiver, + tx_events: Sender, +) { + let mut current_version = 0; + let mut current_addr = String::new(); + loop { + { + let config = shared_config.lock().unwrap(); + if config.version != current_version { + current_version = config.version; + current_addr = format!("{}:{}", config.ip, config.tcp_port); + } + } + if current_addr.is_empty() || current_addr.starts_with(":") { + thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + if let Ok(mut stream) = TcpStream::connect(¤t_addr) { + let _ = stream.set_nodelay(true); + let mut reader = BufReader::new(stream.try_clone().unwrap()); + let _ = tx_events.send(InternalEvent::Connected); + let stop_flag = Arc::new(Mutex::new(false)); + let stop_flag_reader = stop_flag.clone(); + let tx_events_inner = tx_events.clone(); + thread::spawn(move || { + let mut line = String::new(); + let mut json_acc = String::new(); + let mut in_json = false; + while reader.read_line(&mut line).is_ok() { + if *stop_flag_reader.lock().unwrap() { + break; + } + let trimmed = line.trim(); + if trimmed.is_empty() { + line.clear(); + continue; + } + + if !in_json && trimmed.starts_with("{") { + in_json = true; + json_acc.clear(); + } + + if in_json { + json_acc.push_str(trimmed); + if trimmed.contains("OK DISCOVER") { + in_json = false; + let json_clean = + json_acc.split("OK DISCOVER").next().unwrap_or("").trim(); + match serde_json::from_str::(json_clean) { + Ok(resp) => { + let _ = tx_events_inner + .send(InternalEvent::Discovery(resp.signals)); + } + Err(e) => { + let _ = + tx_events_inner.send(InternalEvent::InternalLog(format!( + "Discovery JSON Error: {} | Payload: {}", + e, json_clean + ))); + } + } + json_acc.clear(); + } else if trimmed.contains("OK TREE") { + in_json = false; + let json_clean = json_acc.split("OK TREE").next().unwrap_or("").trim(); + match serde_json::from_str::(json_clean) { + Ok(resp) => { + let _ = tx_events_inner.send(InternalEvent::Tree(resp)); + } + Err(e) => { + let _ = tx_events_inner.send(InternalEvent::InternalLog( + format!("Tree JSON Error: {}", e), + )); + } + } + json_acc.clear(); + } else if trimmed.contains("OK INFO") { + in_json = false; + let json_clean = json_acc.split("OK INFO").next().unwrap_or("").trim(); + let _ = tx_events_inner + .send(InternalEvent::NodeInfo(json_clean.to_string())); + json_acc.clear(); + } else if trimmed.contains("OK CONFIG") { + in_json = false; + let json_clean = json_acc.split("OK CONFIG").next().unwrap_or("").trim(); + match serde_json::from_str::(json_clean) { + Ok(val) => { + let _ = tx_events_inner.send(InternalEvent::Config(val)); + } + Err(e) => { + let _ = tx_events_inner.send(InternalEvent::InternalLog( + format!("Config JSON Error: {} | Payload sample: {}", e, &json_clean[..json_clean.len().min(100)]), + )); + } + } + json_acc.clear(); + } + } else { + if trimmed.starts_with("OK SERVICE_INFO") { + let parts: Vec<&str> = trimmed.split_whitespace().collect(); + let mut udp = String::new(); + let mut log = String::new(); + for p in parts { + if p.starts_with("UDP_STREAM:") { + udp = p.split(':').nth(1).unwrap_or("").to_string(); + } + if p.starts_with("TCP_LOG:") { + log = p.split(':').nth(1).unwrap_or("").to_string(); + } + } + if !udp.is_empty() || !log.is_empty() { + let _ = tx_events_inner.send(InternalEvent::ServiceConfig { + udp_port: udp, + log_port: log, + }); + } + } + let _ = tx_events_inner + .send(InternalEvent::CommandResponse(trimmed.to_string())); + } + line.clear(); + } + }); + while let Ok(cmd) = rx_cmd.recv() { + { + let config = shared_config.lock().unwrap(); + if config.version != current_version { + *stop_flag.lock().unwrap() = true; + let _ = tx_events.send(InternalEvent::Disconnected); + break; + } + } + if stream.write_all(format!("{} +", cmd).as_bytes()).is_err() { + let _ = tx_events.send(InternalEvent::Disconnected); + break; + } + thread::sleep(std::time::Duration::from_millis(100)); + } + let _ = tx_events.send(InternalEvent::Disconnected); + } + thread::sleep(std::time::Duration::from_secs(2)); + } +} + +pub fn tcp_log_worker(shared_config: Arc>, tx_events: Sender) { + let mut current_version = 0; + let mut current_addr = String::new(); + loop { + { + let config = shared_config.lock().unwrap(); + if config.version != current_version { + current_version = config.version; + current_addr = format!("{}:{}", config.ip, config.log_port); + } + } + if current_addr.is_empty() || current_addr.starts_with(":") { + thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + if let Ok(stream) = TcpStream::connect(¤t_addr) { + let mut reader = BufReader::new(stream); + let mut line = String::new(); + while reader.read_line(&mut line).is_ok() { + if shared_config.lock().unwrap().version != current_version { + break; + } + let trimmed = line.trim(); + if trimmed.starts_with("LOG ") { + let parts: Vec<&str> = trimmed[4..].splitn(2, ' ').collect(); + if parts.len() == 2 { + let _ = tx_events.send(InternalEvent::Log(LogEntry { + time: Local::now().format("%H:%M:%S%.3f").to_string(), + level: parts[0].to_string(), + message: parts[1].to_string(), + })); + } + } + line.clear(); + } + } + thread::sleep(std::time::Duration::from_secs(2)); + } +} + +#[allow(dead_code)] +pub fn recording_worker( + rx: Receiver<[f64; 2]>, + path: String, + signal_name: String, + tx_events: Sender, +) { + let file = match File::create(&path) { + Ok(f) => f, + Err(e) => { + let _ = tx_events.send(InternalEvent::RecordingError( + signal_name, + format!("File Error: {}", e), + )); + return; + } + }; + let schema = Arc::new(Schema::new(vec![ + Field::new("timestamp", DataType::Float64, false), + Field::new("value", DataType::Float64, false), + ])); + let mut writer = match ArrowWriter::try_new( + file, + schema.clone(), + Some(WriterProperties::builder().build()), + ) { + Ok(w) => w, + Err(e) => { + let _ = tx_events.send(InternalEvent::RecordingError( + signal_name, + format!("Parquet Error: {}", e), + )); + return; + } + }; + let (mut t_acc, mut v_acc) = (Vec::with_capacity(1000), Vec::with_capacity(1000)); + while let Ok([t, v]) = rx.recv() { + t_acc.push(t); + v_acc.push(v); + if t_acc.len() >= 1000 { + let batch = RecordBatch::try_new( + schema.clone(), + vec![ + Arc::new(Float64Array::from(t_acc.clone())), + Arc::new(Float64Array::from(v_acc.clone())), + ], + ) + .unwrap(); + let _ = writer.write(&batch); + t_acc.clear(); + v_acc.clear(); + } + } + if !t_acc.is_empty() { + let batch = RecordBatch::try_new( + schema.clone(), + vec![ + Arc::new(Float64Array::from(t_acc)), + Arc::new(Float64Array::from(v_acc)), + ], + ) + .unwrap(); + let _ = writer.write(&batch); + } + let _ = writer.close(); +} + +pub fn udp_worker( + shared_config: Arc>, + id_to_meta: Arc>>, + traced_data: Arc>>, + tx_events: Sender, +) { + let mut current_version = 0; + let mut socket: Option = None; + let mut last_seq: Option = None; + let mut last_warning_time = std::time::Instant::now(); + + loop { + let (ver, port) = { + let config = shared_config.lock().unwrap(); + (config.version, config.udp_port.clone()) + }; + if ver != current_version || socket.is_none() { + current_version = ver; + { + let mut base = BASE_TELEM_TS.lock().unwrap(); + *base = None; + } + if port.is_empty() { + socket = None; + continue; + } + let port_num: u16 = port.parse().unwrap_or(8081); + let s = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP)).ok(); + let mut bound = false; + if let Some(sock) = s { + let _ = sock.set_reuse_address(true); + #[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))] + let _ = sock.set_reuse_port(true); + let _ = sock.set_recv_buffer_size(10 * 1024 * 1024); + let addr = format!("0.0.0.0:{}", port_num) + .parse::() + .unwrap(); + if sock.bind(&addr.into()).is_ok() { + socket = Some(sock.into()); + bound = true; + } + } + if !bound { + thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + let _ = socket + .as_ref() + .unwrap() + .set_read_timeout(Some(std::time::Duration::from_millis(500))); + last_seq = None; + } + let s = if let Some(sock) = socket.as_ref() { + sock + } else { + thread::sleep(std::time::Duration::from_secs(1)); + continue; + }; + let mut buf = [0u8; 4096]; + let mut total_packets = 0u64; + loop { + if shared_config.lock().unwrap().version != current_version { + break; + } + if let Ok(n) = s.recv(&mut buf) { + total_packets += 1; + if (total_packets % 500) == 0 { + let _ = tx_events.send(InternalEvent::UdpStats(total_packets)); + } + if n < 20 { + continue; + } + if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0xDA7A57AD { + continue; + } + let seq = u32::from_le_bytes(buf[4..8].try_into().unwrap()); + if let Some(last) = last_seq { + if seq != last + 1 && seq > last { + let _ = tx_events.send(InternalEvent::UdpDropped(seq - last - 1)); + } + } + last_seq = Some(seq); + let count = u32::from_le_bytes(buf[16..20].try_into().unwrap()); + + let mut offset = 20; + let mut local_updates: HashMap> = HashMap::new(); + let mut last_values: HashMap = HashMap::new(); + let metas = id_to_meta.lock().unwrap(); + + if metas.is_empty() && count > 0 && last_warning_time.elapsed().as_secs() > 5 { + let _ = tx_events.send(InternalEvent::InternalLog( + "UDP received but Metadata empty. Still discovering?".to_string(), + )); + last_warning_time = std::time::Instant::now(); + } + + for _ in 0..count { + if offset + 16 > n { + break; + } + let id = u32::from_le_bytes(buf[offset..offset + 4].try_into().unwrap()); + let ts_raw = + u64::from_le_bytes(buf[offset + 4..offset + 12].try_into().unwrap()); + let size = + u32::from_le_bytes(buf[offset + 12..offset + 16].try_into().unwrap()); + offset += 16; + + if offset + size as usize > n { + break; + } + let data_slice = &buf[offset..offset + size as usize]; + + let mut base_ts_guard = BASE_TELEM_TS.lock().unwrap(); + if base_ts_guard.is_none() { + *base_ts_guard = Some(ts_raw); + } + + let base = base_ts_guard.unwrap(); + let ts_s = if ts_raw >= base { + (ts_raw - base) as f64 / 1000000.0 + } else { + 0.0 + }; + drop(base_ts_guard); + + if let Some(meta) = metas.get(&id) { + let _ = tx_events.send(InternalEvent::TelemMatched(id)); + + if meta.is_state { + let state_name = String::from_utf8_lossy(data_slice) + .trim_matches(char::from(0)) + .to_string(); + for name in &meta.names { + let _ = tx_events.send(InternalEvent::StateUpdate(name.clone(), state_name.clone())); + } + } else { + let t = meta.sig_type.as_str(); + let type_size = if meta.elements > 0 { size / meta.elements } else { size }; + + for i in 0..meta.elements { + let elem_offset = (i * type_size) as usize; + if elem_offset + type_size as usize > data_slice.len() { break; } + let elem_data = &data_slice[elem_offset..elem_offset + type_size as usize]; + + let val = match type_size { + 1 => { + if t.contains('u') { + elem_data[0] as f64 + } else { + (elem_data[0] as i8) as f64 + } + } + 2 => { + let b = elem_data[0..2].try_into().unwrap(); + if t.contains('u') { + u16::from_le_bytes(b) as f64 + } else { + i16::from_le_bytes(b) as f64 + } + } + 4 => { + let b = elem_data[0..4].try_into().unwrap(); + if t.contains("float") { + f32::from_le_bytes(b) as f64 + } else if t.contains('u') { + u32::from_le_bytes(b) as f64 + } else { + i32::from_le_bytes(b) as f64 + } + } + 8 => { + let b = elem_data[0..8].try_into().unwrap(); + if t.contains("float") { + f64::from_le_bytes(b) + } else if t.contains('u') { + u64::from_le_bytes(b) as f64 + } else { + i64::from_le_bytes(b) as f64 + } + } + _ => 0.0, + }; + + for name in &meta.names { + let target_name = if meta.elements > 1 { + format!("{}[{}]", name, i) + } else { + name.clone() + }; + local_updates + .entry(target_name.clone()) + .or_default() + .push([ts_s, val]); + last_values.insert(target_name, val); + } + } + } + } + offset += size as usize; + } + drop(metas); + if !local_updates.is_empty() { + let mut data_map = traced_data.lock().unwrap(); + for (name, new_points) in local_updates { + if let Some(entry) = data_map.get_mut(&name) { + for point in new_points { + entry.values.push_back(point); + if let Some(tx) = &entry.recording_tx { + let _ = tx.send(point); + } + } + if let Some(lv) = last_values.get(&name) { + entry.last_value = *lv; + } + while entry.values.len() > 100000 { + entry.values.pop_front(); + } + } + } + } + } + } + } +} diff --git a/compile_commands.json b/compile_commands.json index c8aabcd..12fd692 100644 --- a/compile_commands.json +++ b/compile_commands.json @@ -51,6 +51,7 @@ "-c", "-I../../../..//Source/Core/Types/Result", "-I../../../..//Source/Core/Types/Vec", + "-I../../../..//Source/Components/Interfaces/TCPLogger", "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L0Types", "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L1Portability", "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L2Objects", @@ -59,6 +60,7 @@ "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Logger", "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Configuration", "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L5GAMs", + "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4StateMachine", "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L1Portability", "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L3Services", "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4Messages", @@ -66,6 +68,7 @@ "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L5GAMs", "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L1Portability", "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L3Streams", + "-I/home/martino/Projects/marte_debug/dependency/MARTe2-components/Source/Components/DataSources/RealTimeThreadSynchronisation", "-fPIC", "-Wall", "-std=c++98", @@ -89,298 +92,5 @@ ], "directory": "/home/martino/Projects/marte_debug/Source/Components/Interfaces/DebugService", "output": "../../../..//Build/x86-linux/Components/Interfaces/DebugService/DebugService.o" - }, - { - "file": "UnitTests.cpp", - "arguments": [ - "g++", - "-c", - "-I../../Source/Core/Types/Result", - "-I../../Source/Core/Types/Vec", - "-I../../Source/Components/Interfaces/DebugService", - "-I../../Source/Components/Interfaces/TCPLogger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L0Types", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L2Objects", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L3Streams", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Logger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Configuration", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L3Services", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4LoggerService", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L3Streams", - "-fPIC", - "-Wall", - "-std=c++98", - "-Werror", - "-Wno-invalid-offsetof", - "-Wno-unused-variable", - "-fno-strict-aliasing", - "-frtti", - "-DMARTe2_TEST_ENVIRONMENT=GTest", - "-DARCHITECTURE=x86_gcc", - "-DENVIRONMENT=Linux", - "-DUSE_PTHREAD", - "-pthread", - "-Wno-deprecated-declarations", - "-Wno-unused-value", - "-g", - "-ggdb", - "UnitTests.cpp", - "-o", - "../../Build/x86-linux/Test/UnitTests/UnitTests/UnitTests.o" - ], - "directory": "/home/martino/Projects/marte_debug/Test/UnitTests", - "output": "../../Build/x86-linux/Test/UnitTests/UnitTests/UnitTests.o" - }, - { - "file": "SchedulerTest.cpp", - "arguments": [ - "g++", - "-c", - "-I../../Source/Core/Types/Result", - "-I../../Source/Core/Types/Vec", - "-I../../Source/Components/Interfaces/DebugService", - "-I../../Source/Components/Interfaces/TCPLogger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L0Types", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L2Objects", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L3Streams", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Logger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Configuration", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L6App", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L3Services", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4LoggerService", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L3Streams", - "-fPIC", - "-Wall", - "-std=c++98", - "-Werror", - "-Wno-invalid-offsetof", - "-Wno-unused-variable", - "-fno-strict-aliasing", - "-frtti", - "-DMARTe2_TEST_ENVIRONMENT=GTest", - "-DARCHITECTURE=x86_gcc", - "-DENVIRONMENT=Linux", - "-DUSE_PTHREAD", - "-pthread", - "-Wno-deprecated-declarations", - "-Wno-unused-value", - "-g", - "-ggdb", - "SchedulerTest.cpp", - "-o", - "../../Build/x86-linux/Test/Integration/Integration/SchedulerTest.o" - ], - "directory": "/home/martino/Projects/marte_debug/Test/Integration", - "output": "../../Build/x86-linux/Test/Integration/Integration/SchedulerTest.o" - }, - { - "file": "TraceTest.cpp", - "arguments": [ - "g++", - "-c", - "-I../../Source/Core/Types/Result", - "-I../../Source/Core/Types/Vec", - "-I../../Source/Components/Interfaces/DebugService", - "-I../../Source/Components/Interfaces/TCPLogger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L0Types", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L2Objects", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L3Streams", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Logger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Configuration", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L6App", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L3Services", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4LoggerService", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L3Streams", - "-fPIC", - "-Wall", - "-std=c++98", - "-Werror", - "-Wno-invalid-offsetof", - "-Wno-unused-variable", - "-fno-strict-aliasing", - "-frtti", - "-DMARTe2_TEST_ENVIRONMENT=GTest", - "-DARCHITECTURE=x86_gcc", - "-DENVIRONMENT=Linux", - "-DUSE_PTHREAD", - "-pthread", - "-Wno-deprecated-declarations", - "-Wno-unused-value", - "-g", - "-ggdb", - "TraceTest.cpp", - "-o", - "../../Build/x86-linux/Test/Integration/Integration/TraceTest.o" - ], - "directory": "/home/martino/Projects/marte_debug/Test/Integration", - "output": "../../Build/x86-linux/Test/Integration/Integration/TraceTest.o" - }, - { - "file": "ValidationTest.cpp", - "arguments": [ - "g++", - "-c", - "-I../../Source/Core/Types/Result", - "-I../../Source/Core/Types/Vec", - "-I../../Source/Components/Interfaces/DebugService", - "-I../../Source/Components/Interfaces/TCPLogger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L0Types", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L2Objects", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L3Streams", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Logger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Configuration", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L6App", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L3Services", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4LoggerService", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L3Streams", - "-fPIC", - "-Wall", - "-std=c++98", - "-Werror", - "-Wno-invalid-offsetof", - "-Wno-unused-variable", - "-fno-strict-aliasing", - "-frtti", - "-DMARTe2_TEST_ENVIRONMENT=GTest", - "-DARCHITECTURE=x86_gcc", - "-DENVIRONMENT=Linux", - "-DUSE_PTHREAD", - "-pthread", - "-Wno-deprecated-declarations", - "-Wno-unused-value", - "-g", - "-ggdb", - "ValidationTest.cpp", - "-o", - "../../Build/x86-linux/Test/Integration/Integration/ValidationTest.o" - ], - "directory": "/home/martino/Projects/marte_debug/Test/Integration", - "output": "../../Build/x86-linux/Test/Integration/Integration/ValidationTest.o" - }, - { - "file": "ConfigCommandTest.cpp", - "arguments": [ - "g++", - "-c", - "-I../../Source/Core/Types/Result", - "-I../../Source/Core/Types/Vec", - "-I../../Source/Components/Interfaces/DebugService", - "-I../../Source/Components/Interfaces/TCPLogger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L0Types", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L2Objects", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L3Streams", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Logger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Configuration", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L6App", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L3Services", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4LoggerService", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L3Streams", - "-fPIC", - "-Wall", - "-std=c++98", - "-Werror", - "-Wno-invalid-offsetof", - "-Wno-unused-variable", - "-fno-strict-aliasing", - "-frtti", - "-DMARTe2_TEST_ENVIRONMENT=GTest", - "-DARCHITECTURE=x86_gcc", - "-DENVIRONMENT=Linux", - "-DUSE_PTHREAD", - "-pthread", - "-Wno-deprecated-declarations", - "-Wno-unused-value", - "-g", - "-ggdb", - "ConfigCommandTest.cpp", - "-o", - "../../Build/x86-linux/Test/Integration/Integration/ConfigCommandTest.o" - ], - "directory": "/home/martino/Projects/marte_debug/Test/Integration", - "output": "../../Build/x86-linux/Test/Integration/Integration/ConfigCommandTest.o" - }, - { - "file": "IntegrationTests.cpp", - "arguments": [ - "g++", - "-c", - "-I../../Source/Core/Types/Result", - "-I../../Source/Core/Types/Vec", - "-I../../Source/Components/Interfaces/DebugService", - "-I../../Source/Components/Interfaces/TCPLogger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L0Types", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L2Objects", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L3Streams", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Logger", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Configuration", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L6App", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L3Services", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4Messages", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4LoggerService", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L5GAMs", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L1Portability", - "-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/FileSystem/L3Streams", - "-fPIC", - "-Wall", - "-std=c++98", - "-Werror", - "-Wno-invalid-offsetof", - "-Wno-unused-variable", - "-fno-strict-aliasing", - "-frtti", - "-DMARTe2_TEST_ENVIRONMENT=GTest", - "-DARCHITECTURE=x86_gcc", - "-DENVIRONMENT=Linux", - "-DUSE_PTHREAD", - "-pthread", - "-Wno-deprecated-declarations", - "-Wno-unused-value", - "-g", - "-ggdb", - "IntegrationTests.cpp", - "-o", - "../../Build/x86-linux/Test/Integration/Integration/IntegrationTests.o" - ], - "directory": "/home/martino/Projects/marte_debug/Test/Integration", - "output": "../../Build/x86-linux/Test/Integration/Integration/IntegrationTests.o" } ] \ No newline at end of file diff --git a/env.sh b/env.sh index e5a09dc..16cbf7c 100644 --- a/env.sh +++ b/env.sh @@ -6,11 +6,12 @@ export MARTe2_DIR=$DIR/dependency/MARTe2 export MARTe2_Components_DIR=$DIR/dependency/MARTe2-components export TARGET=x86-linux export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MARTe2_DIR/Build/$TARGET/Core -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MARTe2_Components_DIR/Build/$TARGET/Components -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$DIR/Build/$TARGET/Components/Interfaces/DebugService -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$DIR/Build/$TARGET/Components/Interfaces/TCPLogger +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MARTe2_Components_DIR/Build/$TARGET/Components/Components +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MARTe2_Components_DIR/Build/$TARGET/Components/DataSources/RealTimeThreadSynchronisation export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MARTe2_Components_DIR/Build/$TARGET/Components/DataSources/LinuxTimer export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MARTe2_Components_DIR/Build/$TARGET/Components/DataSources/LoggerDataSource export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MARTe2_Components_DIR/Build/$TARGET/Components/GAMs/IOGAM +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$DIR/Build/$TARGET/Components/Interfaces/DebugService +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$DIR/Build/$TARGET/Components/Interfaces/TCPLogger echo "MARTe2 Environment Set (MARTe2_DIR=$MARTe2_DIR)" echo "MARTe2 Components Environment Set (MARTe2_Components_DIR=$MARTe2_Components_DIR)"