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