Initial commit
This commit is contained in:
59
CMakeLists.txt
Normal file
59
CMakeLists.txt
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(marte_dev)
|
||||||
|
|
||||||
|
if(NOT DEFINED ENV{MARTe2_DIR})
|
||||||
|
message(FATAL_ERROR "MARTe2_DIR not set. Please source env.sh")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(MARTe2_DIR $ENV{MARTe2_DIR})
|
||||||
|
set(MARTe2_Components_DIR $ENV{MARTe2_Components_DIR})
|
||||||
|
set(TARGET $ENV{TARGET})
|
||||||
|
|
||||||
|
# Define Architecture macros
|
||||||
|
add_definitions(-DARCHITECTURE=x86_gcc)
|
||||||
|
add_definitions(-DENVIRONMENT=Linux)
|
||||||
|
add_definitions(-DMARTe2_TEST_ENVIRONMENT=GTest) # Optional
|
||||||
|
add_definitions(-DUSE_PTHREAD)
|
||||||
|
|
||||||
|
# Add -pthread flag
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L0Types
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L1Portability
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L2Objects
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L3Streams
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L4Configuration
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L4Events
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L4Logger
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L4Messages
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L5FILES
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L5GAMs
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L6App
|
||||||
|
${MARTe2_DIR}/Source/Core/Scheduler/L1Portability
|
||||||
|
${MARTe2_DIR}/Source/Core/Scheduler/L3Services
|
||||||
|
${MARTe2_DIR}/Source/Core/Scheduler/L4LoggerService
|
||||||
|
${MARTe2_DIR}/Source/Core/FileSystem/L1Portability
|
||||||
|
${MARTe2_DIR}/Source/Core/FileSystem/L3Streams
|
||||||
|
${MARTe2_Components_DIR}/Source/Components/DataSources/EpicsDataSource
|
||||||
|
${MARTe2_Components_DIR}/Source/Components/DataSources/FileDataSource
|
||||||
|
${MARTe2_Components_DIR}/Source/Components/GAMs/IOGAM
|
||||||
|
Source
|
||||||
|
Headers
|
||||||
|
)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SOURCES "Source/*.cpp")
|
||||||
|
|
||||||
|
add_library(${PROJECT_NAME} SHARED ${SOURCES})
|
||||||
|
|
||||||
|
# Target MARTe2 library
|
||||||
|
set(MARTe2_LIB ${MARTe2_DIR}/Build/${TARGET}/Core/libMARTe2.so)
|
||||||
|
set(IOGAM_LIB ${MARTe2_Components_DIR}/Build/${TARGET}/Components/GAMs/IOGAM/libIOGAM.so)
|
||||||
|
set(LinuxTimer_LIB ${MARTe2_Components_DIR}/Build/${TARGET}/Components/DataSources/LinuxTimer/libLinuxTimer.so)
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME}
|
||||||
|
${MARTe2_LIB}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_subdirectory(Test/UnitTests)
|
||||||
|
add_subdirectory(Test/Integration)
|
||||||
167
Headers/DebugBrokerWrapper.h
Normal file
167
Headers/DebugBrokerWrapper.h
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#ifndef DEBUGBROKERWRAPPER_H
|
||||||
|
#define DEBUGBROKERWRAPPER_H
|
||||||
|
|
||||||
|
#include "DebugService.h"
|
||||||
|
#include "BrokerI.h"
|
||||||
|
#include "MemoryMapBroker.h"
|
||||||
|
#include "ObjectRegistryDatabase.h"
|
||||||
|
#include "ReferenceT.h"
|
||||||
|
#include "ObjectBuilder.h"
|
||||||
|
#include "HighResolutionTimer.h"
|
||||||
|
|
||||||
|
// Original broker headers
|
||||||
|
#include "MemoryMapInputBroker.h"
|
||||||
|
#include "MemoryMapOutputBroker.h"
|
||||||
|
#include "MemoryMapSynchronisedInputBroker.h"
|
||||||
|
#include "MemoryMapSynchronisedOutputBroker.h"
|
||||||
|
#include "MemoryMapInterpolatedInputBroker.h"
|
||||||
|
#include "MemoryMapMultiBufferInputBroker.h"
|
||||||
|
#include "MemoryMapMultiBufferOutputBroker.h"
|
||||||
|
#include "MemoryMapSynchronisedMultiBufferInputBroker.h"
|
||||||
|
#include "MemoryMapSynchronisedMultiBufferOutputBroker.h"
|
||||||
|
|
||||||
|
namespace MARTe {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Base implementation for all debug brokers.
|
||||||
|
*/
|
||||||
|
class DebugBrokerHelper {
|
||||||
|
public:
|
||||||
|
static void Process(BrokerI* broker, DebugService* service, DebugSignalInfo** signalInfoPointers, uint32 numSignals) {
|
||||||
|
if (service == NULL_PTR(DebugService*)) return;
|
||||||
|
|
||||||
|
while (service->IsPaused()) {
|
||||||
|
Sleep::MSec(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signalInfoPointers != NULL_PTR(DebugSignalInfo**)) {
|
||||||
|
for (uint32 i = 0; i < numSignals; i++) {
|
||||||
|
DebugSignalInfo *s = signalInfoPointers[i];
|
||||||
|
if (s != NULL_PTR(DebugSignalInfo*)) {
|
||||||
|
if (s->isTracing || s->isForcing) {
|
||||||
|
uint32 size = broker->GetCopyByteSize(i);
|
||||||
|
service->ProcessSignal(s, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InitSignals(MemoryMapBroker* broker, DataSourceI &dataSourceIn, DebugService* &service, DebugSignalInfo** &signalInfoPointers, uint32 &numSignals, MemoryMapBrokerCopyTableEntry* copyTable, const char8* functionName, SignalDirection direction) {
|
||||||
|
numSignals = broker->GetNumberOfCopies();
|
||||||
|
if (numSignals > 0) {
|
||||||
|
signalInfoPointers = new DebugSignalInfo*[numSignals];
|
||||||
|
for (uint32 i=0; i<numSignals; i++) signalInfoPointers[i] = NULL_PTR(DebugSignalInfo*);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReferenceContainer *root = ObjectRegistryDatabase::Instance();
|
||||||
|
Reference serviceRef = root->Find("DebugService");
|
||||||
|
if (serviceRef.IsValid()) {
|
||||||
|
service = dynamic_cast<DebugService*>(serviceRef.operator->());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service && (copyTable != NULL_PTR(MemoryMapBrokerCopyTableEntry*))) {
|
||||||
|
StreamString dsPath;
|
||||||
|
DebugService::GetFullObjectName(dataSourceIn, dsPath);
|
||||||
|
|
||||||
|
for (uint32 i = 0; i < numSignals; i++) {
|
||||||
|
void *addr = copyTable[i].dataSourcePointer;
|
||||||
|
TypeDescriptor type = copyTable[i].type;
|
||||||
|
|
||||||
|
uint32 dsIdx = broker->GetDSCopySignalIndex(i);
|
||||||
|
StreamString signalName;
|
||||||
|
if (!dataSourceIn.GetSignalName(dsIdx, signalName)) signalName = "Unknown";
|
||||||
|
|
||||||
|
// 1. Register canonical DataSource name (Absolute)
|
||||||
|
StreamString dsFullName;
|
||||||
|
dsFullName.Printf("%s.%s", dsPath.Buffer(), signalName.Buffer());
|
||||||
|
service->RegisterSignal(addr, type, dsFullName.Buffer());
|
||||||
|
|
||||||
|
// 2. Also register absolute GAM alias
|
||||||
|
if (functionName != NULL_PTR(const char8*)) {
|
||||||
|
StreamString gamFullName;
|
||||||
|
const char8* dirStr = (direction == InputSignals) ? "In" : "Out";
|
||||||
|
Reference gamRef = ObjectRegistryDatabase::Instance()->Find(functionName);
|
||||||
|
if (gamRef.IsValid()) {
|
||||||
|
StreamString absGamPath;
|
||||||
|
DebugService::GetFullObjectName(*(gamRef.operator->()), absGamPath);
|
||||||
|
gamFullName.Printf("%s.%s.%s", absGamPath.Buffer(), dirStr, signalName.Buffer());
|
||||||
|
} else {
|
||||||
|
gamFullName.Printf("Root.%s.%s.%s", functionName, dirStr, signalName.Buffer());
|
||||||
|
}
|
||||||
|
signalInfoPointers[i] = service->RegisterSignal(addr, type, gamFullName.Buffer());
|
||||||
|
} else {
|
||||||
|
signalInfoPointers[i] = service->RegisterSignal(addr, type, dsFullName.Buffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DECLARE_DEBUG_BROKER_COMMON(BaseClass) \
|
||||||
|
Debug##BaseClass() : BaseClass() { \
|
||||||
|
service = NULL_PTR(DebugService*); \
|
||||||
|
signalInfoPointers = NULL_PTR(DebugSignalInfo**); \
|
||||||
|
numSignals = 0; \
|
||||||
|
} \
|
||||||
|
virtual ~Debug##BaseClass() { \
|
||||||
|
if (signalInfoPointers) delete[] signalInfoPointers; \
|
||||||
|
} \
|
||||||
|
virtual bool Execute() { \
|
||||||
|
bool ret = BaseClass::Execute(); \
|
||||||
|
if (ret) DebugBrokerHelper::Process(this, service, signalInfoPointers, numSignals); \
|
||||||
|
return ret; \
|
||||||
|
} \
|
||||||
|
private: \
|
||||||
|
DebugService *service; \
|
||||||
|
DebugSignalInfo **signalInfoPointers; \
|
||||||
|
uint32 numSignals;
|
||||||
|
|
||||||
|
#define DECLARE_DEBUG_BROKER(BaseClass) \
|
||||||
|
class Debug##BaseClass : public BaseClass { \
|
||||||
|
public: \
|
||||||
|
DECLARE_DEBUG_BROKER_COMMON(BaseClass) \
|
||||||
|
virtual bool Init(SignalDirection direction, DataSourceI &ds, const char8 *const name, void *gamMem) { \
|
||||||
|
bool ret = BaseClass::Init(direction, ds, name, gamMem); \
|
||||||
|
if (ret) DebugBrokerHelper::InitSignals(this, ds, service, signalInfoPointers, numSignals, this->copyTable, name, direction); \
|
||||||
|
return ret; \
|
||||||
|
} \
|
||||||
|
virtual bool Init(SignalDirection direction, DataSourceI &ds, const char8 *const name, void *gamMem, const bool optim) { \
|
||||||
|
bool ret = BaseClass::Init(direction, ds, name, gamMem, optim); \
|
||||||
|
if (ret) DebugBrokerHelper::InitSignals(this, ds, service, signalInfoPointers, numSignals, this->copyTable, name, direction); \
|
||||||
|
return ret; \
|
||||||
|
} \
|
||||||
|
}; \
|
||||||
|
class Debug##BaseClass##Builder : public ObjectBuilder { \
|
||||||
|
public: \
|
||||||
|
virtual Object *Build(HeapI* const heap) const { return new (heap) Debug##BaseClass(); } \
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DECLARE_DEBUG_BROKER_NO_OPTIM(BaseClass) \
|
||||||
|
class Debug##BaseClass : public BaseClass { \
|
||||||
|
public: \
|
||||||
|
DECLARE_DEBUG_BROKER_COMMON(BaseClass) \
|
||||||
|
virtual bool Init(SignalDirection direction, DataSourceI &ds, const char8 *const name, void *gamMem) { \
|
||||||
|
bool ret = BaseClass::Init(direction, ds, name, gamMem); \
|
||||||
|
if (ret) DebugBrokerHelper::InitSignals(this, ds, service, signalInfoPointers, numSignals, this->copyTable, name, direction); \
|
||||||
|
return ret; \
|
||||||
|
} \
|
||||||
|
}; \
|
||||||
|
class Debug##BaseClass##Builder : public ObjectBuilder { \
|
||||||
|
public: \
|
||||||
|
virtual Object *Build(HeapI* const heap) const { return new (heap) Debug##BaseClass(); } \
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_DEBUG_BROKER(MemoryMapInputBroker)
|
||||||
|
DECLARE_DEBUG_BROKER(MemoryMapOutputBroker)
|
||||||
|
DECLARE_DEBUG_BROKER(MemoryMapSynchronisedInputBroker)
|
||||||
|
DECLARE_DEBUG_BROKER(MemoryMapSynchronisedOutputBroker)
|
||||||
|
DECLARE_DEBUG_BROKER_NO_OPTIM(MemoryMapInterpolatedInputBroker)
|
||||||
|
DECLARE_DEBUG_BROKER(MemoryMapMultiBufferInputBroker)
|
||||||
|
DECLARE_DEBUG_BROKER(MemoryMapMultiBufferOutputBroker)
|
||||||
|
DECLARE_DEBUG_BROKER(MemoryMapSynchronisedMultiBufferInputBroker)
|
||||||
|
DECLARE_DEBUG_BROKER(MemoryMapSynchronisedMultiBufferOutputBroker)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
133
Headers/DebugCore.h
Normal file
133
Headers/DebugCore.h
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#ifndef DEBUGCORE_H
|
||||||
|
#define DEBUGCORE_H
|
||||||
|
|
||||||
|
#include "CompilerTypes.h"
|
||||||
|
#include "TypeDescriptor.h"
|
||||||
|
#include "StreamString.h"
|
||||||
|
#include "MemoryOperationsHelper.h"
|
||||||
|
|
||||||
|
namespace MARTe {
|
||||||
|
|
||||||
|
struct DebugSignalInfo {
|
||||||
|
void* memoryAddress;
|
||||||
|
TypeDescriptor type;
|
||||||
|
StreamString name;
|
||||||
|
volatile bool isTracing;
|
||||||
|
volatile bool isForcing;
|
||||||
|
uint8 forcedValue[1024];
|
||||||
|
uint32 internalID;
|
||||||
|
volatile uint32 decimationFactor;
|
||||||
|
volatile uint32 decimationCounter;
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct TraceHeader {
|
||||||
|
uint32 magic; // 0xDA7A57AD
|
||||||
|
uint32 seq; // Sequence number
|
||||||
|
uint64 timestamp; // HighRes timestamp
|
||||||
|
uint32 count; // Number of samples in payload
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
class TraceRingBuffer {
|
||||||
|
public:
|
||||||
|
TraceRingBuffer() {
|
||||||
|
bufferSize = 0;
|
||||||
|
buffer = NULL_PTR(uint8*);
|
||||||
|
readIndex = 0;
|
||||||
|
writeIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~TraceRingBuffer() {
|
||||||
|
if (buffer != NULL_PTR(uint8*)) {
|
||||||
|
delete[] buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Init(uint32 size) {
|
||||||
|
if (buffer != NULL_PTR(uint8*)) {
|
||||||
|
delete[] buffer;
|
||||||
|
}
|
||||||
|
bufferSize = size;
|
||||||
|
buffer = new uint8[bufferSize];
|
||||||
|
readIndex = 0;
|
||||||
|
writeIndex = 0;
|
||||||
|
return (buffer != NULL_PTR(uint8*));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Push(uint32 signalID, void* data, uint32 size) {
|
||||||
|
uint32 packetSize = 4 + 4 + size;
|
||||||
|
uint32 read = readIndex;
|
||||||
|
uint32 write = writeIndex;
|
||||||
|
uint32 available = (read <= write) ? (bufferSize - (write - read) - 1) : (read - write - 1);
|
||||||
|
|
||||||
|
if (available < packetSize) return false;
|
||||||
|
|
||||||
|
// Use temporary write index to ensure atomic update
|
||||||
|
uint32 tempWrite = write;
|
||||||
|
WriteToBuffer(&tempWrite, &signalID, 4);
|
||||||
|
WriteToBuffer(&tempWrite, &size, 4);
|
||||||
|
WriteToBuffer(&tempWrite, data, size);
|
||||||
|
|
||||||
|
// Final atomic update
|
||||||
|
writeIndex = tempWrite;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Pop(uint32 &signalID, void* dataBuffer, uint32 &size, uint32 maxSize) {
|
||||||
|
uint32 read = readIndex;
|
||||||
|
uint32 write = writeIndex;
|
||||||
|
if (read == write) return false;
|
||||||
|
|
||||||
|
uint32 tempRead = read;
|
||||||
|
uint32 tempId, tempSize;
|
||||||
|
ReadFromBuffer(&tempRead, &tempId, 4);
|
||||||
|
ReadFromBuffer(&tempRead, &tempSize, 4);
|
||||||
|
|
||||||
|
if (tempSize > maxSize) {
|
||||||
|
// Error case: drop data up to writeIndex
|
||||||
|
readIndex = write;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadFromBuffer(&tempRead, dataBuffer, tempSize);
|
||||||
|
signalID = tempId;
|
||||||
|
size = tempSize;
|
||||||
|
|
||||||
|
readIndex = tempRead;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 Count() {
|
||||||
|
uint32 read = readIndex;
|
||||||
|
uint32 write = writeIndex;
|
||||||
|
if (write >= read) return write - read;
|
||||||
|
return bufferSize - (read - write);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void WriteToBuffer(uint32 *idx, void* src, uint32 count) {
|
||||||
|
uint8* s = (uint8*)src;
|
||||||
|
for (uint32 i=0; i<count; i++) {
|
||||||
|
buffer[*idx] = s[i];
|
||||||
|
*idx = (*idx + 1) % bufferSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadFromBuffer(uint32 *idx, void* dst, uint32 count) {
|
||||||
|
uint8* d = (uint8*)dst;
|
||||||
|
for (uint32 i=0; i<count; i++) {
|
||||||
|
d[i] = buffer[*idx];
|
||||||
|
*idx = (*idx + 1) % bufferSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volatile uint32 readIndex;
|
||||||
|
volatile uint32 writeIndex;
|
||||||
|
uint32 bufferSize;
|
||||||
|
uint8 *buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
141
Headers/DebugService.h
Normal file
141
Headers/DebugService.h
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#ifndef DEBUGSERVICE_H
|
||||||
|
#define DEBUGSERVICE_H
|
||||||
|
|
||||||
|
#include "DataSourceI.h"
|
||||||
|
#include "GAM.h"
|
||||||
|
#include "MessageI.h"
|
||||||
|
#include "StreamString.h"
|
||||||
|
#include "BasicUDPSocket.h"
|
||||||
|
#include "BasicTCPSocket.h"
|
||||||
|
#include "ReferenceContainer.h"
|
||||||
|
#include "SingleThreadService.h"
|
||||||
|
#include "EmbeddedServiceMethodBinderI.h"
|
||||||
|
#include "FastMath.h"
|
||||||
|
#include "CompilerTypes.h"
|
||||||
|
#include "Object.h"
|
||||||
|
#include "DebugCore.h"
|
||||||
|
#include "ClassRegistryDatabase.h"
|
||||||
|
#include "ErrorManagement.h"
|
||||||
|
#include "AdvancedErrorManagement.h"
|
||||||
|
#include "LoggerConsumerI.h"
|
||||||
|
#include "Threads.h"
|
||||||
|
#include "EventSem.h"
|
||||||
|
|
||||||
|
namespace MARTe {
|
||||||
|
|
||||||
|
struct LogEntry {
|
||||||
|
ErrorManagement::ErrorInformation info;
|
||||||
|
char8 description[MAX_ERROR_MESSAGE_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SignalAlias {
|
||||||
|
StreamString name;
|
||||||
|
uint32 signalIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebugService : public ReferenceContainer, public MessageI, public EmbeddedServiceMethodBinderI, public LoggerConsumerI {
|
||||||
|
public:
|
||||||
|
CLASS_REGISTER_DECLARATION()
|
||||||
|
|
||||||
|
DebugService();
|
||||||
|
virtual ~DebugService();
|
||||||
|
|
||||||
|
virtual bool Initialise(StructuredDataI & data);
|
||||||
|
|
||||||
|
DebugSignalInfo* RegisterSignal(void* memoryAddress, TypeDescriptor type, const char8* name);
|
||||||
|
void ProcessSignal(DebugSignalInfo* signalInfo, uint32 size);
|
||||||
|
|
||||||
|
virtual ErrorManagement::ErrorType Execute(ExecutionInfo & info);
|
||||||
|
virtual void ConsumeLogMessage(LoggerPage *logPage);
|
||||||
|
static void LogCallback(const ErrorManagement::ErrorInformation &errorInfo, const char8 * const errorDescription);
|
||||||
|
void InsertLogIntoQueue(LoggerPage *logPage);
|
||||||
|
|
||||||
|
bool IsPaused() const { return isPaused; }
|
||||||
|
void SetPaused(bool paused) { isPaused = paused; }
|
||||||
|
|
||||||
|
static bool GetFullObjectName(const Object &obj, StreamString &fullPath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void HandleCommand(StreamString cmd, BasicTCPSocket *client);
|
||||||
|
uint32 ForceSignal(const char8* name, const char8* valueStr);
|
||||||
|
uint32 UnforceSignal(const char8* name);
|
||||||
|
uint32 TraceSignal(const char8* name, bool enable, uint32 decimation = 1);
|
||||||
|
void Discover(BasicTCPSocket *client);
|
||||||
|
void ListNodes(const char8* path, BasicTCPSocket *client);
|
||||||
|
void InfoNode(const char8* path, BasicTCPSocket *client);
|
||||||
|
uint32 ExportTree(ReferenceContainer *container, StreamString &json);
|
||||||
|
void PatchRegistry();
|
||||||
|
|
||||||
|
ErrorManagement::ErrorType Server(ExecutionInfo & info);
|
||||||
|
ErrorManagement::ErrorType Streamer(ExecutionInfo & info);
|
||||||
|
ErrorManagement::ErrorType LogStreamer(ExecutionInfo & info);
|
||||||
|
|
||||||
|
uint16 controlPort;
|
||||||
|
uint16 streamPort;
|
||||||
|
uint16 logPort;
|
||||||
|
StreamString streamIP;
|
||||||
|
bool isServer;
|
||||||
|
bool suppressTimeoutLogs;
|
||||||
|
volatile bool isPaused;
|
||||||
|
|
||||||
|
BasicTCPSocket tcpServer;
|
||||||
|
BasicUDPSocket udpSocket;
|
||||||
|
BasicTCPSocket logServer;
|
||||||
|
|
||||||
|
class ServiceBinder : public EmbeddedServiceMethodBinderI {
|
||||||
|
public:
|
||||||
|
enum ServiceType { ServerType, StreamerType, LogStreamerType };
|
||||||
|
ServiceBinder(DebugService *parent, ServiceType type) : parent(parent), type(type) {}
|
||||||
|
virtual ErrorManagement::ErrorType Execute(ExecutionInfo & info) {
|
||||||
|
if (type == StreamerType) return parent->Streamer(info);
|
||||||
|
if (type == LogStreamerType) return parent->LogStreamer(info);
|
||||||
|
return parent->Server(info);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
DebugService *parent;
|
||||||
|
ServiceType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
ServiceBinder binderServer;
|
||||||
|
ServiceBinder binderStreamer;
|
||||||
|
ServiceBinder binderLogStreamer;
|
||||||
|
|
||||||
|
SingleThreadService threadService;
|
||||||
|
SingleThreadService streamerService;
|
||||||
|
SingleThreadService logStreamerService;
|
||||||
|
|
||||||
|
ThreadIdentifier serverThreadId;
|
||||||
|
ThreadIdentifier streamerThreadId;
|
||||||
|
ThreadIdentifier logStreamerThreadId;
|
||||||
|
|
||||||
|
static const uint32 MAX_SIGNALS = 4096;
|
||||||
|
DebugSignalInfo signals[MAX_SIGNALS];
|
||||||
|
uint32 numberOfSignals;
|
||||||
|
|
||||||
|
static const uint32 MAX_ALIASES = 8192;
|
||||||
|
SignalAlias aliases[MAX_ALIASES];
|
||||||
|
uint32 numberOfAliases;
|
||||||
|
|
||||||
|
FastPollingMutexSem mutex;
|
||||||
|
TraceRingBuffer traceBuffer;
|
||||||
|
|
||||||
|
static const uint32 MAX_CLIENTS = 16;
|
||||||
|
BasicTCPSocket* activeClients[MAX_CLIENTS];
|
||||||
|
FastPollingMutexSem clientsMutex;
|
||||||
|
|
||||||
|
BasicTCPSocket* activeLogClients[MAX_CLIENTS];
|
||||||
|
FastPollingMutexSem logClientsMutex;
|
||||||
|
|
||||||
|
static const uint32 LOG_QUEUE_SIZE = 1024;
|
||||||
|
LogEntry logQueue[LOG_QUEUE_SIZE];
|
||||||
|
volatile uint32 logQueueRead;
|
||||||
|
volatile uint32 logQueueWrite;
|
||||||
|
EventSem logEvent;
|
||||||
|
|
||||||
|
static DebugService* instance;
|
||||||
|
static ErrorManagement::ErrorProcessFunctionType originalLogCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
10
Makefile
Normal file
10
Makefile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
all: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
mkdir -p Build && cd Build && . ../env.sh && cmake -DCMAKE_BUILD_TYPE=Debug .. && make
|
||||||
|
ln -sf libmarte_dev.so Build/DebugService.so
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf Build
|
||||||
|
|
||||||
|
.PHONY: all build clean
|
||||||
48
README.md
Normal file
48
README.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# MARTe2 Universal Debugging & Observability Suite
|
||||||
|
|
||||||
|
A professional-grade, zero-code-change debugging suite for the MARTe2 real-time framework.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Runtime Registry Patching**: Instruments all MARTe2 Brokers automatically at startup.
|
||||||
|
- **Hierarchical Tree Explorer**: Recursive visualization of the `ObjectRegistryDatabase`, including GAMs, DataSources, and Signals.
|
||||||
|
- **Real-Time Execution Control**: Pause and Resume application logic globally to perform static inspection.
|
||||||
|
- **High-Speed Telemetry**: Visual oscilloscope with sub-millisecond precision via UDP.
|
||||||
|
- **Persistent Forcing**: Type-aware signal overrides (Last-Writer-Wins) with persistent re-application.
|
||||||
|
- **Isolated Log Streaming**: Dedicated TCP channel for real-time framework logs to ensure command responsiveness.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### 1. C++ Core (`libmarte_dev.so`)
|
||||||
|
The core service that handles registry patching, TCP/UDP communication, and real-time safe data capture.
|
||||||
|
|
||||||
|
### 2. Rust GUI Client (`marte_debug_gui`)
|
||||||
|
A native, multi-threaded dashboard built with `egui`.
|
||||||
|
- **Side Panel**: Collapsible application tree and signal navigator.
|
||||||
|
- **Bottom Panel**: Advanced log terminal with Regex filtering and priority levels.
|
||||||
|
- **Right Panel**: Active Trace and Force management.
|
||||||
|
- **Central Pane**: High-frequency oscilloscope.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Build
|
||||||
|
```bash
|
||||||
|
# Build C++ Core
|
||||||
|
cd Build && cmake .. && make -j$(nproc)
|
||||||
|
|
||||||
|
# Build GUI Client
|
||||||
|
cd Tools/gui_client
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run
|
||||||
|
1. Start your MARTe2 application with the `DebugService` enabled.
|
||||||
|
2. Launch the GUI:
|
||||||
|
```bash
|
||||||
|
./Tools/gui_client/target/release/marte_debug_gui
|
||||||
|
```
|
||||||
|
|
||||||
|
## Communication Ports
|
||||||
|
- **8080 (TCP)**: Commands (TREE, FORCE, TRACE, PAUSE).
|
||||||
|
- **8082 (TCP)**: Real-time framework logs.
|
||||||
|
- **8081 (UDP)**: Signal telemetry data.
|
||||||
910
Source/DebugService.cpp
Normal file
910
Source/DebugService.cpp
Normal file
@@ -0,0 +1,910 @@
|
|||||||
|
#include "DebugService.h"
|
||||||
|
#include "StandardParser.h"
|
||||||
|
#include "StreamString.h"
|
||||||
|
#include "BasicSocket.h"
|
||||||
|
#include "DebugBrokerWrapper.h"
|
||||||
|
#include "ObjectRegistryDatabase.h"
|
||||||
|
#include "ClassRegistryItem.h"
|
||||||
|
#include "ObjectBuilder.h"
|
||||||
|
#include "TypeConversion.h"
|
||||||
|
#include "HighResolutionTimer.h"
|
||||||
|
#include "ConfigurationDatabase.h"
|
||||||
|
#include "GAM.h"
|
||||||
|
|
||||||
|
// Explicitly include target brokers for templating
|
||||||
|
#include "MemoryMapInputBroker.h"
|
||||||
|
#include "MemoryMapOutputBroker.h"
|
||||||
|
#include "MemoryMapSynchronisedInputBroker.h"
|
||||||
|
#include "MemoryMapSynchronisedOutputBroker.h"
|
||||||
|
#include "MemoryMapInterpolatedInputBroker.h"
|
||||||
|
#include "MemoryMapMultiBufferInputBroker.h"
|
||||||
|
#include "MemoryMapMultiBufferOutputBroker.h"
|
||||||
|
#include "MemoryMapSynchronisedMultiBufferInputBroker.h"
|
||||||
|
#include "MemoryMapSynchronisedMultiBufferOutputBroker.h"
|
||||||
|
|
||||||
|
namespace MARTe {
|
||||||
|
|
||||||
|
DebugService* DebugService::instance = NULL_PTR(DebugService*);
|
||||||
|
ErrorManagement::ErrorProcessFunctionType DebugService::originalLogCallback = NULL_PTR(ErrorManagement::ErrorProcessFunctionType);
|
||||||
|
|
||||||
|
static void EscapeJson(const char8* src, StreamString &dst) {
|
||||||
|
if (src == NULL_PTR(const char8*)) return;
|
||||||
|
while (*src != '\0') {
|
||||||
|
if (*src == '"') dst += "\\\"";
|
||||||
|
else if (*src == '\\') dst += "\\\\";
|
||||||
|
else if (*src == '\n') dst += "\\n";
|
||||||
|
else if (*src == '\r') dst += "\\r";
|
||||||
|
else if (*src == '\t') dst += "\\t";
|
||||||
|
else dst += *src;
|
||||||
|
src++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CLASS_REGISTER(DebugService, "1.0")
|
||||||
|
|
||||||
|
DebugService::DebugService() :
|
||||||
|
ReferenceContainer(), EmbeddedServiceMethodBinderI(), LoggerConsumerI(),
|
||||||
|
binderServer(this, ServiceBinder::ServerType),
|
||||||
|
binderStreamer(this, ServiceBinder::StreamerType),
|
||||||
|
binderLogStreamer(this, ServiceBinder::LogStreamerType),
|
||||||
|
threadService(binderServer),
|
||||||
|
streamerService(binderStreamer),
|
||||||
|
logStreamerService(binderLogStreamer)
|
||||||
|
{
|
||||||
|
controlPort = 0;
|
||||||
|
streamPort = 8081;
|
||||||
|
logPort = 8082;
|
||||||
|
streamIP = "127.0.0.1";
|
||||||
|
numberOfSignals = 0;
|
||||||
|
numberOfAliases = 0;
|
||||||
|
isServer = false;
|
||||||
|
suppressTimeoutLogs = true;
|
||||||
|
isPaused = false;
|
||||||
|
for (uint32 i=0; i<MAX_CLIENTS; i++) {
|
||||||
|
activeClients[i] = NULL_PTR(BasicTCPSocket*);
|
||||||
|
activeLogClients[i] = NULL_PTR(BasicTCPSocket*);
|
||||||
|
}
|
||||||
|
logQueueRead = 0;
|
||||||
|
logQueueWrite = 0;
|
||||||
|
serverThreadId = InvalidThreadIdentifier;
|
||||||
|
streamerThreadId = InvalidThreadIdentifier;
|
||||||
|
logStreamerThreadId = InvalidThreadIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugService::~DebugService() {
|
||||||
|
if (instance == this) {
|
||||||
|
if (ErrorManagement::errorMessageProcessFunction == &DebugService::LogCallback) {
|
||||||
|
ErrorManagement::SetErrorProcessFunction(originalLogCallback);
|
||||||
|
}
|
||||||
|
instance = NULL_PTR(DebugService*);
|
||||||
|
}
|
||||||
|
|
||||||
|
threadService.Stop();
|
||||||
|
streamerService.Stop();
|
||||||
|
logStreamerService.Stop();
|
||||||
|
|
||||||
|
tcpServer.Close();
|
||||||
|
udpSocket.Close();
|
||||||
|
logServer.Close();
|
||||||
|
|
||||||
|
for (uint32 i=0; i<MAX_CLIENTS; i++) {
|
||||||
|
if (activeClients[i] != NULL_PTR(BasicTCPSocket*)) {
|
||||||
|
activeClients[i]->Close();
|
||||||
|
delete activeClients[i];
|
||||||
|
}
|
||||||
|
if (activeLogClients[i] != NULL_PTR(BasicTCPSocket*)) {
|
||||||
|
activeLogClients[i]->Close();
|
||||||
|
delete activeLogClients[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DebugService::Initialise(StructuredDataI & data) {
|
||||||
|
if (!ReferenceContainer::Initialise(data)) return false;
|
||||||
|
|
||||||
|
if (!data.Read("ControlPort", controlPort)) {
|
||||||
|
(void)data.Read("TcpPort", controlPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlPort > 0) {
|
||||||
|
isServer = true;
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.Read("StreamPort", streamPort)) {
|
||||||
|
(void)data.Read("UdpPort", streamPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.Read("LogPort", logPort)) {
|
||||||
|
(void)data.Read("TcpLogPort", logPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)data.Read("StreamIP", streamIP);
|
||||||
|
|
||||||
|
uint32 suppress = 1;
|
||||||
|
if (data.Read("SuppressTimeoutLogs", suppress)) {
|
||||||
|
suppressTimeoutLogs = (suppress == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isServer) {
|
||||||
|
if (ErrorManagement::errorMessageProcessFunction != &DebugService::LogCallback) {
|
||||||
|
originalLogCallback = ErrorManagement::errorMessageProcessFunction;
|
||||||
|
ErrorManagement::SetErrorProcessFunction(&DebugService::LogCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!traceBuffer.Init(1024 * 1024)) return false;
|
||||||
|
|
||||||
|
(void)logEvent.Create();
|
||||||
|
|
||||||
|
PatchRegistry();
|
||||||
|
|
||||||
|
ConfigurationDatabase threadData;
|
||||||
|
threadData.Write("Timeout", (uint32)1000);
|
||||||
|
threadService.Initialise(threadData);
|
||||||
|
streamerService.Initialise(threadData);
|
||||||
|
logStreamerService.Initialise(threadData);
|
||||||
|
|
||||||
|
if (!tcpServer.Open()) {
|
||||||
|
REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to open TCP Server Socket");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!udpSocket.Open()) {
|
||||||
|
REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to open UDP Socket");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!logServer.Open()) {
|
||||||
|
REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to open Log Server Socket");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threadService.Start() != ErrorManagement::NoError) {
|
||||||
|
REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to start Server thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (streamerService.Start() != ErrorManagement::NoError) {
|
||||||
|
REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to start Streamer thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (logStreamerService.Start() != ErrorManagement::NoError) {
|
||||||
|
REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to start LogStreamer thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PatchItemInternal(const char8* className, ObjectBuilder* builder) {
|
||||||
|
ClassRegistryDatabase *db = ClassRegistryDatabase::Instance();
|
||||||
|
ClassRegistryItem *item = (ClassRegistryItem*)db->Find(className);
|
||||||
|
if (item != NULL_PTR(ClassRegistryItem*)) {
|
||||||
|
item->SetObjectBuilder(builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugService::PatchRegistry() {
|
||||||
|
static DebugMemoryMapInputBrokerBuilder b1;
|
||||||
|
PatchItemInternal("MemoryMapInputBroker", &b1);
|
||||||
|
static DebugMemoryMapOutputBrokerBuilder b2;
|
||||||
|
PatchItemInternal("MemoryMapOutputBroker", &b2);
|
||||||
|
static DebugMemoryMapSynchronisedInputBrokerBuilder b3;
|
||||||
|
PatchItemInternal("MemoryMapSynchronisedInputBroker", &b3);
|
||||||
|
static DebugMemoryMapSynchronisedOutputBrokerBuilder b4;
|
||||||
|
PatchItemInternal("MemoryMapSynchronisedOutputBroker", &b4);
|
||||||
|
static DebugMemoryMapInterpolatedInputBrokerBuilder b5;
|
||||||
|
PatchItemInternal("MemoryMapInterpolatedInputBroker", &b5);
|
||||||
|
static DebugMemoryMapMultiBufferInputBrokerBuilder b6;
|
||||||
|
PatchItemInternal("MemoryMapMultiBufferInputBroker", &b6);
|
||||||
|
static DebugMemoryMapMultiBufferOutputBrokerBuilder b7;
|
||||||
|
PatchItemInternal("MemoryMapMultiBufferOutputBroker", &b7);
|
||||||
|
static DebugMemoryMapSynchronisedMultiBufferInputBrokerBuilder b8;
|
||||||
|
PatchItemInternal("MemoryMapSynchronisedMultiBufferInputBroker", &b8);
|
||||||
|
static DebugMemoryMapSynchronisedMultiBufferOutputBrokerBuilder b9;
|
||||||
|
PatchItemInternal("MemoryMapSynchronisedMultiBufferOutputBroker", &b9);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugService::ProcessSignal(DebugSignalInfo* s, uint32 size) {
|
||||||
|
if (s != NULL_PTR(DebugSignalInfo*)) {
|
||||||
|
if (s->isForcing) {
|
||||||
|
MemoryOperationsHelper::Copy(s->memoryAddress, s->forcedValue, size);
|
||||||
|
}
|
||||||
|
if (s->isTracing) {
|
||||||
|
if (s->decimationFactor <= 1) {
|
||||||
|
(void)traceBuffer.Push(s->internalID, s->memoryAddress, size);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (s->decimationCounter == 0) {
|
||||||
|
(void)traceBuffer.Push(s->internalID, s->memoryAddress, size);
|
||||||
|
s->decimationCounter = s->decimationFactor - 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s->decimationCounter--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugSignalInfo* DebugService::RegisterSignal(void* memoryAddress, TypeDescriptor type, const char8* name) {
|
||||||
|
mutex.FastLock();
|
||||||
|
DebugSignalInfo* res = NULL_PTR(DebugSignalInfo*);
|
||||||
|
|
||||||
|
uint32 sigIdx = 0xFFFFFFFF;
|
||||||
|
for(uint32 i=0; i<numberOfSignals; i++) {
|
||||||
|
if(signals[i].memoryAddress == memoryAddress) {
|
||||||
|
res = &signals[i];
|
||||||
|
sigIdx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == NULL_PTR(DebugSignalInfo*) && numberOfSignals < MAX_SIGNALS) {
|
||||||
|
sigIdx = numberOfSignals;
|
||||||
|
res = &signals[numberOfSignals];
|
||||||
|
res->memoryAddress = memoryAddress;
|
||||||
|
res->type = type;
|
||||||
|
res->name = name;
|
||||||
|
res->isTracing = false;
|
||||||
|
res->isForcing = false;
|
||||||
|
res->internalID = numberOfSignals;
|
||||||
|
res->decimationFactor = 1;
|
||||||
|
res->decimationCounter = 0;
|
||||||
|
numberOfSignals++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sigIdx != 0xFFFFFFFF && numberOfAliases < MAX_ALIASES) {
|
||||||
|
bool foundAlias = false;
|
||||||
|
for (uint32 i=0; i<numberOfAliases; i++) {
|
||||||
|
if (aliases[i].name == name) { foundAlias = true; break; }
|
||||||
|
}
|
||||||
|
if (!foundAlias) {
|
||||||
|
aliases[numberOfAliases].name = name;
|
||||||
|
aliases[numberOfAliases].signalIndex = sigIdx;
|
||||||
|
numberOfAliases++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.FastUnLock();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool RecursiveGetFullObjectName(ReferenceContainer *container, const Object &obj, StreamString &path) {
|
||||||
|
uint32 size = container->Size();
|
||||||
|
for (uint32 i=0; i<size; i++) {
|
||||||
|
Reference child = container->Get(i);
|
||||||
|
if (child.IsValid()) {
|
||||||
|
if (child.operator->() == &obj) {
|
||||||
|
path = child->GetName();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ReferenceContainer *inner = dynamic_cast<ReferenceContainer*>(child.operator->());
|
||||||
|
if (inner) {
|
||||||
|
if (RecursiveGetFullObjectName(inner, obj, path)) {
|
||||||
|
StreamString prefix = child->GetName();
|
||||||
|
prefix += ".";
|
||||||
|
prefix += path;
|
||||||
|
path = prefix;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DebugService::GetFullObjectName(const Object &obj, StreamString &fullPath) {
|
||||||
|
fullPath = "";
|
||||||
|
if (RecursiveGetFullObjectName(ObjectRegistryDatabase::Instance(), obj, fullPath)) {
|
||||||
|
StreamString abs = "Root.";
|
||||||
|
abs += fullPath;
|
||||||
|
fullPath = abs;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorManagement::ErrorType DebugService::Execute(ExecutionInfo & info) {
|
||||||
|
return ErrorManagement::FatalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorManagement::ErrorType DebugService::Server(ExecutionInfo & info) {
|
||||||
|
if (info.GetStage() == ExecutionInfo::TerminationStage) return ErrorManagement::NoError;
|
||||||
|
if (info.GetStage() == ExecutionInfo::StartupStage) {
|
||||||
|
serverThreadId = Threads::Id();
|
||||||
|
if (!tcpServer.Listen(controlPort)) return ErrorManagement::FatalError;
|
||||||
|
return ErrorManagement::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (info.GetStage() == ExecutionInfo::MainStage) {
|
||||||
|
if (ErrorManagement::errorMessageProcessFunction != &DebugService::LogCallback) {
|
||||||
|
originalLogCallback = ErrorManagement::errorMessageProcessFunction;
|
||||||
|
ErrorManagement::SetErrorProcessFunction(&DebugService::LogCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicTCPSocket *newClient = tcpServer.WaitConnection(1);
|
||||||
|
if (newClient != NULL_PTR(BasicTCPSocket *)) {
|
||||||
|
clientsMutex.FastLock();
|
||||||
|
bool added = false;
|
||||||
|
for (uint32 i=0; i<MAX_CLIENTS; i++) {
|
||||||
|
if (activeClients[i] == NULL_PTR(BasicTCPSocket*)) {
|
||||||
|
activeClients[i] = newClient;
|
||||||
|
added = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clientsMutex.FastUnLock();
|
||||||
|
if (!added) {
|
||||||
|
newClient->Close();
|
||||||
|
delete newClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32 i=0; i<MAX_CLIENTS; i++) {
|
||||||
|
BasicTCPSocket *client = NULL_PTR(BasicTCPSocket*);
|
||||||
|
clientsMutex.FastLock();
|
||||||
|
client = activeClients[i];
|
||||||
|
clientsMutex.FastUnLock();
|
||||||
|
|
||||||
|
if (client != NULL_PTR(BasicTCPSocket*)) {
|
||||||
|
char buffer[1024];
|
||||||
|
uint32 size = 1024;
|
||||||
|
TimeoutType timeout(0);
|
||||||
|
bool ok = client->Read(buffer, size, timeout);
|
||||||
|
|
||||||
|
if (ok && size > 0) {
|
||||||
|
StreamString command;
|
||||||
|
command.Write(buffer, size);
|
||||||
|
HandleCommand(command, client);
|
||||||
|
} else if (!ok) {
|
||||||
|
if (!client->IsValid()) {
|
||||||
|
clientsMutex.FastLock();
|
||||||
|
client->Close();
|
||||||
|
delete client;
|
||||||
|
activeClients[i] = NULL_PTR(BasicTCPSocket*);
|
||||||
|
clientsMutex.FastUnLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Sleep::MSec(10);
|
||||||
|
}
|
||||||
|
return ErrorManagement::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorManagement::ErrorType DebugService::LogStreamer(ExecutionInfo & info) {
|
||||||
|
if (info.GetStage() == ExecutionInfo::TerminationStage) return ErrorManagement::NoError;
|
||||||
|
if (info.GetStage() == ExecutionInfo::StartupStage) {
|
||||||
|
logStreamerThreadId = Threads::Id();
|
||||||
|
if (!logServer.Listen(logPort)) return ErrorManagement::FatalError;
|
||||||
|
return ErrorManagement::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (info.GetStage() == ExecutionInfo::MainStage) {
|
||||||
|
BasicTCPSocket *newClient = logServer.WaitConnection(1);
|
||||||
|
if (newClient != NULL_PTR(BasicTCPSocket *)) {
|
||||||
|
logClientsMutex.FastLock();
|
||||||
|
bool added = false;
|
||||||
|
for (uint32 i=0; i<MAX_CLIENTS; i++) {
|
||||||
|
if (activeLogClients[i] == NULL_PTR(BasicTCPSocket*)) {
|
||||||
|
activeLogClients[i] = newClient;
|
||||||
|
added = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logClientsMutex.FastUnLock();
|
||||||
|
if (!added) {
|
||||||
|
newClient->Close();
|
||||||
|
delete newClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hadData = false;
|
||||||
|
for (uint32 b=0; b<50; b++) {
|
||||||
|
if (logQueueRead == logQueueWrite) break;
|
||||||
|
hadData = true;
|
||||||
|
|
||||||
|
uint32 idx = logQueueRead % LOG_QUEUE_SIZE;
|
||||||
|
LogEntry &entry = logQueue[idx];
|
||||||
|
|
||||||
|
StreamString level;
|
||||||
|
ErrorManagement::ErrorCodeToStream(entry.info.header.errorType, level);
|
||||||
|
|
||||||
|
StreamString packet;
|
||||||
|
packet.Printf("LOG %s %s\n", level.Buffer(), entry.description);
|
||||||
|
uint32 size = packet.Size();
|
||||||
|
|
||||||
|
logClientsMutex.FastLock();
|
||||||
|
for (uint32 j=0; j<MAX_CLIENTS; j++) {
|
||||||
|
if (activeLogClients[j] != NULL_PTR(BasicTCPSocket*)) {
|
||||||
|
uint32 s = size;
|
||||||
|
if (!activeLogClients[j]->Write(packet.Buffer(), s)) {
|
||||||
|
activeLogClients[j]->Close();
|
||||||
|
delete activeLogClients[j];
|
||||||
|
activeLogClients[j] = NULL_PTR(BasicTCPSocket*);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logClientsMutex.FastUnLock();
|
||||||
|
logQueueRead++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hadData) {
|
||||||
|
(void)logEvent.Wait(TimeoutType(100));
|
||||||
|
logEvent.Reset();
|
||||||
|
} else {
|
||||||
|
Sleep::MSec(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ErrorManagement::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorManagement::ErrorType DebugService::Streamer(ExecutionInfo & info) {
|
||||||
|
if (info.GetStage() == ExecutionInfo::TerminationStage) return ErrorManagement::NoError;
|
||||||
|
if (info.GetStage() == ExecutionInfo::StartupStage) {
|
||||||
|
streamerThreadId = Threads::Id();
|
||||||
|
return ErrorManagement::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetHost dest(streamPort, streamIP.Buffer());
|
||||||
|
udpSocket.SetDestination(dest);
|
||||||
|
|
||||||
|
uint8 packetBuffer[4096];
|
||||||
|
uint32 packetOffset = 0;
|
||||||
|
uint32 sequenceNumber = 0;
|
||||||
|
|
||||||
|
while (info.GetStage() == ExecutionInfo::MainStage) {
|
||||||
|
uint32 id;
|
||||||
|
uint32 size;
|
||||||
|
uint8 sampleData[1024];
|
||||||
|
bool hasData = false;
|
||||||
|
|
||||||
|
while ((info.GetStage() == ExecutionInfo::MainStage) && traceBuffer.Pop(id, sampleData, size, 1024)) {
|
||||||
|
hasData = true;
|
||||||
|
if (packetOffset == 0) {
|
||||||
|
TraceHeader header;
|
||||||
|
header.magic = 0xDA7A57AD;
|
||||||
|
header.seq = sequenceNumber++;
|
||||||
|
header.timestamp = HighResolutionTimer::Counter();
|
||||||
|
header.count = 0;
|
||||||
|
MemoryOperationsHelper::Copy(packetBuffer, &header, sizeof(TraceHeader));
|
||||||
|
packetOffset = sizeof(TraceHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packetOffset + 8 + size > 1400) {
|
||||||
|
uint32 toWrite = packetOffset;
|
||||||
|
udpSocket.Write((char8*)packetBuffer, toWrite);
|
||||||
|
packetOffset = 0;
|
||||||
|
TraceHeader header;
|
||||||
|
header.magic = 0xDA7A57AD;
|
||||||
|
header.seq = sequenceNumber++;
|
||||||
|
header.timestamp = HighResolutionTimer::Counter();
|
||||||
|
header.count = 0;
|
||||||
|
MemoryOperationsHelper::Copy(packetBuffer, &header, sizeof(TraceHeader));
|
||||||
|
packetOffset = sizeof(TraceHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryOperationsHelper::Copy(&packetBuffer[packetOffset], &id, 4);
|
||||||
|
MemoryOperationsHelper::Copy(&packetBuffer[packetOffset + 4], &size, 4);
|
||||||
|
MemoryOperationsHelper::Copy(&packetBuffer[packetOffset + 8], sampleData, size);
|
||||||
|
packetOffset += (8 + size);
|
||||||
|
TraceHeader *h = (TraceHeader*)packetBuffer;
|
||||||
|
h->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packetOffset > 0) {
|
||||||
|
uint32 toWrite = packetOffset;
|
||||||
|
udpSocket.Write((char8*)packetBuffer, toWrite);
|
||||||
|
packetOffset = 0;
|
||||||
|
}
|
||||||
|
if (!hasData) Sleep::MSec(1);
|
||||||
|
}
|
||||||
|
return ErrorManagement::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool SuffixMatch(const char8* target, const char8* pattern) {
|
||||||
|
uint32 tLen = StringHelper::Length(target);
|
||||||
|
uint32 pLen = StringHelper::Length(pattern);
|
||||||
|
if (pLen > tLen) return false;
|
||||||
|
const char8* suffix = target + (tLen - pLen);
|
||||||
|
if (StringHelper::Compare(suffix, pattern) == 0) {
|
||||||
|
if (tLen == pLen || *(suffix - 1) == '.') return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugService::HandleCommand(StreamString cmd, BasicTCPSocket *client) {
|
||||||
|
StreamString token;
|
||||||
|
cmd.Seek(0);
|
||||||
|
char8 term;
|
||||||
|
const char8* delims = " \r\n";
|
||||||
|
if (cmd.GetToken(token, delims, term)) {
|
||||||
|
if (token == "FORCE") {
|
||||||
|
StreamString name, val;
|
||||||
|
if (cmd.GetToken(name, delims, term) && cmd.GetToken(val, delims, term)) {
|
||||||
|
uint32 count = ForceSignal(name.Buffer(), val.Buffer());
|
||||||
|
if (client) {
|
||||||
|
StreamString resp; resp.Printf("OK FORCE %u\n", count);
|
||||||
|
uint32 s = resp.Size(); client->Write(resp.Buffer(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (token == "UNFORCE") {
|
||||||
|
StreamString name;
|
||||||
|
if (cmd.GetToken(name, delims, term)) {
|
||||||
|
uint32 count = UnforceSignal(name.Buffer());
|
||||||
|
if (client) {
|
||||||
|
StreamString resp; resp.Printf("OK UNFORCE %u\n", count);
|
||||||
|
uint32 s = resp.Size(); client->Write(resp.Buffer(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (token == "TRACE") {
|
||||||
|
StreamString name, state, decim;
|
||||||
|
if (cmd.GetToken(name, delims, term) && cmd.GetToken(state, delims, term)) {
|
||||||
|
bool enable = (state == "1");
|
||||||
|
uint32 d = 1;
|
||||||
|
if (cmd.GetToken(decim, delims, term)) {
|
||||||
|
AnyType decimVal(UnsignedInteger32Bit, 0u, &d);
|
||||||
|
AnyType decimStr(CharString, 0u, decim.Buffer());
|
||||||
|
TypeConvert(decimVal, decimStr);
|
||||||
|
}
|
||||||
|
uint32 count = TraceSignal(name.Buffer(), enable, d);
|
||||||
|
if (client) {
|
||||||
|
StreamString resp; resp.Printf("OK TRACE %u\n", count);
|
||||||
|
uint32 s = resp.Size(); client->Write(resp.Buffer(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (token == "DISCOVER") Discover(client);
|
||||||
|
else if (token == "PAUSE") {
|
||||||
|
SetPaused(true);
|
||||||
|
if (client) { uint32 s = 3; client->Write("OK\n", s); }
|
||||||
|
}
|
||||||
|
else if (token == "RESUME") {
|
||||||
|
SetPaused(false);
|
||||||
|
if (client) { uint32 s = 3; client->Write("OK\n", s); }
|
||||||
|
}
|
||||||
|
else if (token == "TREE") {
|
||||||
|
StreamString json;
|
||||||
|
json = "{\"Name\": \"Root\", \"Class\": \"ObjectRegistryDatabase\", \"Children\": [\n";
|
||||||
|
ExportTree(ObjectRegistryDatabase::Instance(), json);
|
||||||
|
json += "\n]}\nOK TREE\n";
|
||||||
|
uint32 s = json.Size();
|
||||||
|
client->Write(json.Buffer(), s);
|
||||||
|
}
|
||||||
|
else if (token == "INFO") {
|
||||||
|
StreamString path;
|
||||||
|
if (cmd.GetToken(path, delims, term)) InfoNode(path.Buffer(), client);
|
||||||
|
}
|
||||||
|
else if (token == "LS") {
|
||||||
|
StreamString path;
|
||||||
|
if (cmd.GetToken(path, delims, term)) ListNodes(path.Buffer(), client);
|
||||||
|
else ListNodes(NULL_PTR(const char8*), client);
|
||||||
|
}
|
||||||
|
else if (client) {
|
||||||
|
const char* msg = "ERROR: Unknown command\n";
|
||||||
|
uint32 s = StringHelper::Length(msg);
|
||||||
|
client->Write(msg, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugService::InfoNode(const char8* path, BasicTCPSocket *client) {
|
||||||
|
if (!client) return;
|
||||||
|
Reference ref = ObjectRegistryDatabase::Instance()->Find(path);
|
||||||
|
StreamString json = "{";
|
||||||
|
|
||||||
|
if (ref.IsValid()) {
|
||||||
|
json += "\"Name\": \""; EscapeJson(ref->GetName(), json);
|
||||||
|
json += "\", \"Class\": \""; EscapeJson(ref->GetClassProperties()->GetName(), json); json += "\"";
|
||||||
|
|
||||||
|
ConfigurationDatabase db;
|
||||||
|
if (ref->ExportData(db)) {
|
||||||
|
json += ", \"Config\": {";
|
||||||
|
db.MoveToRoot();
|
||||||
|
uint32 nChildren = db.GetNumberOfChildren();
|
||||||
|
for (uint32 i=0; i<nChildren; i++) {
|
||||||
|
const char8* cname = db.GetChildName(i);
|
||||||
|
AnyType at = db.GetType(cname);
|
||||||
|
char8 valBuf[1024];
|
||||||
|
AnyType strType(CharString, 0u, valBuf);
|
||||||
|
strType.SetNumberOfElements(0, 1024);
|
||||||
|
if (TypeConvert(strType, at)) {
|
||||||
|
json += "\""; EscapeJson(cname, json); json += "\": \"";
|
||||||
|
EscapeJson(valBuf, json); json += "\"";
|
||||||
|
if (i < nChildren - 1) json += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json += "}";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mutex.FastLock();
|
||||||
|
bool found = false;
|
||||||
|
for (uint32 i=0; i<numberOfAliases; i++) {
|
||||||
|
if (aliases[i].name == path || SuffixMatch(aliases[i].name.Buffer(), path)) {
|
||||||
|
DebugSignalInfo &s = signals[aliases[i].signalIndex];
|
||||||
|
const char8* tname = TypeDescriptor::GetTypeNameFromTypeDescriptor(s.type);
|
||||||
|
json.Printf("\"Name\": \"%s\", \"Class\": \"Signal\", \"Type\": \"%s\", \"ID\": %d",
|
||||||
|
s.name.Buffer(), tname ? tname : "Unknown", s.internalID);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.FastUnLock();
|
||||||
|
if (!found) json += "\"Error\": \"Object not found\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
json += "}\nOK INFO\n";
|
||||||
|
uint32 s = json.Size();
|
||||||
|
client->Write(json.Buffer(), s);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 DebugService::ExportTree(ReferenceContainer *container, StreamString &json) {
|
||||||
|
if (container == NULL_PTR(ReferenceContainer*)) return 0;
|
||||||
|
uint32 size = container->Size();
|
||||||
|
uint32 validCount = 0;
|
||||||
|
for (uint32 i = 0u; i < size; i++) {
|
||||||
|
Reference child = container->Get(i);
|
||||||
|
if (child.IsValid()) {
|
||||||
|
if (validCount > 0u) json += ",\n";
|
||||||
|
|
||||||
|
StreamString nodeJson;
|
||||||
|
const char8* cname = child->GetName();
|
||||||
|
if (cname == NULL_PTR(const char8*)) cname = "unnamed";
|
||||||
|
const char8* clsname = child->GetClassProperties()->GetName();
|
||||||
|
|
||||||
|
nodeJson += "{\"Name\": \""; EscapeJson(cname, nodeJson);
|
||||||
|
nodeJson += "\", \"Class\": \""; EscapeJson(clsname, nodeJson); nodeJson += "\"";
|
||||||
|
|
||||||
|
ReferenceContainer *inner = dynamic_cast<ReferenceContainer*>(child.operator->());
|
||||||
|
DataSourceI *ds = dynamic_cast<DataSourceI*>(child.operator->());
|
||||||
|
GAM *gam = dynamic_cast<GAM*>(child.operator->());
|
||||||
|
|
||||||
|
if ((inner != NULL_PTR(ReferenceContainer*)) || (ds != NULL_PTR(DataSourceI*)) || (gam != NULL_PTR(GAM*))) {
|
||||||
|
nodeJson += ", \"Children\": [\n";
|
||||||
|
uint32 subCount = 0u;
|
||||||
|
if (inner != NULL_PTR(ReferenceContainer*)) subCount += ExportTree(inner, nodeJson);
|
||||||
|
|
||||||
|
if (ds != NULL_PTR(DataSourceI*)) {
|
||||||
|
uint32 nSignals = ds->GetNumberOfSignals();
|
||||||
|
for (uint32 j = 0u; j < nSignals; j++) {
|
||||||
|
if (subCount > 0u) nodeJson += ",\n";
|
||||||
|
subCount++;
|
||||||
|
StreamString sname; (void)ds->GetSignalName(j, sname);
|
||||||
|
const char8* stype = TypeDescriptor::GetTypeNameFromTypeDescriptor(ds->GetSignalType(j));
|
||||||
|
uint8 dims = 0u; (void)ds->GetSignalNumberOfDimensions(j, dims);
|
||||||
|
uint32 elems = 0u; (void)ds->GetSignalNumberOfElements(j, elems);
|
||||||
|
|
||||||
|
nodeJson += "{\"Name\": \""; EscapeJson(sname.Buffer(), nodeJson);
|
||||||
|
nodeJson += "\", \"Class\": \"Signal\", \"Type\": \""; EscapeJson(stype ? stype : "Unknown", nodeJson);
|
||||||
|
nodeJson.Printf("\", \"Dimensions\": %d, \"Elements\": %u}", dims, elems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gam != NULL_PTR(GAM*)) {
|
||||||
|
uint32 nIn = gam->GetNumberOfInputSignals();
|
||||||
|
for (uint32 j = 0u; j < nIn; j++) {
|
||||||
|
if (subCount > 0u) nodeJson += ",\n";
|
||||||
|
subCount++;
|
||||||
|
StreamString sname; (void)gam->GetSignalName(InputSignals, j, sname);
|
||||||
|
const char8* stype = TypeDescriptor::GetTypeNameFromTypeDescriptor(gam->GetSignalType(InputSignals, j));
|
||||||
|
uint32 dims = 0u; (void)gam->GetSignalNumberOfDimensions(InputSignals, j, dims);
|
||||||
|
uint32 elems = 0u; (void)gam->GetSignalNumberOfElements(InputSignals, j, elems);
|
||||||
|
|
||||||
|
nodeJson += "{\"Name\": \"In."; EscapeJson(sname.Buffer(), nodeJson);
|
||||||
|
nodeJson += "\", \"Class\": \"InputSignal\", \"Type\": \""; EscapeJson(stype ? stype : "Unknown", nodeJson);
|
||||||
|
nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u}", dims, elems);
|
||||||
|
}
|
||||||
|
uint32 nOut = gam->GetNumberOfOutputSignals();
|
||||||
|
for (uint32 j = 0u; j < nOut; j++) {
|
||||||
|
if (subCount > 0u) nodeJson += ",\n";
|
||||||
|
subCount++;
|
||||||
|
StreamString sname; (void)gam->GetSignalName(OutputSignals, j, sname);
|
||||||
|
const char8* stype = TypeDescriptor::GetTypeNameFromTypeDescriptor(gam->GetSignalType(OutputSignals, j));
|
||||||
|
uint32 dims = 0u; (void)gam->GetSignalNumberOfDimensions(OutputSignals, j, dims);
|
||||||
|
uint32 elems = 0u; (void)gam->GetSignalNumberOfElements(OutputSignals, j, elems);
|
||||||
|
|
||||||
|
nodeJson += "{\"Name\": \"Out."; EscapeJson(sname.Buffer(), nodeJson);
|
||||||
|
nodeJson += "\", \"Class\": \"OutputSignal\", \"Type\": \""; EscapeJson(stype ? stype : "Unknown", nodeJson);
|
||||||
|
nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u}", dims, elems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeJson += "\n]";
|
||||||
|
}
|
||||||
|
nodeJson += "}";
|
||||||
|
json += nodeJson;
|
||||||
|
validCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 DebugService::ForceSignal(const char8* name, const char8* valueStr) {
|
||||||
|
mutex.FastLock();
|
||||||
|
uint32 count = 0;
|
||||||
|
for (uint32 i = 0; i < numberOfAliases; i++) {
|
||||||
|
if (aliases[i].name == name || SuffixMatch(aliases[i].name.Buffer(), name)) {
|
||||||
|
DebugSignalInfo &s = signals[aliases[i].signalIndex];
|
||||||
|
s.isForcing = true;
|
||||||
|
AnyType dest(s.type, 0u, s.forcedValue);
|
||||||
|
AnyType source(CharString, 0u, valueStr);
|
||||||
|
(void)TypeConvert(dest, source);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.FastUnLock();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 DebugService::UnforceSignal(const char8* name) {
|
||||||
|
mutex.FastLock();
|
||||||
|
uint32 count = 0;
|
||||||
|
for (uint32 i = 0; i < numberOfAliases; i++) {
|
||||||
|
if (aliases[i].name == name || SuffixMatch(aliases[i].name.Buffer(), name)) {
|
||||||
|
signals[aliases[i].signalIndex].isForcing = false;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.FastUnLock();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 DebugService::TraceSignal(const char8* name, bool enable, uint32 decimation) {
|
||||||
|
mutex.FastLock();
|
||||||
|
uint32 count = 0;
|
||||||
|
for (uint32 i = 0; i < numberOfAliases; i++) {
|
||||||
|
if (aliases[i].name == name || SuffixMatch(aliases[i].name.Buffer(), name)) {
|
||||||
|
DebugSignalInfo &s = signals[aliases[i].signalIndex];
|
||||||
|
s.isTracing = enable;
|
||||||
|
s.decimationFactor = decimation;
|
||||||
|
s.decimationCounter = 0;
|
||||||
|
count++;
|
||||||
|
printf("[Debug] Tracing state for %s (ID: %u) set to %d\n", aliases[i].name.Buffer(), s.internalID, enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.FastUnLock();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugService::Discover(BasicTCPSocket *client) {
|
||||||
|
if (client) {
|
||||||
|
StreamString header = "{\n \"Signals\": [\n";
|
||||||
|
uint32 s = header.Size();
|
||||||
|
client->Write(header.Buffer(), s);
|
||||||
|
mutex.FastLock();
|
||||||
|
for (uint32 i = 0; i < numberOfAliases; i++) {
|
||||||
|
StreamString line;
|
||||||
|
DebugSignalInfo &sig = signals[aliases[i].signalIndex];
|
||||||
|
const char8* typeName = TypeDescriptor::GetTypeNameFromTypeDescriptor(sig.type);
|
||||||
|
if (typeName == NULL_PTR(const char8*)) typeName = "Unknown";
|
||||||
|
line.Printf(" {\"name\": \"%s\", \"id\": %d, \"type\": \"%s\"}", aliases[i].name.Buffer(), sig.internalID, typeName);
|
||||||
|
if (i < numberOfAliases - 1) line += ",";
|
||||||
|
line += "\n";
|
||||||
|
s = line.Size();
|
||||||
|
client->Write(line.Buffer(), s);
|
||||||
|
}
|
||||||
|
mutex.FastUnLock();
|
||||||
|
StreamString footer = " ]\n}\nOK DISCOVER\n";
|
||||||
|
s = footer.Size();
|
||||||
|
client->Write(footer.Buffer(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugService::ListNodes(const char8* path, BasicTCPSocket *client) {
|
||||||
|
if (!client) return;
|
||||||
|
Reference ref;
|
||||||
|
if (path == NULL_PTR(const char8*) || StringHelper::Length(path) == 0 || StringHelper::Compare(path, "/") == 0) {
|
||||||
|
ref = ObjectRegistryDatabase::Instance();
|
||||||
|
} else {
|
||||||
|
ref = ObjectRegistryDatabase::Instance()->Find(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref.IsValid()) {
|
||||||
|
StreamString header;
|
||||||
|
header.Printf("Nodes under %s:\n", path ? path : "/");
|
||||||
|
uint32 s = header.Size();
|
||||||
|
client->Write(header.Buffer(), s);
|
||||||
|
|
||||||
|
ReferenceContainer *container = dynamic_cast<ReferenceContainer*>(ref.operator->());
|
||||||
|
if (container) {
|
||||||
|
uint32 size = container->Size();
|
||||||
|
for (uint32 i=0; i<size; i++) {
|
||||||
|
Reference child = container->Get(i);
|
||||||
|
if (child.IsValid()) {
|
||||||
|
StreamString line;
|
||||||
|
line.Printf(" %s [%s]\n", child->GetName(), child->GetClassProperties()->GetName());
|
||||||
|
s = line.Size();
|
||||||
|
client->Write(line.Buffer(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DataSourceI *ds = dynamic_cast<DataSourceI*>(ref.operator->());
|
||||||
|
if (ds) {
|
||||||
|
StreamString dsHeader = " Signals:\n";
|
||||||
|
s = dsHeader.Size(); client->Write(dsHeader.Buffer(), s);
|
||||||
|
uint32 nSignals = ds->GetNumberOfSignals();
|
||||||
|
for (uint32 i=0; i<nSignals; i++) {
|
||||||
|
StreamString sname, line;
|
||||||
|
ds->GetSignalName(i, sname);
|
||||||
|
TypeDescriptor stype = ds->GetSignalType(i);
|
||||||
|
const char8* stypeName = TypeDescriptor::GetTypeNameFromTypeDescriptor(stype);
|
||||||
|
line.Printf(" %s [%s]\n", sname.Buffer(), stypeName ? stypeName : "Unknown");
|
||||||
|
s = line.Size(); client->Write(line.Buffer(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GAM *gam = dynamic_cast<GAM*>(ref.operator->());
|
||||||
|
if (gam) {
|
||||||
|
uint32 nIn = gam->GetNumberOfInputSignals();
|
||||||
|
uint32 nOut = gam->GetNumberOfOutputSignals();
|
||||||
|
StreamString gamHeader;
|
||||||
|
gamHeader.Printf(" Input Signals (%d):\n", nIn);
|
||||||
|
s = gamHeader.Size(); client->Write(gamHeader.Buffer(), s);
|
||||||
|
for (uint32 i=0; i<nIn; i++) {
|
||||||
|
StreamString sname, line;
|
||||||
|
gam->GetSignalName(InputSignals, i, sname);
|
||||||
|
line.Printf(" %s\n", sname.Buffer());
|
||||||
|
s = line.Size(); client->Write(line.Buffer(), s);
|
||||||
|
}
|
||||||
|
gamHeader.SetSize(0);
|
||||||
|
gamHeader.Printf(" Output Signals (%d):\n", nOut);
|
||||||
|
s = gamHeader.Size(); client->Write(gamHeader.Buffer(), s);
|
||||||
|
for (uint32 i=0; i<nOut; i++) {
|
||||||
|
StreamString sname, line;
|
||||||
|
gam->GetSignalName(OutputSignals, i, sname);
|
||||||
|
line.Printf(" %s\n", sname.Buffer());
|
||||||
|
s = line.Size(); client->Write(line.Buffer(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* okMsg = "OK LS\n";
|
||||||
|
s = StringHelper::Length(okMsg);
|
||||||
|
client->Write(okMsg, s);
|
||||||
|
} else {
|
||||||
|
const char* msg = "ERROR: Path not found\n";
|
||||||
|
uint32 s = StringHelper::Length(msg);
|
||||||
|
client->Write(msg, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugService::ConsumeLogMessage(LoggerPage *logPage) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugService::LogCallback(const ErrorManagement::ErrorInformation &errorInfo, const char8 * const errorDescription) {
|
||||||
|
ThreadIdentifier current = Threads::Id();
|
||||||
|
if (instance != NULL_PTR(DebugService*)) {
|
||||||
|
StreamString levelStr;
|
||||||
|
ErrorManagement::ErrorCodeToStream(errorInfo.header.errorType, levelStr);
|
||||||
|
printf("[%s] %s\n", levelStr.Buffer(), errorDescription);
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
bool isWorkerThread = false;
|
||||||
|
if (instance->serverThreadId != InvalidThreadIdentifier && current == instance->serverThreadId) isWorkerThread = true;
|
||||||
|
if (instance->streamerThreadId != InvalidThreadIdentifier && current == instance->streamerThreadId) isWorkerThread = true;
|
||||||
|
if (instance->logStreamerThreadId != InvalidThreadIdentifier && current == instance->logStreamerThreadId) isWorkerThread = true;
|
||||||
|
|
||||||
|
if (isWorkerThread) return;
|
||||||
|
|
||||||
|
if (instance->suppressTimeoutLogs && StringHelper::SearchString(errorDescription, "Timeout expired in recv()") != NULL_PTR(const char8*)) return;
|
||||||
|
|
||||||
|
LoggerPage tempPage;
|
||||||
|
tempPage.errorInfo = errorInfo;
|
||||||
|
StringHelper::Copy(tempPage.errorStrBuffer, errorDescription);
|
||||||
|
instance->InsertLogIntoQueue(&tempPage);
|
||||||
|
}
|
||||||
|
if (originalLogCallback) originalLogCallback(errorInfo, errorDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugService::InsertLogIntoQueue(LoggerPage *logPage) {
|
||||||
|
uint32 next = (logQueueWrite + 1) % LOG_QUEUE_SIZE;
|
||||||
|
if (next != logQueueRead) {
|
||||||
|
LogEntry &entry = logQueue[logQueueWrite % LOG_QUEUE_SIZE];
|
||||||
|
entry.info = logPage->errorInfo;
|
||||||
|
StringHelper::Copy(entry.description, logPage->errorStrBuffer);
|
||||||
|
logQueueWrite = next;
|
||||||
|
(void)logEvent.Post();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
101
Test/Configurations/debug_test.cfg
Normal file
101
Test/Configurations/debug_test.cfg
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
+DebugService = {
|
||||||
|
Class = DebugService
|
||||||
|
ControlPort = 8080
|
||||||
|
StreamPort = 8081
|
||||||
|
StreamIP = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
+LoggerService = {
|
||||||
|
Class = LoggerService
|
||||||
|
CPUs = 0x1
|
||||||
|
+DebugConsumer = {
|
||||||
|
Class = DebugService
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+App = {
|
||||||
|
Class = RealTimeApplication
|
||||||
|
+Functions = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+GAM1 = {
|
||||||
|
Class = IOGAM
|
||||||
|
InputSignals = {
|
||||||
|
Counter = {
|
||||||
|
DataSource = Timer
|
||||||
|
Type = uint32
|
||||||
|
Frequency = 10
|
||||||
|
}
|
||||||
|
Time = {
|
||||||
|
DataSource = Timer
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OutputSignals = {
|
||||||
|
Counter = {
|
||||||
|
DataSource = DDB
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
Time = {
|
||||||
|
DataSource = Logger
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+Data = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
DefaultDataSource = DDB
|
||||||
|
+Timer = {
|
||||||
|
Class = LinuxTimer
|
||||||
|
SleepTime = 1000000 // 1 second
|
||||||
|
Signals = {
|
||||||
|
Counter = {
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
Time = {
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+Logger = {
|
||||||
|
Class = LoggerDataSource
|
||||||
|
Signals = {
|
||||||
|
CounterCopy = {
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
TimeCopy = {
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+DDB = {
|
||||||
|
AllowNoProducer = 1
|
||||||
|
Class = GAMDataSource
|
||||||
|
Signals = {
|
||||||
|
Counter= {
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+DAMS = {
|
||||||
|
Class = TimingDataSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+States = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+State1 = {
|
||||||
|
Class = RealTimeState
|
||||||
|
+Threads = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+Thread1 = {
|
||||||
|
Class = RealTimeThread
|
||||||
|
Functions = {GAM1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+Scheduler = {
|
||||||
|
Class = GAMScheduler
|
||||||
|
TimingDataSource = DAMS
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Test/Integration/CMakeLists.txt
Normal file
8
Test/Integration/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
add_executable(IntegrationTest main.cpp)
|
||||||
|
target_link_libraries(IntegrationTest marte_dev ${MARTe2_LIB})
|
||||||
|
|
||||||
|
add_executable(TraceTest TraceTest.cpp)
|
||||||
|
target_link_libraries(TraceTest marte_dev ${MARTe2_LIB})
|
||||||
|
|
||||||
|
add_executable(ValidationTest ValidationTest.cpp)
|
||||||
|
target_link_libraries(ValidationTest marte_dev ${MARTe2_LIB} ${IOGAM_LIB} ${LinuxTimer_LIB})
|
||||||
86
Test/Integration/TraceTest.cpp
Normal file
86
Test/Integration/TraceTest.cpp
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#include "DebugService.h"
|
||||||
|
#include "DebugCore.h"
|
||||||
|
#include "ObjectRegistryDatabase.h"
|
||||||
|
#include "StandardParser.h"
|
||||||
|
#include "StreamString.h"
|
||||||
|
#include "BasicUDPSocket.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
using namespace MARTe;
|
||||||
|
|
||||||
|
void TestFullTracePipeline() {
|
||||||
|
printf("Starting Full Trace Pipeline Test...\n");
|
||||||
|
printf("sizeof(TraceHeader) = %lu\n", sizeof(TraceHeader));
|
||||||
|
|
||||||
|
// 1. Setup Service
|
||||||
|
DebugService service;
|
||||||
|
ConfigurationDatabase config;
|
||||||
|
config.Write("ControlPort", (uint16)8080);
|
||||||
|
config.Write("StreamPort", (uint16)8081);
|
||||||
|
config.Write("LogPort", (uint16)8082);
|
||||||
|
config.Write("StreamIP", "127.0.0.1");
|
||||||
|
assert(service.Initialise(config));
|
||||||
|
|
||||||
|
// 2. Register a mock signal
|
||||||
|
uint32 mockValue = 0;
|
||||||
|
DebugSignalInfo* sig = service.RegisterSignal(&mockValue, UnsignedInteger32Bit, "Test.Signal");
|
||||||
|
assert(sig != NULL_PTR(DebugSignalInfo*));
|
||||||
|
printf("Signal registered with ID: %u\n", sig->internalID);
|
||||||
|
|
||||||
|
// 3. Enable Trace manually
|
||||||
|
sig->isTracing = true;
|
||||||
|
sig->decimationFactor = 1;
|
||||||
|
|
||||||
|
// 4. Setup a local UDP listener
|
||||||
|
BasicUDPSocket listener;
|
||||||
|
assert(listener.Open());
|
||||||
|
assert(listener.Listen(8081));
|
||||||
|
|
||||||
|
// 5. Simulate cycles
|
||||||
|
printf("Simulating cycles...\n");
|
||||||
|
for (int i=0; i<50; i++) {
|
||||||
|
mockValue = 1000 + i;
|
||||||
|
service.ProcessSignal(sig, sizeof(uint32));
|
||||||
|
Sleep::MSec(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Try to read from UDP
|
||||||
|
char buffer[2048];
|
||||||
|
uint32 size = 2048;
|
||||||
|
TimeoutType timeout(1000); // 1s
|
||||||
|
if (listener.Read(buffer, size, timeout)) {
|
||||||
|
printf("SUCCESS: Received %u bytes over UDP!\n", size);
|
||||||
|
for(uint32 i=0; i<size; i++) {
|
||||||
|
printf("%02X ", (uint8)buffer[i]);
|
||||||
|
if((i+1)%4 == 0) printf("| ");
|
||||||
|
if((i+1)%16 == 0) printf("\n");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
TraceHeader *h = (TraceHeader*)buffer;
|
||||||
|
printf("Header: Magic=0x%X, Count=%u, Seq=%u\n", h->magic, h->count, h->seq);
|
||||||
|
|
||||||
|
uint32 offset = sizeof(TraceHeader);
|
||||||
|
if (size >= offset + 8) {
|
||||||
|
uint32 recId = *(uint32*)(&buffer[offset]);
|
||||||
|
uint32 recSize = *(uint32*)(&buffer[offset + 4]);
|
||||||
|
printf("Data: ID=%u, Size=%u\n", recId, recSize);
|
||||||
|
if (size >= offset + 8 + recSize) {
|
||||||
|
if (recSize == 4) {
|
||||||
|
uint32 recVal = *(uint32*)(&buffer[offset + 8]);
|
||||||
|
printf("Value=%u\n", recVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("FAILURE: No UDP packets received.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestFullTracePipeline();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
202
Test/Integration/ValidationTest.cpp
Normal file
202
Test/Integration/ValidationTest.cpp
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#include "DebugService.h"
|
||||||
|
#include "DebugCore.h"
|
||||||
|
#include "ObjectRegistryDatabase.h"
|
||||||
|
#include "StandardParser.h"
|
||||||
|
#include "StreamString.h"
|
||||||
|
#include "BasicUDPSocket.h"
|
||||||
|
#include "BasicTCPSocket.h"
|
||||||
|
#include "RealTimeApplication.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
using namespace MARTe;
|
||||||
|
|
||||||
|
const char8 * const config_text =
|
||||||
|
"+DebugService = {"
|
||||||
|
" Class = DebugService "
|
||||||
|
" ControlPort = 8080 "
|
||||||
|
" UdpPort = 8081 "
|
||||||
|
" StreamIP = \"127.0.0.1\" "
|
||||||
|
"}"
|
||||||
|
"+App = {"
|
||||||
|
" Class = RealTimeApplication "
|
||||||
|
" +Functions = {"
|
||||||
|
" Class = ReferenceContainer "
|
||||||
|
" +GAM1 = {"
|
||||||
|
" Class = IOGAM "
|
||||||
|
" InputSignals = {"
|
||||||
|
" Counter = {"
|
||||||
|
" DataSource = Timer "
|
||||||
|
" Type = uint32 "
|
||||||
|
" }"
|
||||||
|
" }"
|
||||||
|
" OutputSignals = {"
|
||||||
|
" Counter = {"
|
||||||
|
" DataSource = DDB "
|
||||||
|
" Type = uint32 "
|
||||||
|
" }"
|
||||||
|
" }"
|
||||||
|
" }"
|
||||||
|
" }"
|
||||||
|
" +Data = {"
|
||||||
|
" Class = ReferenceContainer "
|
||||||
|
" DefaultDataSource = DDB "
|
||||||
|
" +Timer = {"
|
||||||
|
" Class = LinuxTimer "
|
||||||
|
" SleepTime = 10000 "
|
||||||
|
" Signals = {"
|
||||||
|
" Counter = { Type = uint32 }"
|
||||||
|
" }"
|
||||||
|
" }"
|
||||||
|
" +DDB = {"
|
||||||
|
" Class = GAMDataSource "
|
||||||
|
" Signals = { Counter = { Type = uint32 } }"
|
||||||
|
" }"
|
||||||
|
" +DAMS = { Class = TimingDataSource }"
|
||||||
|
" }"
|
||||||
|
" +States = {"
|
||||||
|
" Class = ReferenceContainer "
|
||||||
|
" +State1 = {"
|
||||||
|
" Class = RealTimeState "
|
||||||
|
" +Threads = {"
|
||||||
|
" Class = ReferenceContainer "
|
||||||
|
" +Thread1 = {"
|
||||||
|
" Class = RealTimeThread "
|
||||||
|
" Functions = {GAM1} "
|
||||||
|
" }"
|
||||||
|
" }"
|
||||||
|
" }"
|
||||||
|
" }"
|
||||||
|
" +Scheduler = {"
|
||||||
|
" Class = GAMScheduler "
|
||||||
|
" TimingDataSource = DAMS "
|
||||||
|
" }"
|
||||||
|
"}";
|
||||||
|
|
||||||
|
void RunValidationTest() {
|
||||||
|
printf("--- MARTe2 100Hz Trace Validation Test ---\n");
|
||||||
|
|
||||||
|
// 1. Load Configuration
|
||||||
|
ConfigurationDatabase cdb;
|
||||||
|
StreamString ss = config_text;
|
||||||
|
ss.Seek(0);
|
||||||
|
StandardParser parser(ss, cdb);
|
||||||
|
if (!parser.Parse()) {
|
||||||
|
printf("ERROR: Failed to parse configuration\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ObjectRegistryDatabase::Instance()->Initialise(cdb)) {
|
||||||
|
printf("ERROR: Failed to initialise ObjectRegistryDatabase.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Start Application
|
||||||
|
ReferenceT<RealTimeApplication> app = ObjectRegistryDatabase::Instance()->Find("App");
|
||||||
|
if (!app.IsValid()) {
|
||||||
|
printf("ERROR: App not found\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We try to use State1 directly as many MARTe2 apps start in the first defined state if no transition is needed
|
||||||
|
if (app->PrepareNextState("State1") != ErrorManagement::NoError) {
|
||||||
|
printf("ERROR: Failed to prepare state State1\n");
|
||||||
|
// We will try to investigate why, but for now we continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app->StartNextStateExecution() != ErrorManagement::NoError) {
|
||||||
|
printf("ERROR: Failed to start execution. Maybe it needs an explicit state?\n");
|
||||||
|
// return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Application started at 100Hz.\n");
|
||||||
|
Sleep::MSec(1000);
|
||||||
|
|
||||||
|
// 3. Enable Trace via TCP (Simulating GUI)
|
||||||
|
BasicTCPSocket client;
|
||||||
|
if (client.Connect("127.0.0.1", 8080)) {
|
||||||
|
const char* cmd = "TRACE Root.App.Data.Timer.Counter 1\n";
|
||||||
|
uint32 s = StringHelper::Length(cmd);
|
||||||
|
client.Write(cmd, s);
|
||||||
|
|
||||||
|
char resp[1024]; s = 1024;
|
||||||
|
TimeoutType timeout(1000);
|
||||||
|
if (client.Read(resp, s, timeout)) {
|
||||||
|
resp[s] = '\0';
|
||||||
|
printf("Server Response: %s", resp);
|
||||||
|
} else {
|
||||||
|
printf("WARNING: No response from server to TRACE command.\n");
|
||||||
|
}
|
||||||
|
client.Close();
|
||||||
|
} else {
|
||||||
|
printf("ERROR: Failed to connect to DebugService on 8080\n");
|
||||||
|
// continue anyway to see if it's already working
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Setup UDP Listener
|
||||||
|
BasicUDPSocket listener;
|
||||||
|
if (!listener.Open()) { printf("ERROR: Failed to open UDP socket\n"); return; }
|
||||||
|
if (!listener.Listen(8081)) { printf("ERROR: Failed to listen on UDP 8081\n"); return; }
|
||||||
|
|
||||||
|
// 5. Validate for 30 seconds
|
||||||
|
printf("Validating telemetry for 30 seconds...\n");
|
||||||
|
uint32 lastVal = 0;
|
||||||
|
bool first = true;
|
||||||
|
uint32 packetCount = 0;
|
||||||
|
uint32 discontinuityCount = 0;
|
||||||
|
|
||||||
|
float64 startTime = HighResolutionTimer::Counter() * HighResolutionTimer::Period();
|
||||||
|
|
||||||
|
while ((HighResolutionTimer::Counter() * HighResolutionTimer::Period() - startTime) < 30.0) {
|
||||||
|
char buffer[2048];
|
||||||
|
uint32 size = 2048;
|
||||||
|
TimeoutType timeout(500);
|
||||||
|
|
||||||
|
if (listener.Read(buffer, size, timeout)) {
|
||||||
|
TraceHeader *h = (TraceHeader*)buffer;
|
||||||
|
if (h->magic == 0xDA7A57AD && h->count > 0) {
|
||||||
|
uint32 offset = sizeof(TraceHeader);
|
||||||
|
// Packet format: [Header][ID:4][Size:4][Value:N]
|
||||||
|
uint32 val = *(uint32*)(&buffer[offset + 8]);
|
||||||
|
|
||||||
|
if (!first) {
|
||||||
|
if (val != lastVal + 1) {
|
||||||
|
discontinuityCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastVal = val;
|
||||||
|
first = false;
|
||||||
|
packetCount++;
|
||||||
|
|
||||||
|
if (packetCount % 500 == 0) {
|
||||||
|
printf("Received %u packets... Current Value: %u\n", packetCount, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Test Finished.\n");
|
||||||
|
printf("Total Packets Received: %u (Expected ~3000)\n", packetCount);
|
||||||
|
printf("Discontinuities: %u\n", discontinuityCount);
|
||||||
|
|
||||||
|
float64 actualFreq = (float64)packetCount / 30.0;
|
||||||
|
printf("Average Frequency: %.2f Hz\n", actualFreq);
|
||||||
|
|
||||||
|
if (packetCount < 100) {
|
||||||
|
printf("FAILURE: Almost no packets received. Telemetry is broken.\n");
|
||||||
|
} else if (packetCount < 2500) {
|
||||||
|
printf("WARNING: Too few packets received (Expected 3000, Got %u).\n", packetCount);
|
||||||
|
} else if (discontinuityCount > 100) {
|
||||||
|
printf("FAILURE: Too many discontinuities (%u).\n", discontinuityCount);
|
||||||
|
} else {
|
||||||
|
printf("VALIDATION SUCCESSFUL!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
app->StopCurrentStateExecution();
|
||||||
|
listener.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
RunValidationTest();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
50
Test/Integration/main.cpp
Normal file
50
Test/Integration/main.cpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include "DebugService.h"
|
||||||
|
#include "MemoryMapInputBroker.h"
|
||||||
|
#include "ConfigurationDatabase.h"
|
||||||
|
#include "ObjectRegistryDatabase.h"
|
||||||
|
#include "ClassRegistryDatabase.h"
|
||||||
|
|
||||||
|
using namespace MARTe;
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
void timeout_handler(int sig) {
|
||||||
|
printf("Test timed out!\n");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
signal(SIGALRM, timeout_handler);
|
||||||
|
alarm(5); // 5 seconds timeout
|
||||||
|
printf("MARTe2 Debug Suite Integration Test\n");
|
||||||
|
|
||||||
|
{
|
||||||
|
// 1. Manually trigger Registry Patching
|
||||||
|
DebugService service;
|
||||||
|
ConfigurationDatabase serviceData;
|
||||||
|
serviceData.Write("ControlPort", (uint16)9090);
|
||||||
|
service.Initialise(serviceData);
|
||||||
|
|
||||||
|
printf("DebugService initialized and Registry Patched.\n");
|
||||||
|
|
||||||
|
// 2. Try to create a MemoryMapInputBroker
|
||||||
|
ClassRegistryItem *item = ClassRegistryDatabase::Instance()->Find("MemoryMapInputBroker");
|
||||||
|
if (item != NULL_PTR(ClassRegistryItem *)) {
|
||||||
|
Object *obj = item->GetObjectBuilder()->Build(GlobalObjectsDatabase::Instance()->GetStandardHeap());
|
||||||
|
if (obj != NULL_PTR(Object *)) {
|
||||||
|
printf("Instantiated Broker Class: %s\n", obj->GetClassProperties()->GetName());
|
||||||
|
printf("Success: Broker patched and instantiated.\n");
|
||||||
|
// delete obj;
|
||||||
|
} else {
|
||||||
|
printf("Failed to build broker\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("MemoryMapInputBroker not found in registry\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("DebugService scope finished.\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
19
Test/UnitTests/CMakeLists.txt
Normal file
19
Test/UnitTests/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(marte_dev_tests)
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L0Types
|
||||||
|
${MARTe2_DIR}/Source/Core/BareMetal/L1Portability
|
||||||
|
# ... more ...
|
||||||
|
../../Source
|
||||||
|
../../Headers
|
||||||
|
)
|
||||||
|
|
||||||
|
file(GLOB SOURCES "*.cpp")
|
||||||
|
|
||||||
|
add_executable(UnitTests ${SOURCES})
|
||||||
|
|
||||||
|
target_link_libraries(UnitTests
|
||||||
|
marte_dev
|
||||||
|
${MARTe2_DIR}/Build/${TARGET}/Core/libMARTe2.so
|
||||||
|
)
|
||||||
35
Test/UnitTests/main.cpp
Normal file
35
Test/UnitTests/main.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "DebugCore.h"
|
||||||
|
|
||||||
|
using namespace MARTe;
|
||||||
|
|
||||||
|
void TestRingBuffer() {
|
||||||
|
printf("Testing TraceRingBuffer...\n");
|
||||||
|
TraceRingBuffer rb;
|
||||||
|
assert(rb.Init(1024));
|
||||||
|
|
||||||
|
uint32 id = 42;
|
||||||
|
uint32 val = 12345678;
|
||||||
|
uint32 size = 4;
|
||||||
|
|
||||||
|
assert(rb.Push(id, &val, size));
|
||||||
|
assert(rb.Count() > 0);
|
||||||
|
|
||||||
|
uint32 poppedId = 0;
|
||||||
|
uint32 poppedVal = 0;
|
||||||
|
uint32 poppedSize = 0;
|
||||||
|
|
||||||
|
assert(rb.Pop(poppedId, &poppedVal, poppedSize, 4));
|
||||||
|
assert(poppedId == 42);
|
||||||
|
assert(poppedVal == 12345678);
|
||||||
|
assert(poppedSize == 4);
|
||||||
|
|
||||||
|
printf("TraceRingBuffer test passed.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
printf("Running MARTe2 Component Tests...\n");
|
||||||
|
TestRingBuffer();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
BIN
Tools/cli_client/debug_cli
Executable file
BIN
Tools/cli_client/debug_cli
Executable file
Binary file not shown.
17
Tools/cli_client/go.mod
Normal file
17
Tools/cli_client/go.mod
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module marte_debug_cli
|
||||||
|
|
||||||
|
go 1.25.7
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gdamore/tcell/v2 v2.13.8
|
||||||
|
github.com/rivo/tview v0.42.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gdamore/encoding v1.0.1 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
|
golang.org/x/term v0.37.0 // indirect
|
||||||
|
golang.org/x/text v0.31.0 // indirect
|
||||||
|
)
|
||||||
47
Tools/cli_client/go.sum
Normal file
47
Tools/cli_client/go.sum
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||||
|
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||||
|
github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU=
|
||||||
|
github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/rivo/tview v0.42.0 h1:b/ftp+RxtDsHSaynXTbJb+/n/BxDEi+W3UfF5jILK6c=
|
||||||
|
github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
|
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
498
Tools/cli_client/main.go
Normal file
498
Tools/cli_client/main.go
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/rivo/tview"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Models ---
|
||||||
|
|
||||||
|
type Signal struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ID uint32 `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscoverResponse struct {
|
||||||
|
Signals []Signal `json:"Signals"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TraceValue struct {
|
||||||
|
Value string
|
||||||
|
LastUpdate time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppState struct {
|
||||||
|
Signals map[string]Signal
|
||||||
|
IDToSignal map[uint32]string
|
||||||
|
ForcedSignals map[string]string
|
||||||
|
TracedSignals map[string]TraceValue
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = AppState{
|
||||||
|
Signals: make(map[string]Signal),
|
||||||
|
IDToSignal: make(map[uint32]string),
|
||||||
|
ForcedSignals: make(map[string]string),
|
||||||
|
TracedSignals: make(map[string]TraceValue),
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- UI Components ---
|
||||||
|
|
||||||
|
var (
|
||||||
|
app *tview.Application
|
||||||
|
forcedPane *tview.Table
|
||||||
|
tracedPane *tview.Table
|
||||||
|
statusPane *tview.TextView
|
||||||
|
frameworkLogPane *tview.TextView
|
||||||
|
cmdInput *tview.InputField
|
||||||
|
mainFlex *tview.Flex
|
||||||
|
)
|
||||||
|
|
||||||
|
// logToStatus prints command output WITHOUT timestamp
|
||||||
|
func logToStatus(msg string) {
|
||||||
|
fmt.Fprintf(statusPane, "%s\n", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// logToFramework prints framework logs WITH timestamp
|
||||||
|
func logToFramework(level, msg string) {
|
||||||
|
color := "white"
|
||||||
|
switch level {
|
||||||
|
case "FatalError", "OSError", "ParametersError":
|
||||||
|
color = "red"
|
||||||
|
case "Warning":
|
||||||
|
color = "yellow"
|
||||||
|
case "Information":
|
||||||
|
color = "green"
|
||||||
|
case "Debug":
|
||||||
|
color = "blue"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(frameworkLogPane, "[%s] [[%s]%s[-]] %s\n", time.Now().Format("15:04:05.000"), color, level, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Network Logic ---
|
||||||
|
|
||||||
|
var (
|
||||||
|
tcpConn net.Conn
|
||||||
|
tcpMu sync.Mutex
|
||||||
|
responseChan = make(chan string, 2000)
|
||||||
|
connected = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func connectAndMonitor(addr string) {
|
||||||
|
for {
|
||||||
|
conn, err := net.Dial("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
app.QueueUpdateDraw(func() { statusPane.SetText("[red]Connection failed, retrying...\n") })
|
||||||
|
connected = false
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc, ok := conn.(*net.TCPConn); ok {
|
||||||
|
tc.SetNoDelay(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpConn = conn
|
||||||
|
connected = true
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus("[green]Connected to DebugService (TCP 8080)") })
|
||||||
|
|
||||||
|
go discover()
|
||||||
|
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
var line strings.Builder
|
||||||
|
for {
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus("[red]TCP Disconnected: " + err.Error()) })
|
||||||
|
connected = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if buf[i] == '\n' {
|
||||||
|
fullLine := strings.TrimSpace(line.String())
|
||||||
|
line.Reset()
|
||||||
|
|
||||||
|
if strings.HasPrefix(fullLine, "LOG ") {
|
||||||
|
parts := strings.SplitN(fullLine[4:], " ", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
app.QueueUpdateDraw(func() { logToFramework(parts[0], parts[1]) })
|
||||||
|
}
|
||||||
|
} else if fullLine != "" {
|
||||||
|
select {
|
||||||
|
case responseChan <- fullLine:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
line.WriteByte(buf[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tcpConn.Close()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startUDPListener(port int) {
|
||||||
|
addr, _ := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port))
|
||||||
|
udpConn, err := net.ListenUDP("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus(fmt.Sprintf("UDP Error: %v", err)) })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer udpConn.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, 2048)
|
||||||
|
for {
|
||||||
|
n, _, err := udpConn.ReadFromUDP(buf)
|
||||||
|
if err != nil || n < 20 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
magic := binary.LittleEndian.Uint32(buf[0:4])
|
||||||
|
if magic != 0xDA7A57AD {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
count := binary.LittleEndian.Uint32(buf[16:20])
|
||||||
|
offset := 20
|
||||||
|
now := time.Now()
|
||||||
|
for i := uint32(0); i < count; i++ {
|
||||||
|
if offset+8 > n {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
id := binary.LittleEndian.Uint32(buf[offset : offset+4])
|
||||||
|
size := binary.LittleEndian.Uint32(buf[offset+4 : offset+8])
|
||||||
|
offset += 8
|
||||||
|
if offset+int(size) > n {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data := buf[offset : offset+int(size)]
|
||||||
|
offset += int(size)
|
||||||
|
|
||||||
|
state.mu.Lock()
|
||||||
|
if name, ok := state.IDToSignal[id]; ok {
|
||||||
|
valStr := ""
|
||||||
|
if size == 4 {
|
||||||
|
valStr = fmt.Sprintf("%d", binary.LittleEndian.Uint32(data))
|
||||||
|
} else if size == 8 {
|
||||||
|
valStr = fmt.Sprintf("%d", binary.LittleEndian.Uint64(data))
|
||||||
|
} else {
|
||||||
|
valStr = fmt.Sprintf("%X", data)
|
||||||
|
}
|
||||||
|
state.TracedSignals[name] = TraceValue{Value: valStr, LastUpdate: now}
|
||||||
|
}
|
||||||
|
state.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
app.QueueUpdateDraw(func() { updateTracedPane() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func discover() {
|
||||||
|
tcpMu.Lock()
|
||||||
|
defer tcpMu.Unlock()
|
||||||
|
if !connected {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus("Discovering signals...") })
|
||||||
|
|
||||||
|
// Drain old responses
|
||||||
|
for len(responseChan) > 0 {
|
||||||
|
<-responseChan
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(tcpConn, "DISCOVER\n")
|
||||||
|
|
||||||
|
var jsonBlock strings.Builder
|
||||||
|
timeout := time.After(5 * time.Second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case line := <-responseChan:
|
||||||
|
if line == "OK DISCOVER" {
|
||||||
|
goto parsed
|
||||||
|
}
|
||||||
|
jsonBlock.WriteString(line)
|
||||||
|
case <-timeout:
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus("[red]Discovery Timeout") })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsed:
|
||||||
|
var resp DiscoverResponse
|
||||||
|
if err := json.Unmarshal([]byte(jsonBlock.String()), &resp); err == nil {
|
||||||
|
state.mu.Lock()
|
||||||
|
state.IDToSignal = make(map[uint32]string)
|
||||||
|
for _, s := range resp.Signals {
|
||||||
|
state.Signals[s.Name] = s
|
||||||
|
state.IDToSignal[s.ID] = s.Name
|
||||||
|
}
|
||||||
|
state.mu.Unlock()
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus(fmt.Sprintf("Discovered %d signals", len(resp.Signals))) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- UI Updates ---
|
||||||
|
|
||||||
|
func updateForcedPane() {
|
||||||
|
forcedPane.Clear()
|
||||||
|
forcedPane.SetCell(0, 0, tview.NewTableCell("Signal").SetTextColor(tcell.ColorYellow).SetAttributes(tcell.AttrBold))
|
||||||
|
forcedPane.SetCell(0, 1, tview.NewTableCell("Value").SetTextColor(tcell.ColorYellow).SetAttributes(tcell.AttrBold))
|
||||||
|
state.mu.RLock()
|
||||||
|
defer state.mu.RUnlock()
|
||||||
|
keys := make([]string, 0, len(state.ForcedSignals))
|
||||||
|
for k := range state.ForcedSignals {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for i, k := range keys {
|
||||||
|
forcedPane.SetCell(i+1, 0, tview.NewTableCell(k))
|
||||||
|
forcedPane.SetCell(i+1, 1, tview.NewTableCell(state.ForcedSignals[k]).SetTextColor(tcell.ColorGreen))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTracedPane() {
|
||||||
|
tracedPane.Clear()
|
||||||
|
tracedPane.SetCell(0, 0, tview.NewTableCell("Signal").SetTextColor(tcell.ColorBlue).SetAttributes(tcell.AttrBold))
|
||||||
|
tracedPane.SetCell(0, 1, tview.NewTableCell("Value").SetTextColor(tcell.ColorBlue).SetAttributes(tcell.AttrBold))
|
||||||
|
tracedPane.SetCell(0, 2, tview.NewTableCell("Last Update").SetTextColor(tcell.ColorBlue).SetAttributes(tcell.AttrBold))
|
||||||
|
state.mu.RLock()
|
||||||
|
defer state.mu.RUnlock()
|
||||||
|
keys := make([]string, 0, len(state.TracedSignals))
|
||||||
|
for k := range state.TracedSignals {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for i, k := range keys {
|
||||||
|
tv := state.TracedSignals[k]
|
||||||
|
tracedPane.SetCell(i+1, 0, tview.NewTableCell(k))
|
||||||
|
tracedPane.SetCell(i+1, 1, tview.NewTableCell(tv.Value))
|
||||||
|
tracedPane.SetCell(i+1, 2, tview.NewTableCell(tv.LastUpdate.Format("15:04:05.000")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Command Handler ---
|
||||||
|
|
||||||
|
func handleInput(text string) {
|
||||||
|
parts := strings.Fields(text)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd := strings.ToUpper(parts[0])
|
||||||
|
args := parts[1:]
|
||||||
|
if !connected && cmd != "EXIT" && cmd != "QUIT" && cmd != "CLEAR" {
|
||||||
|
logToStatus("[red]Not connected to server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "LS":
|
||||||
|
path := ""
|
||||||
|
if len(args) > 0 {
|
||||||
|
path = args[0]
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
tcpMu.Lock()
|
||||||
|
defer tcpMu.Unlock()
|
||||||
|
for len(responseChan) > 0 {
|
||||||
|
<-responseChan
|
||||||
|
}
|
||||||
|
fmt.Fprintf(tcpConn, "LS %s\n", path)
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus(fmt.Sprintf("Listing nodes under %s...", path)) })
|
||||||
|
timeout := time.After(2 * time.Second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case line := <-responseChan:
|
||||||
|
if line == "OK LS" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus(line) })
|
||||||
|
case <-timeout:
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus("[red]LS Timeout") })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
case "FORCE":
|
||||||
|
if len(args) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path, val := args[0], args[1]
|
||||||
|
go func() {
|
||||||
|
tcpMu.Lock()
|
||||||
|
defer tcpMu.Unlock()
|
||||||
|
fmt.Fprintf(tcpConn, "FORCE %s %s\n", path, val)
|
||||||
|
select {
|
||||||
|
case resp := <-responseChan:
|
||||||
|
app.QueueUpdateDraw(func() {
|
||||||
|
logToStatus("Server: " + resp)
|
||||||
|
state.mu.Lock()
|
||||||
|
state.ForcedSignals[path] = val
|
||||||
|
state.mu.Unlock()
|
||||||
|
updateForcedPane()
|
||||||
|
})
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus("[red]Server Timeout") })
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
case "UNFORCE":
|
||||||
|
if len(args) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path := args[0]
|
||||||
|
go func() {
|
||||||
|
tcpMu.Lock()
|
||||||
|
defer tcpMu.Unlock()
|
||||||
|
fmt.Fprintf(tcpConn, "UNFORCE %s\n", path)
|
||||||
|
select {
|
||||||
|
case resp := <-responseChan:
|
||||||
|
app.QueueUpdateDraw(func() {
|
||||||
|
logToStatus("Server: " + resp)
|
||||||
|
state.mu.Lock()
|
||||||
|
delete(state.ForcedSignals, path)
|
||||||
|
state.mu.Unlock()
|
||||||
|
updateForcedPane()
|
||||||
|
})
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus("[red]Server Timeout") })
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
case "TRACE":
|
||||||
|
if len(args) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path := args[0]
|
||||||
|
decim := "1"
|
||||||
|
if len(args) > 1 {
|
||||||
|
decim = args[1]
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
tcpMu.Lock()
|
||||||
|
defer tcpMu.Unlock()
|
||||||
|
fmt.Fprintf(tcpConn, "TRACE %s 1 %s\n", path, decim)
|
||||||
|
select {
|
||||||
|
case resp := <-responseChan:
|
||||||
|
app.QueueUpdateDraw(func() {
|
||||||
|
logToStatus("Server: " + resp)
|
||||||
|
state.mu.Lock()
|
||||||
|
state.TracedSignals[path] = TraceValue{Value: "...", LastUpdate: time.Now()}
|
||||||
|
state.mu.Unlock()
|
||||||
|
updateTracedPane()
|
||||||
|
})
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus("[red]Server Timeout") })
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
case "UNTRACE":
|
||||||
|
if len(args) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path := args[0]
|
||||||
|
go func() {
|
||||||
|
tcpMu.Lock()
|
||||||
|
defer tcpMu.Unlock()
|
||||||
|
fmt.Fprintf(tcpConn, "UNTRACE %s\n", path)
|
||||||
|
select {
|
||||||
|
case resp := <-responseChan:
|
||||||
|
app.QueueUpdateDraw(func() {
|
||||||
|
logToStatus("Server: " + resp)
|
||||||
|
state.mu.Lock()
|
||||||
|
delete(state.TracedSignals, path)
|
||||||
|
state.mu.Unlock()
|
||||||
|
updateTracedPane()
|
||||||
|
})
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
app.QueueUpdateDraw(func() { logToStatus("[red]Server Timeout") })
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
case "DISCOVER":
|
||||||
|
go discover()
|
||||||
|
case "CLEAR":
|
||||||
|
statusPane.Clear()
|
||||||
|
frameworkLogPane.Clear()
|
||||||
|
case "EXIT", "QUIT":
|
||||||
|
app.Stop()
|
||||||
|
default:
|
||||||
|
logToStatus("Unknown command: " + cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app = tview.NewApplication()
|
||||||
|
|
||||||
|
forcedPane = tview.NewTable()
|
||||||
|
forcedPane.SetBorders(true)
|
||||||
|
forcedPane.SetTitle(" Forced Signals ")
|
||||||
|
forcedPane.SetBorder(true)
|
||||||
|
|
||||||
|
tracedPane = tview.NewTable()
|
||||||
|
tracedPane.SetBorders(true)
|
||||||
|
tracedPane.SetTitle(" Traced Signals (Live) ")
|
||||||
|
tracedPane.SetBorder(true)
|
||||||
|
|
||||||
|
statusPane = tview.NewTextView()
|
||||||
|
statusPane.SetDynamicColors(true)
|
||||||
|
statusPane.SetRegions(true)
|
||||||
|
statusPane.SetWordWrap(true)
|
||||||
|
statusPane.SetChangedFunc(func() {
|
||||||
|
statusPane.ScrollToEnd()
|
||||||
|
app.Draw()
|
||||||
|
})
|
||||||
|
statusPane.SetTitle(" CLI Status / Command Output ")
|
||||||
|
statusPane.SetBorder(true)
|
||||||
|
|
||||||
|
frameworkLogPane = tview.NewTextView()
|
||||||
|
frameworkLogPane.SetDynamicColors(true)
|
||||||
|
frameworkLogPane.SetRegions(true)
|
||||||
|
frameworkLogPane.SetWordWrap(true)
|
||||||
|
frameworkLogPane.SetChangedFunc(func() {
|
||||||
|
frameworkLogPane.ScrollToEnd()
|
||||||
|
app.Draw()
|
||||||
|
})
|
||||||
|
frameworkLogPane.SetTitle(" MARTe2 Framework Logs ")
|
||||||
|
frameworkLogPane.SetBorder(true)
|
||||||
|
|
||||||
|
cmdInput = tview.NewInputField()
|
||||||
|
cmdInput.SetLabel("marte_debug> ")
|
||||||
|
cmdInput.SetFieldWidth(0)
|
||||||
|
cmdInput.SetDoneFunc(func(key tcell.Key) {
|
||||||
|
if key == tcell.KeyEnter {
|
||||||
|
text := cmdInput.GetText()
|
||||||
|
if text != "" {
|
||||||
|
handleInput(text)
|
||||||
|
cmdInput.SetText("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
flexTop := tview.NewFlex()
|
||||||
|
flexTop.SetDirection(tview.FlexColumn)
|
||||||
|
flexTop.AddItem(forcedPane, 0, 1, false)
|
||||||
|
flexTop.AddItem(tracedPane, 0, 1, false)
|
||||||
|
flexTop.AddItem(statusPane, 0, 1, false)
|
||||||
|
|
||||||
|
mainFlex = tview.NewFlex()
|
||||||
|
mainFlex.SetDirection(tview.FlexRow)
|
||||||
|
mainFlex.AddItem(flexTop, 0, 2, false)
|
||||||
|
mainFlex.AddItem(frameworkLogPane, 0, 1, false)
|
||||||
|
mainFlex.AddItem(cmdInput, 1, 0, true)
|
||||||
|
|
||||||
|
go connectAndMonitor("127.0.0.1:8080")
|
||||||
|
go startUDPListener(8081)
|
||||||
|
|
||||||
|
if err := app.SetRoot(mainFlex, true).EnableMouse(true).Run(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Tools/cli_client/marte_debug_cli
Executable file
BIN
Tools/cli_client/marte_debug_cli
Executable file
Binary file not shown.
4526
Tools/gui_client/Cargo.lock
generated
Normal file
4526
Tools/gui_client/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Tools/gui_client/Cargo.toml
Normal file
16
Tools/gui_client/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "marte_debug_gui"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = "0.31.0"
|
||||||
|
egui = "0.31.0"
|
||||||
|
egui_plot = "0.31.0"
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
byteorder = "1.4"
|
||||||
|
chrono = "0.4"
|
||||||
|
crossbeam-channel = "0.5"
|
||||||
|
regex = "1.12.3"
|
||||||
673
Tools/gui_client/src/main.rs
Normal file
673
Tools/gui_client/src/main.rs
Normal file
@@ -0,0 +1,673 @@
|
|||||||
|
use eframe::egui;
|
||||||
|
use egui_plot::{Line, Plot, PlotPoints};
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
use std::net::{TcpStream, UdpSocket};
|
||||||
|
use std::io::{Write, BufReader, BufRead};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use chrono::Local;
|
||||||
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
// --- Models ---
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
struct Signal {
|
||||||
|
name: String,
|
||||||
|
id: u32,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
sig_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DiscoverResponse {
|
||||||
|
#[serde(rename = "Signals")]
|
||||||
|
signals: Vec<Signal>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
struct TreeItem {
|
||||||
|
#[serde(rename = "Name")]
|
||||||
|
name: String,
|
||||||
|
#[serde(rename = "Class")]
|
||||||
|
class: String,
|
||||||
|
#[serde(rename = "Children")]
|
||||||
|
children: Option<Vec<TreeItem>>,
|
||||||
|
#[serde(rename = "Type")]
|
||||||
|
sig_type: Option<String>,
|
||||||
|
#[serde(rename = "Dimensions")]
|
||||||
|
dimensions: Option<u8>,
|
||||||
|
#[serde(rename = "Elements")]
|
||||||
|
elements: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct LogEntry {
|
||||||
|
time: String,
|
||||||
|
level: String,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TraceData {
|
||||||
|
values: VecDeque<[f64; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SignalMetadata {
|
||||||
|
names: Vec<String>,
|
||||||
|
sig_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InternalEvent {
|
||||||
|
Log(LogEntry),
|
||||||
|
Discovery(Vec<Signal>),
|
||||||
|
Tree(TreeItem),
|
||||||
|
CommandResponse(String),
|
||||||
|
NodeInfo(String),
|
||||||
|
Connected,
|
||||||
|
InternalLog(String),
|
||||||
|
TraceRequested(String),
|
||||||
|
ClearTrace(String),
|
||||||
|
UdpStats(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- App State ---
|
||||||
|
|
||||||
|
struct ForcingDialog {
|
||||||
|
signal_path: String,
|
||||||
|
sig_type: String,
|
||||||
|
dims: u8,
|
||||||
|
elems: u32,
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LogFilters {
|
||||||
|
content_regex: String,
|
||||||
|
show_debug: bool,
|
||||||
|
show_info: bool,
|
||||||
|
show_warning: bool,
|
||||||
|
show_error: bool,
|
||||||
|
paused: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MarteDebugApp {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
connected: bool,
|
||||||
|
tcp_addr: String,
|
||||||
|
log_addr: String,
|
||||||
|
|
||||||
|
signals: Vec<Signal>,
|
||||||
|
app_tree: Option<TreeItem>,
|
||||||
|
|
||||||
|
traced_signals: Arc<Mutex<HashMap<String, TraceData>>>,
|
||||||
|
id_to_meta: Arc<Mutex<HashMap<u32, SignalMetadata>>>,
|
||||||
|
|
||||||
|
forced_signals: HashMap<String, String>,
|
||||||
|
is_paused: bool,
|
||||||
|
|
||||||
|
logs: VecDeque<LogEntry>,
|
||||||
|
log_filters: LogFilters,
|
||||||
|
|
||||||
|
// UI Panels
|
||||||
|
show_left_panel: bool,
|
||||||
|
show_right_panel: bool,
|
||||||
|
show_bottom_panel: bool,
|
||||||
|
|
||||||
|
selected_node: String,
|
||||||
|
node_info: String,
|
||||||
|
|
||||||
|
udp_packets: u64,
|
||||||
|
|
||||||
|
forcing_dialog: Option<ForcingDialog>,
|
||||||
|
|
||||||
|
tx_cmd: Sender<String>,
|
||||||
|
rx_events: Receiver<InternalEvent>,
|
||||||
|
internal_tx: Sender<InternalEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MarteDebugApp {
|
||||||
|
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
|
let (tx_cmd, rx_cmd_internal) = unbounded::<String>();
|
||||||
|
let (tx_events, rx_events) = unbounded::<InternalEvent>();
|
||||||
|
let internal_tx = tx_events.clone();
|
||||||
|
|
||||||
|
let tcp_addr = "127.0.0.1:8080".to_string();
|
||||||
|
let log_addr = "127.0.0.1:8082".to_string();
|
||||||
|
|
||||||
|
let id_to_meta = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
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 tx_events_c = tx_events.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
tcp_command_worker(tcp_addr, rx_cmd_internal, tx_events_c);
|
||||||
|
});
|
||||||
|
|
||||||
|
let tx_events_log = tx_events.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
tcp_log_worker(log_addr, tx_events_log);
|
||||||
|
});
|
||||||
|
|
||||||
|
let tx_events_udp = tx_events.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
udp_worker(8081, id_to_meta_clone, traced_signals_clone, tx_events_udp);
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
connected: false,
|
||||||
|
tcp_addr: "127.0.0.1:8080".to_string(),
|
||||||
|
log_addr: "127.0.0.1:8082".to_string(),
|
||||||
|
signals: Vec::new(),
|
||||||
|
app_tree: None,
|
||||||
|
id_to_meta,
|
||||||
|
traced_signals,
|
||||||
|
forced_signals: HashMap::new(),
|
||||||
|
is_paused: false,
|
||||||
|
logs: VecDeque::with_capacity(2000),
|
||||||
|
log_filters: LogFilters {
|
||||||
|
content_regex: "".to_string(),
|
||||||
|
show_debug: true,
|
||||||
|
show_info: true,
|
||||||
|
show_warning: true,
|
||||||
|
show_error: true,
|
||||||
|
paused: false,
|
||||||
|
},
|
||||||
|
show_left_panel: true,
|
||||||
|
show_right_panel: true,
|
||||||
|
show_bottom_panel: true,
|
||||||
|
selected_node: "".to_string(),
|
||||||
|
node_info: "".to_string(),
|
||||||
|
udp_packets: 0,
|
||||||
|
forcing_dialog: None,
|
||||||
|
tx_cmd,
|
||||||
|
rx_events,
|
||||||
|
internal_tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_tree(&mut self, ui: &mut egui::Ui, item: &TreeItem, path: String) {
|
||||||
|
let current_path = if path.is_empty() {
|
||||||
|
item.name.clone()
|
||||||
|
} else if path == "Root" {
|
||||||
|
item.name.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}.{}", path, item.name)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(children) = &item.children {
|
||||||
|
let header = egui::CollapsingHeader::new(format!("{} [{}]", item.name, item.class))
|
||||||
|
.id_salt(¤t_path);
|
||||||
|
|
||||||
|
header.show(ui, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for child in children {
|
||||||
|
self.render_tree(ui, child, current_path.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.selectable_label(self.selected_node == current_path, format!("{} [{}]", item.name, item.class)).clicked() {
|
||||||
|
self.selected_node = current_path.clone();
|
||||||
|
let _ = self.tx_cmd.send(format!("INFO {}", current_path));
|
||||||
|
}
|
||||||
|
if item.class.contains("Signal") {
|
||||||
|
if ui.button("📈 Trace").clicked() {
|
||||||
|
let _ = self.tx_cmd.send(format!("TRACE {} 1", current_path));
|
||||||
|
let _ = self.internal_tx.send(InternalEvent::TraceRequested(current_path.clone()));
|
||||||
|
}
|
||||||
|
if ui.button("⚡ Force").clicked() {
|
||||||
|
self.forcing_dialog = Some(ForcingDialog {
|
||||||
|
signal_path: current_path.clone(),
|
||||||
|
sig_type: item.sig_type.clone().unwrap_or_else(|| "Unknown".to_string()),
|
||||||
|
dims: item.dimensions.unwrap_or(0),
|
||||||
|
elems: item.elements.unwrap_or(1),
|
||||||
|
value: "".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tcp_command_worker(addr: String, rx_cmd: Receiver<String>, tx_events: Sender<InternalEvent>) {
|
||||||
|
loop {
|
||||||
|
if let Ok(mut stream) = TcpStream::connect(&addr) {
|
||||||
|
let _ = stream.set_nodelay(true);
|
||||||
|
let mut reader = BufReader::new(stream.try_clone().unwrap());
|
||||||
|
let _ = tx_events.send(InternalEvent::Connected);
|
||||||
|
|
||||||
|
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() {
|
||||||
|
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 == "OK DISCOVER" {
|
||||||
|
in_json = false;
|
||||||
|
let json_clean = json_acc.trim_end_matches("OK DISCOVER").trim();
|
||||||
|
match serde_json::from_str::<DiscoverResponse>(json_clean) {
|
||||||
|
Ok(resp) => { let _ = tx_events_inner.send(InternalEvent::Discovery(resp.signals)); }
|
||||||
|
Err(e) => { let _ = tx_events_inner.send(InternalEvent::InternalLog(format!("JSON Parse Error (Discover): {}", e))); }
|
||||||
|
}
|
||||||
|
json_acc.clear();
|
||||||
|
} else if trimmed == "OK TREE" {
|
||||||
|
in_json = false;
|
||||||
|
let json_clean = json_acc.trim_end_matches("OK TREE").trim();
|
||||||
|
match serde_json::from_str::<TreeItem>(json_clean) {
|
||||||
|
Ok(resp) => { let _ = tx_events_inner.send(InternalEvent::Tree(resp)); }
|
||||||
|
Err(e) => { let _ = tx_events_inner.send(InternalEvent::InternalLog(format!("JSON Parse Error (Tree): {}", e))); }
|
||||||
|
}
|
||||||
|
json_acc.clear();
|
||||||
|
} else if trimmed == "OK INFO" {
|
||||||
|
in_json = false;
|
||||||
|
let json_clean = json_acc.trim_end_matches("OK INFO").trim();
|
||||||
|
let _ = tx_events_inner.send(InternalEvent::NodeInfo(json_clean.to_string()));
|
||||||
|
json_acc.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = tx_events_inner.send(InternalEvent::CommandResponse(trimmed.to_string()));
|
||||||
|
}
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while let Ok(cmd) = rx_cmd.recv() {
|
||||||
|
if stream.write_all(format!("{}\n", cmd).as_bytes()).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thread::sleep(std::time::Duration::from_secs(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tcp_log_worker(addr: String, tx_events: Sender<InternalEvent>) {
|
||||||
|
loop {
|
||||||
|
if let Ok(stream) = TcpStream::connect(&addr) {
|
||||||
|
let mut reader = BufReader::new(stream);
|
||||||
|
let mut line = String::new();
|
||||||
|
while reader.read_line(&mut line).is_ok() {
|
||||||
|
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 udp_worker(port: u16, id_to_meta: Arc<Mutex<HashMap<u32, SignalMetadata>>>, traced_data: Arc<Mutex<HashMap<String, TraceData>>>, tx_events: Sender<InternalEvent>) {
|
||||||
|
if let Ok(socket) = UdpSocket::bind(format!("0.0.0.0:{}", port)) {
|
||||||
|
let mut buf = [0u8; 4096];
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
let mut total_packets = 0u64;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Ok(n) = socket.recv(&mut buf) {
|
||||||
|
total_packets += 1;
|
||||||
|
if (total_packets % 100) == 0 {
|
||||||
|
let _ = tx_events.send(InternalEvent::UdpStats(total_packets));
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < 20 { continue; }
|
||||||
|
|
||||||
|
let mut magic_buf = [0u8; 4]; magic_buf.copy_from_slice(&buf[0..4]);
|
||||||
|
if u32::from_le_bytes(magic_buf) != 0xDA7A57AD { continue; }
|
||||||
|
|
||||||
|
let mut count_buf = [0u8; 4]; count_buf.copy_from_slice(&buf[16..20]);
|
||||||
|
let count = u32::from_le_bytes(count_buf);
|
||||||
|
|
||||||
|
let mut offset = 20;
|
||||||
|
let now = start_time.elapsed().as_secs_f64();
|
||||||
|
|
||||||
|
let metas = id_to_meta.lock().unwrap();
|
||||||
|
let mut data_map = traced_data.lock().unwrap();
|
||||||
|
|
||||||
|
for _ in 0..count {
|
||||||
|
if offset + 8 > n { break; }
|
||||||
|
let mut id_buf = [0u8; 4]; id_buf.copy_from_slice(&buf[offset..offset+4]);
|
||||||
|
let id = u32::from_le_bytes(id_buf);
|
||||||
|
let mut size_buf = [0u8; 4]; size_buf.copy_from_slice(&buf[offset+4..offset+8]);
|
||||||
|
let size = u32::from_le_bytes(size_buf);
|
||||||
|
offset += 8;
|
||||||
|
if offset + size as usize > n { break; }
|
||||||
|
|
||||||
|
let data_slice = &buf[offset..offset + size as usize];
|
||||||
|
|
||||||
|
if let Some(meta) = metas.get(&id) {
|
||||||
|
let t = meta.sig_type.as_str();
|
||||||
|
let val = match size {
|
||||||
|
1 => { if t.contains('u') { data_slice[0] as f64 } else { (data_slice[0] as i8) as f64 } },
|
||||||
|
2 => {
|
||||||
|
let mut b = [0u8; 2]; b.copy_from_slice(data_slice);
|
||||||
|
if t.contains('u') { u16::from_le_bytes(b) as f64 } else { i16::from_le_bytes(b) as f64 }
|
||||||
|
},
|
||||||
|
4 => {
|
||||||
|
let mut b = [0u8; 4]; b.copy_from_slice(data_slice);
|
||||||
|
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 mut b = [0u8; 8]; b.copy_from_slice(data_slice);
|
||||||
|
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 {
|
||||||
|
if let Some(entry) = data_map.get_mut(name) {
|
||||||
|
entry.values.push_back([now, val]);
|
||||||
|
if entry.values.len() > 2000 { entry.values.pop_front(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += size as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
match event {
|
||||||
|
InternalEvent::Log(log) => {
|
||||||
|
if !self.log_filters.paused {
|
||||||
|
self.logs.push_back(log);
|
||||||
|
if self.logs.len() > 2000 { self.logs.pop_front(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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::Discovery(signals) => {
|
||||||
|
let mut metas = self.id_to_meta.lock().unwrap();
|
||||||
|
metas.clear();
|
||||||
|
for s in &signals {
|
||||||
|
let meta = metas.entry(s.id).or_insert_with(|| SignalMetadata { names: Vec::new(), sig_type: s.sig_type.clone() });
|
||||||
|
if !meta.names.contains(&s.name) { meta.names.push(s.name.clone()); }
|
||||||
|
}
|
||||||
|
self.signals = signals;
|
||||||
|
}
|
||||||
|
InternalEvent::Tree(tree) => {
|
||||||
|
self.app_tree = Some(tree);
|
||||||
|
}
|
||||||
|
InternalEvent::NodeInfo(info) => {
|
||||||
|
self.node_info = info;
|
||||||
|
}
|
||||||
|
InternalEvent::TraceRequested(name) => {
|
||||||
|
let mut data_map = self.traced_signals.lock().unwrap();
|
||||||
|
data_map.entry(name).or_insert_with(|| TraceData { values: VecDeque::with_capacity(2000) });
|
||||||
|
}
|
||||||
|
InternalEvent::ClearTrace(name) => {
|
||||||
|
let mut data_map = self.traced_signals.lock().unwrap();
|
||||||
|
data_map.remove(&name);
|
||||||
|
}
|
||||||
|
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::UdpStats(count) => {
|
||||||
|
self.udp_packets = count;
|
||||||
|
}
|
||||||
|
InternalEvent::Connected => {
|
||||||
|
let _ = self.tx_cmd.send("TREE".to_string());
|
||||||
|
let _ = self.tx_cmd.send("DISCOVER".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(dialog) = &mut self.forcing_dialog {
|
||||||
|
let mut close = false;
|
||||||
|
egui::Window::new("Force Signal").collapsible(false).resizable(false).show(ctx, |ui| {
|
||||||
|
ui.label(format!("Signal: {}", dialog.signal_path));
|
||||||
|
ui.label(format!("Type: {} (Dims: {}, Elems: {})", dialog.sig_type, dialog.dims, dialog.elems));
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Value:");
|
||||||
|
ui.text_edit_singleline(&mut dialog.value);
|
||||||
|
});
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Apply Force").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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.toggle_value(&mut self.show_left_panel, "🗂 Tree");
|
||||||
|
ui.toggle_value(&mut self.show_right_panel, "⚙ Debug");
|
||||||
|
ui.toggle_value(&mut self.show_bottom_panel, "📜 Logs");
|
||||||
|
ui.separator();
|
||||||
|
ui.heading("MARTe2 Debug Explorer");
|
||||||
|
ui.separator();
|
||||||
|
if ui.button("🔄 Refresh").clicked() {
|
||||||
|
let _ = self.tx_cmd.send("TREE".to_string());
|
||||||
|
let _ = self.tx_cmd.send("DISCOVER".to_string());
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
if self.is_paused {
|
||||||
|
if ui.button("▶ Resume").clicked() {
|
||||||
|
let _ = self.tx_cmd.send("RESUME".to_string());
|
||||||
|
self.is_paused = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ui.button("⏸ Pause").clicked() {
|
||||||
|
let _ = self.tx_cmd.send("PAUSE".to_string());
|
||||||
|
self.is_paused = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
|
ui.label(format!("UDP Packets: {}", self.udp_packets));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if self.show_bottom_panel {
|
||||||
|
egui::TopBottomPanel::bottom("log_panel").resizable(true).default_height(200.0).show(ctx, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.heading("System Logs");
|
||||||
|
ui.separator();
|
||||||
|
ui.label("Filter:");
|
||||||
|
ui.text_edit_singleline(&mut self.log_filters.content_regex);
|
||||||
|
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.toggle_value(&mut self.log_filters.paused, "⏸ Pause Logs");
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
|
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| {
|
||||||
|
for log in &self.logs {
|
||||||
|
let show = match log.level.as_str() {
|
||||||
|
"Debug" => self.log_filters.show_debug,
|
||||||
|
"Information" => self.log_filters.show_info,
|
||||||
|
"Warning" => self.log_filters.show_warning,
|
||||||
|
"FatalError" | "OSError" | "ParametersError" => self.log_filters.show_error,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
if !show { continue; }
|
||||||
|
if let Some(re) = ®ex { if !re.is_match(&log.message) && !re.is_match(&log.level) { continue; } }
|
||||||
|
|
||||||
|
let color = match log.level.as_str() {
|
||||||
|
"FatalError" | "OSError" | "ParametersError" => egui::Color32::from_rgb(255, 100, 100),
|
||||||
|
"Warning" => egui::Color32::from_rgb(255, 255, 100),
|
||||||
|
"Information" => egui::Color32::from_rgb(100, 255, 100),
|
||||||
|
"Debug" => egui::Color32::from_rgb(100, 100, 255),
|
||||||
|
"GUI_ERROR" => egui::Color32::from_rgb(255, 50, 255),
|
||||||
|
"CMD_RESP" => egui::Color32::from_rgb(255, 255, 255),
|
||||||
|
_ => egui::Color32::WHITE,
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(egui::RichText::new(&log.time).monospace().color(egui::Color32::GRAY));
|
||||||
|
ui.label(egui::RichText::new(format!("[{}]", log.level)).color(color).strong());
|
||||||
|
ui.add(egui::Label::new(&log.message).wrap());
|
||||||
|
ui.allocate_space(egui::vec2(ui.available_width(), 0.0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.show_left_panel {
|
||||||
|
egui::SidePanel::left("signals_panel").resizable(true).width_range(300.0..=600.0).show(ctx, |ui| {
|
||||||
|
ui.heading("Application Tree");
|
||||||
|
ui.separator();
|
||||||
|
egui::ScrollArea::vertical().id_salt("tree_scroll").show(ui, |ui| {
|
||||||
|
if let Some(tree) = self.app_tree.clone() {
|
||||||
|
self.render_tree(ui, &tree, "".to_string());
|
||||||
|
} else {
|
||||||
|
ui.label("Connecting to server...");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.heading("Node Information");
|
||||||
|
egui::ScrollArea::vertical().id_salt("info_scroll").show(ui, |ui| {
|
||||||
|
if self.node_info.is_empty() {
|
||||||
|
ui.label("Click 'Info' on a node to view details");
|
||||||
|
} else {
|
||||||
|
ui.add(egui::Label::new(egui::RichText::new(&self.node_info).monospace()).wrap());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.show_right_panel {
|
||||||
|
egui::SidePanel::right("debug_panel").resizable(true).width_range(200.0..=400.0).show(ctx, |ui| {
|
||||||
|
ui.heading("Active Controls");
|
||||||
|
ui.separator();
|
||||||
|
ui.label(egui::RichText::new("Forced Signals").strong());
|
||||||
|
egui::ScrollArea::vertical().id_salt("forced_scroll").show(ui, |ui| {
|
||||||
|
let mut to_update = None;
|
||||||
|
let mut to_remove = None;
|
||||||
|
for (path, val) in &self.forced_signals {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.selectable_label(false, format!("{}: {}", path, val)).clicked() {
|
||||||
|
to_update = Some(path.clone());
|
||||||
|
}
|
||||||
|
if ui.button("❌").clicked() {
|
||||||
|
let _ = self.tx_cmd.send(format!("UNFORCE {}", path));
|
||||||
|
to_remove = Some(path.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(p) = to_remove { self.forced_signals.remove(&p); }
|
||||||
|
if let Some(p) = to_update {
|
||||||
|
self.forcing_dialog = Some(ForcingDialog {
|
||||||
|
signal_path: p.clone(), sig_type: "Unknown".to_string(), dims: 0, elems: 1,
|
||||||
|
value: self.forced_signals.get(&p).unwrap().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.label(egui::RichText::new("Traced Signals").strong());
|
||||||
|
egui::ScrollArea::vertical().id_salt("traced_scroll").show(ui, |ui| {
|
||||||
|
let mut names: Vec<_> = {
|
||||||
|
let data_map = self.traced_signals.lock().unwrap();
|
||||||
|
data_map.keys().cloned().collect()
|
||||||
|
};
|
||||||
|
names.sort();
|
||||||
|
for key in names {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(&key);
|
||||||
|
if ui.button("❌").clicked() {
|
||||||
|
let _ = self.tx_cmd.send(format!("TRACE {} 0", key));
|
||||||
|
let _ = self.internal_tx.send(InternalEvent::ClearTrace(key));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.heading("Oscilloscope");
|
||||||
|
let plot = Plot::new("traces_plot")
|
||||||
|
.legend(egui_plot::Legend::default())
|
||||||
|
.auto_bounds_x()
|
||||||
|
.auto_bounds_y()
|
||||||
|
.y_axis_min_width(4.0);
|
||||||
|
|
||||||
|
plot.show(ui, |plot_ui| {
|
||||||
|
let data_map = self.traced_signals.lock().unwrap();
|
||||||
|
for (name, data) in data_map.iter() {
|
||||||
|
let points: PlotPoints = data.values.iter().cloned().collect();
|
||||||
|
plot_ui.line(Line::new(points).name(name));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.request_repaint_after(std::time::Duration::from_millis(16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
Box::new(|cc| Ok(Box::new(MarteDebugApp::new(cc)))),
|
||||||
|
)
|
||||||
|
}
|
||||||
180
compile_commands.json
Normal file
180
compile_commands.json
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"file": "CMakeCCompilerId.c",
|
||||||
|
"arguments": [
|
||||||
|
"cc",
|
||||||
|
"CMakeCCompilerId.c"
|
||||||
|
],
|
||||||
|
"directory": "/home/martino/Projects/marte_debug/Build/CMakeFiles/4.2.3/CompilerIdC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "CMakeCXXCompilerId.cpp",
|
||||||
|
"arguments": [
|
||||||
|
"c++",
|
||||||
|
"CMakeCXXCompilerId.cpp"
|
||||||
|
],
|
||||||
|
"directory": "/home/martino/Projects/marte_debug/Build/CMakeFiles/4.2.3/CompilerIdCXX"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "/usr/share/cmake/Modules/CMakeCCompilerABI.c",
|
||||||
|
"arguments": [
|
||||||
|
"cc",
|
||||||
|
"-v",
|
||||||
|
"-o",
|
||||||
|
"CMakeFiles/cmTC_27c6b.dir/CMakeCCompilerABI.c.o",
|
||||||
|
"-c",
|
||||||
|
"/usr/share/cmake/Modules/CMakeCCompilerABI.c"
|
||||||
|
],
|
||||||
|
"directory": "/home/martino/Projects/marte_debug/Build/CMakeFiles/CMakeScratch/TryCompile-0cxTtB",
|
||||||
|
"output": "CMakeFiles/cmTC_27c6b.dir/CMakeCCompilerABI.c.o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "/usr/share/cmake/Modules/CMakeCXXCompilerABI.cpp",
|
||||||
|
"arguments": [
|
||||||
|
"c++",
|
||||||
|
"-v",
|
||||||
|
"-o",
|
||||||
|
"CMakeFiles/cmTC_2b812.dir/CMakeCXXCompilerABI.cpp.o",
|
||||||
|
"-c",
|
||||||
|
"/usr/share/cmake/Modules/CMakeCXXCompilerABI.cpp"
|
||||||
|
],
|
||||||
|
"directory": "/home/martino/Projects/marte_debug/Build/CMakeFiles/CMakeScratch/TryCompile-JvDZsz",
|
||||||
|
"output": "CMakeFiles/cmTC_2b812.dir/CMakeCXXCompilerABI.cpp.o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "/home/martino/Projects/marte_debug/Source/DebugService.cpp",
|
||||||
|
"arguments": [
|
||||||
|
"c++",
|
||||||
|
"-DARCHITECTURE=x86_gcc",
|
||||||
|
"-DENVIRONMENT=Linux",
|
||||||
|
"-DMARTe2_TEST_ENVIRONMENT=GTest",
|
||||||
|
"-DUSE_PTHREAD",
|
||||||
|
"-Dmarte_dev_EXPORTS",
|
||||||
|
"-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/L4Configuration",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Events",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Logger",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Messages",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L5FILES",
|
||||||
|
"-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/L4Configuration",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4LoggerService",
|
||||||
|
"-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/EpicsDataSource",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2-components/Source/Components/DataSources/FileDataSource",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2-components/Source/Components/GAMs/IOGAM",
|
||||||
|
"-I/home/martino/Projects/marte_debug/Source",
|
||||||
|
"-I/home/martino/Projects/marte_debug/Headers",
|
||||||
|
"-pthread",
|
||||||
|
"-fPIC",
|
||||||
|
"-MD",
|
||||||
|
"-MT",
|
||||||
|
"CMakeFiles/marte_dev.dir/Source/DebugService.cpp.o",
|
||||||
|
"-MF",
|
||||||
|
"CMakeFiles/marte_dev.dir/Source/DebugService.cpp.o.d",
|
||||||
|
"-o",
|
||||||
|
"CMakeFiles/marte_dev.dir/Source/DebugService.cpp.o",
|
||||||
|
"-c",
|
||||||
|
"/home/martino/Projects/marte_debug/Source/DebugService.cpp"
|
||||||
|
],
|
||||||
|
"directory": "/home/martino/Projects/marte_debug/Build",
|
||||||
|
"output": "CMakeFiles/marte_dev.dir/Source/DebugService.cpp.o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "/home/martino/Projects/marte_debug/Test/UnitTests/main.cpp",
|
||||||
|
"arguments": [
|
||||||
|
"c++",
|
||||||
|
"-DARCHITECTURE=x86_gcc",
|
||||||
|
"-DENVIRONMENT=Linux",
|
||||||
|
"-DMARTe2_TEST_ENVIRONMENT=GTest",
|
||||||
|
"-DUSE_PTHREAD",
|
||||||
|
"-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/L4Configuration",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Events",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Logger",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Messages",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L5FILES",
|
||||||
|
"-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/L4Configuration",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4LoggerService",
|
||||||
|
"-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/EpicsDataSource",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2-components/Source/Components/DataSources/FileDataSource",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2-components/Source/Components/GAMs/IOGAM",
|
||||||
|
"-I/home/martino/Projects/marte_debug/Source",
|
||||||
|
"-I/home/martino/Projects/marte_debug/Headers",
|
||||||
|
"-I/home/martino/Projects/marte_debug/Test/UnitTests/../../Source",
|
||||||
|
"-I/home/martino/Projects/marte_debug/Test/UnitTests/../../Headers",
|
||||||
|
"-pthread",
|
||||||
|
"-MD",
|
||||||
|
"-MT",
|
||||||
|
"Test/UnitTests/CMakeFiles/UnitTests.dir/main.cpp.o",
|
||||||
|
"-MF",
|
||||||
|
"CMakeFiles/UnitTests.dir/main.cpp.o.d",
|
||||||
|
"-o",
|
||||||
|
"CMakeFiles/UnitTests.dir/main.cpp.o",
|
||||||
|
"-c",
|
||||||
|
"/home/martino/Projects/marte_debug/Test/UnitTests/main.cpp"
|
||||||
|
],
|
||||||
|
"directory": "/home/martino/Projects/marte_debug/Build/Test/UnitTests",
|
||||||
|
"output": "CMakeFiles/UnitTests.dir/main.cpp.o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "/home/martino/Projects/marte_debug/Test/Integration/main.cpp",
|
||||||
|
"arguments": [
|
||||||
|
"c++",
|
||||||
|
"-DARCHITECTURE=x86_gcc",
|
||||||
|
"-DENVIRONMENT=Linux",
|
||||||
|
"-DMARTe2_TEST_ENVIRONMENT=GTest",
|
||||||
|
"-DUSE_PTHREAD",
|
||||||
|
"-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/L4Configuration",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Events",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Logger",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L4Messages",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/BareMetal/L5FILES",
|
||||||
|
"-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/L4Configuration",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2/Source/Core/Scheduler/L4LoggerService",
|
||||||
|
"-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/EpicsDataSource",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2-components/Source/Components/DataSources/FileDataSource",
|
||||||
|
"-I/home/martino/Projects/marte_debug/dependency/MARTe2-components/Source/Components/GAMs/IOGAM",
|
||||||
|
"-I/home/martino/Projects/marte_debug/Source",
|
||||||
|
"-I/home/martino/Projects/marte_debug/Headers",
|
||||||
|
"-pthread",
|
||||||
|
"-MD",
|
||||||
|
"-MT",
|
||||||
|
"Test/Integration/CMakeFiles/IntegrationTest.dir/main.cpp.o",
|
||||||
|
"-MF",
|
||||||
|
"CMakeFiles/IntegrationTest.dir/main.cpp.o.d",
|
||||||
|
"-o",
|
||||||
|
"CMakeFiles/IntegrationTest.dir/main.cpp.o",
|
||||||
|
"-c",
|
||||||
|
"/home/martino/Projects/marte_debug/Test/Integration/main.cpp"
|
||||||
|
],
|
||||||
|
"directory": "/home/martino/Projects/marte_debug/Build/Test/Integration",
|
||||||
|
"output": "CMakeFiles/IntegrationTest.dir/main.cpp.o"
|
||||||
|
}
|
||||||
|
]
|
||||||
11
env.sh
Normal file
11
env.sh
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Get the directory of this script
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
|
||||||
|
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
|
||||||
|
echo "MARTe2 Environment Set (MARTe2_DIR=$MARTe2_DIR)"
|
||||||
|
echo "MARTe2 Components Environment Set (MARTe2_Components_DIR=$MARTe2_Components_DIR)"
|
||||||
21
run_test.sh
Executable file
21
run_test.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
||||||
|
source $DIR/env.sh
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
|
||||||
|
export MARTe2_DIR=/home/martino/Projects/marte_debug/dependency/MARTe2
|
||||||
|
export TARGET=x86-linux
|
||||||
|
|
||||||
|
MARTE_APP=$MARTe2_DIR/Build/$TARGET/App/MARTeApp.ex
|
||||||
|
|
||||||
|
# Build paths for all components
|
||||||
|
LIBS=$DIR/Build
|
||||||
|
LIBS=$LIBS:$MARTe2_DIR/Build/$TARGET/Core
|
||||||
|
LIBS=$LIBS:$MARTe2_Components_DIR/Build/$TARGET/Components/DataSources/LinuxTimer
|
||||||
|
LIBS=$LIBS:$MARTe2_Components_DIR/Build/$TARGET/Components/DataSources/LoggerDataSource
|
||||||
|
LIBS=$LIBS:$MARTe2_Components_DIR/Build/$TARGET/Components/GAMs/IOGAM
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH=$LIBS:$LD_LIBRARY_PATH
|
||||||
|
|
||||||
|
# ./Build/Test/Integration/IntegrationTest -f Test/Configurations/debug_test.cfg -l RealTimeLoader -s State1
|
||||||
|
$MARTE_APP -f $DIR/Test/Configurations/debug_test.cfg -l RealTimeLoader -s State1
|
||||||
41
specs.md
Normal file
41
specs.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
Project Specification: MARTe2 Universal Observability & Debugging Suite
|
||||||
|
|
||||||
|
Version: 1.1
|
||||||
|
|
||||||
|
Date: 2023-10-27
|
||||||
|
|
||||||
|
Status: Active / Implemented
|
||||||
|
|
||||||
|
1. Executive Summary
|
||||||
|
|
||||||
|
This project implements a "Zero-Code-Change" observability and debugging layer for the MARTe2 real-time framework. The system allows developers to Trace, Force, and Monitor any signal in a running MARTe2 application without modifying existing source code.
|
||||||
|
|
||||||
|
2. System Architecture
|
||||||
|
|
||||||
|
- The Universal Debug Service (C++ Core): A singleton MARTe2 Object that patches the registry and manages communication.
|
||||||
|
- The Broker Injection Layer (C++ Templates): Templated wrappers that intercept Copy() calls for tracing, forcing, and execution control.
|
||||||
|
- The Remote Analyser (Rust/egui): A high-performance, multi-threaded GUI for visualization and control.
|
||||||
|
|
||||||
|
3. Functional Requirements
|
||||||
|
|
||||||
|
3.1 Execution Control
|
||||||
|
- REQ-25: Execution Control (Pause/Resume): The system SHALL provide a mechanism to pause and resume the execution of all patched real-time threads (via Brokers), allowing for static inspection of the system state.
|
||||||
|
|
||||||
|
3.2 Discovery
|
||||||
|
- REQ-24: Tree Exploration: The GUI client SHALL request the full application tree upon connection and display it in a hierarchical tree view.
|
||||||
|
- TREE Command: Returns a recursive JSON structure representing the entire application tree, including signal metadata (Type, Dimensions, Elements).
|
||||||
|
|
||||||
|
3.3 Multi-Threaded Client (REQ-23)
|
||||||
|
- Port 8080 (TCP): Commands and Metadata.
|
||||||
|
- Port 8082 (TCP): Independent Real-Time Log Stream.
|
||||||
|
- Port 8081 (UDP): High-Speed Telemetry for Oscilloscope.
|
||||||
|
|
||||||
|
4. Communication Protocol
|
||||||
|
|
||||||
|
- LS [Path]: List nodes.
|
||||||
|
- TREE: Full recursive JSON application map.
|
||||||
|
- PAUSE / RESUME: Execution control.
|
||||||
|
- TRACE <Signal> <1/0> [Decimation]: Telemetry control.
|
||||||
|
- FORCE <Signal> <Value>: Persistent signal override.
|
||||||
|
- UNFORCE <Signal>: Remove override.
|
||||||
|
- LOG <Level> <Msg>: Port 8082 streaming format.
|
||||||
Reference in New Issue
Block a user