2 Commits

Author SHA1 Message Date
Martino Ferrari
d3077e78ec implemented array support on client and server 2026-03-03 21:58:32 +01:00
Martino Ferrari
a941563749 Improved overall features 2026-03-03 21:41:59 +01:00
14 changed files with 881 additions and 170 deletions

View File

@@ -109,10 +109,15 @@ public:
fprintf(stderr, ">> registering %s.%s [%p]\n", dsPath.Buffer(),
signalName.Buffer(), mmb);
uint8 dims = 0;
uint32 elems = 1;
(void)dataSourceIn.GetSignalNumberOfDimensions(dsIdx, dims);
(void)dataSourceIn.GetSignalNumberOfElements(dsIdx, elems);
// Register canonical name
StreamString dsFullName;
dsFullName.Printf("%s.%s", dsPath.Buffer(), signalName.Buffer());
service->RegisterSignal(addr, type, dsFullName.Buffer());
service->RegisterSignal(addr, type, dsFullName.Buffer(), dims, elems);
// Register alias
if (functionName != NULL_PTR(const char8 *)) {
@@ -140,29 +145,21 @@ public:
if (gamRef.IsValid()) {
StreamString absGamPath;
DebugService::GetFullObjectName(*(gamRef.operator->()), absGamPath);
// Register full path (InputSignals/OutputSignals)
// gamFullName.fPrintf(stderr, "%s.%s.%s", absGamPath.Buffer(),
// dirStr, signalName.Buffer()); signalInfoPointers[i] =
// service->RegisterSignal(addr, type, gamFullName.Buffer()); Also
// register short path (In/Out) for GUI compatibility
// Register short path (In/Out) for GUI compatibility
gamFullName.Printf("%s.%s.%s", absGamPath.Buffer(), dirStrShort,
signalName.Buffer());
signalInfoPointers[i] =
service->RegisterSignal(addr, type, gamFullName.Buffer());
service->RegisterSignal(addr, type, gamFullName.Buffer(), dims, elems);
} else {
// Fallback to short name
// gamFullName.fPrintf(stderr, "%s.%s.%s", functionName, dirStr,
// signalName.Buffer()); signalInfoPointers[i] =
// service->RegisterSignal(addr, type, gamFullName.Buffer()); Also
// register short form
// Fallback to short form
gamFullName.Printf("%s.%s.%s", functionName, dirStrShort,
signalName.Buffer());
signalInfoPointers[i] =
service->RegisterSignal(addr, type, gamFullName.Buffer());
service->RegisterSignal(addr, type, gamFullName.Buffer(), dims, elems);
}
} else {
signalInfoPointers[i] =
service->RegisterSignal(addr, type, dsFullName.Buffer());
service->RegisterSignal(addr, type, dsFullName.Buffer(), dims, elems);
}
}

View File

@@ -12,6 +12,8 @@ struct DebugSignalInfo {
void* memoryAddress;
TypeDescriptor type;
StreamString name;
uint8 numberOfDimensions;
uint32 numberOfElements;
volatile bool isTracing;
volatile bool isForcing;
uint8 forcedValue[1024];

View File

@@ -1,15 +1,18 @@
#include "BasicTCPSocket.h"
#include "ClassRegistryItem.h"
#include "ConfigurationDatabase.h"
#include "DataSourceI.h"
#include "DebugBrokerWrapper.h"
#include "DebugService.h"
#include "GAM.h"
#include "GlobalObjectsDatabase.h"
#include "HighResolutionTimer.h"
#include "ObjectBuilder.h"
#include "ObjectRegistryDatabase.h"
#include "StreamString.h"
#include "TimeoutType.h"
#include "TypeConversion.h"
#include "ReferenceT.h"
namespace MARTe {
@@ -84,6 +87,7 @@ DebugService::DebugService()
threadService(binderServer), streamerService(binderStreamer) {
controlPort = 0;
streamPort = 8081;
logPort = 8082;
streamIP = "127.0.0.1";
isServer = false;
suppressTimeoutLogs = true;
@@ -106,6 +110,7 @@ DebugService::~DebugService() {
for (uint32 i = 0; i < signals.Size(); i++) {
delete signals[i];
}
this->Purge();
}
bool DebugService::Initialise(StructuredDataI &data) {
@@ -132,6 +137,15 @@ bool DebugService::Initialise(StructuredDataI &data) {
(void)data.Read("UdpPort", port);
streamPort = (uint16)port;
}
port = 8082;
if (data.Read("LogPort", port)) {
logPort = (uint16)port;
} else {
(void)data.Read("TcpLogPort", port);
logPort = (uint16)port;
}
StreamString tempIP;
if (data.Read("StreamIP", tempIP)) {
streamIP = tempIP;
@@ -175,6 +189,34 @@ bool DebugService::Initialise(StructuredDataI &data) {
return false;
if (streamerService.Start() != ErrorManagement::NoError)
return false;
if (logPort > 0) {
Reference tcpLogger(
"TcpLogger", GlobalObjectsDatabase::Instance()->GetStandardHeap());
if (tcpLogger.IsValid()) {
ConfigurationDatabase loggerConfig;
loggerConfig.Write("Port", (uint32)logPort);
if (tcpLogger->Initialise(loggerConfig)) {
this->Insert(tcpLogger);
Reference loggerService(
"LoggerService",
GlobalObjectsDatabase::Instance()->GetStandardHeap());
if (loggerService.IsValid()) {
ConfigurationDatabase serviceConfig;
serviceConfig.Write("CPUs", (uint32)1);
ReferenceContainer *lc =
dynamic_cast<ReferenceContainer *>(loggerService.operator->());
if (lc != NULL_PTR(ReferenceContainer *)) {
lc->Insert(tcpLogger);
}
if (loggerService->Initialise(serviceConfig)) {
this->Insert(loggerService);
}
}
}
}
}
}
return true;
}
@@ -230,7 +272,9 @@ void DebugService::PatchRegistry() {
DebugSignalInfo *DebugService::RegisterSignal(void *memoryAddress,
TypeDescriptor type,
const char8 *name) {
const char8 *name,
uint8 numberOfDimensions,
uint32 numberOfElements) {
printf("<debug> registering: %s\n", name);
mutex.FastLock();
DebugSignalInfo *res = NULL_PTR(DebugSignalInfo *);
@@ -248,6 +292,8 @@ DebugSignalInfo *DebugService::RegisterSignal(void *memoryAddress,
res->memoryAddress = memoryAddress;
res->type = type;
res->name = name;
res->numberOfDimensions = numberOfDimensions;
res->numberOfElements = numberOfElements;
res->isTracing = false;
res->isForcing = false;
res->internalID = sigIdx;
@@ -430,6 +476,24 @@ ErrorManagement::ErrorType DebugService::Streamer(ExecutionInfo &info) {
uint32 packetOffset = 0;
uint32 sequenceNumber = 0;
while (info.GetStage() == ExecutionInfo::MainStage) {
// Poll monitored signals
uint64 currentTimeMs = (uint64)((float64)HighResolutionTimer::Counter() *
HighResolutionTimer::Period() * 1000.0);
mutex.FastLock();
for (uint32 i = 0; i < monitoredSignals.Size(); i++) {
if (currentTimeMs >= (monitoredSignals[i].lastPollTime + monitoredSignals[i].periodMs)) {
monitoredSignals[i].lastPollTime = currentTimeMs;
uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() *
HighResolutionTimer::Period() * 1000000.0);
void *address = NULL_PTR(void *);
if (monitoredSignals[i].dataSource->GetSignalMemoryBuffer(monitoredSignals[i].signalIdx, 0, address)) {
traceBuffer.Push(monitoredSignals[i].internalID, ts, (uint8 *)address, monitoredSignals[i].size);
}
}
}
mutex.FastUnLock();
uint32 id, size;
uint64 ts;
uint8 sampleData[1024];
@@ -536,7 +600,47 @@ void DebugService::HandleCommand(StreamString cmd, BasicTCPSocket *client) {
}
} else if (token == "DISCOVER")
Discover(client);
else if (token == "CONFIG")
else if (token == "SERVICE_INFO") {
if (client) {
StreamString resp;
resp.Printf("OK SERVICE_INFO TCP_CTRL:%u UDP_STREAM:%u TCP_LOG:%u STATE:%s\n",
controlPort, streamPort, logPort, isPaused ? "PAUSED" : "RUNNING");
uint32 s = resp.Size();
(void)client->Write(resp.Buffer(), s);
}
} else if (token == "MONITOR") {
StreamString subToken;
if (cmd.GetToken(subToken, delims, term) && subToken == "SIGNAL") {
StreamString name, period;
if (cmd.GetToken(name, delims, term) && cmd.GetToken(period, delims, term)) {
uint32 p = 100;
AnyType pVal(UnsignedInteger32Bit, 0u, &p);
AnyType pStr(CharString, 0u, period.Buffer());
(void)TypeConvert(pVal, pStr);
uint32 count = RegisterMonitorSignal(name.Buffer(), p);
if (client) {
StreamString resp;
resp.Printf("OK MONITOR %u\n", count);
uint32 s = resp.Size();
(void)client->Write(resp.Buffer(), s);
}
}
}
} else if (token == "UNMONITOR") {
StreamString subToken;
if (cmd.GetToken(subToken, delims, term) && subToken == "SIGNAL") {
StreamString name;
if (cmd.GetToken(name, delims, term)) {
uint32 count = UnmonitorSignal(name.Buffer());
if (client) {
StreamString resp;
resp.Printf("OK UNMONITOR %u\n", count);
uint32 s = resp.Size();
(void)client->Write(resp.Buffer(), s);
}
}
}
} else if (token == "CONFIG")
ServeConfig(client);
else if (token == "PAUSE") {
SetPaused(true);
@@ -554,7 +658,7 @@ void DebugService::HandleCommand(StreamString cmd, BasicTCPSocket *client) {
StreamString json;
json = "{\"Name\": \"Root\", \"Class\": \"ObjectRegistryDatabase\", "
"\"Children\": [\n";
(void)ExportTree(ObjectRegistryDatabase::Instance(), json);
(void)ExportTree(ObjectRegistryDatabase::Instance(), json, NULL_PTR(const char8 *));
json += "\n]}\nOK TREE\n";
uint32 s = json.Size();
if (client)
@@ -747,7 +851,7 @@ void DebugService::InfoNode(const char8 *path, BasicTCPSocket *client) {
}
uint32 DebugService::ExportTree(ReferenceContainer *container,
StreamString &json) {
StreamString &json, const char8 *pathPrefix) {
if (container == NULL_PTR(ReferenceContainer *))
return 0;
uint32 size = container->Size();
@@ -761,6 +865,14 @@ uint32 DebugService::ExportTree(ReferenceContainer *container,
const char8 *cname = child->GetName();
if (cname == NULL_PTR(const char8 *))
cname = "unnamed";
StreamString currentPath;
if (pathPrefix != NULL_PTR(const char8 *)) {
currentPath.Printf("%s.%s", pathPrefix, cname);
} else {
currentPath = cname;
}
nodeJson += "{\"Name\": \"";
EscapeJson(cname, nodeJson);
nodeJson += "\", \"Class\": \"";
@@ -775,7 +887,7 @@ uint32 DebugService::ExportTree(ReferenceContainer *container,
nodeJson += ", \"Children\": [\n";
uint32 subCount = 0u;
if (inner != NULL_PTR(ReferenceContainer *))
subCount += ExportTree(inner, nodeJson);
subCount += ExportTree(inner, nodeJson, currentPath.Buffer());
if (ds != NULL_PTR(DataSourceI *)) {
uint32 nSignals = ds->GetNumberOfSignals();
for (uint32 j = 0u; j < nSignals; j++) {
@@ -790,12 +902,22 @@ uint32 DebugService::ExportTree(ReferenceContainer *container,
(void)ds->GetSignalNumberOfDimensions(j, dims);
uint32 elems = 0u;
(void)ds->GetSignalNumberOfElements(j, elems);
StreamString signalFullPath;
signalFullPath.Printf("%s.%s", currentPath.Buffer(), sname.Buffer());
bool traceable = false;
bool forcable = false;
(void)IsInstrumented(signalFullPath.Buffer(), traceable, forcable);
nodeJson += "{\"Name\": \"";
EscapeJson(sname.Buffer(), nodeJson);
nodeJson += "\", \"Class\": \"Signal\", \"Type\": \"";
EscapeJson(stype ? stype : "Unknown", nodeJson);
nodeJson.Printf("\", \"Dimensions\": %d, \"Elements\": %u}", dims,
nodeJson.Printf("\", \"Dimensions\": %d, \"Elements\": %u", dims,
elems);
nodeJson.Printf(", \"IsTraceable\": %s, \"IsForcable\": %s}",
traceable ? "true" : "false",
forcable ? "true" : "false");
}
}
if (gam != NULL_PTR(GAM *)) {
@@ -812,12 +934,23 @@ uint32 DebugService::ExportTree(ReferenceContainer *container,
(void)gam->GetSignalNumberOfDimensions(InputSignals, j, dims);
uint32 elems = 0u;
(void)gam->GetSignalNumberOfElements(InputSignals, j, elems);
StreamString signalFullPath;
signalFullPath.Printf("%s.In.%s", currentPath.Buffer(),
sname.Buffer());
bool traceable = false;
bool forcable = false;
(void)IsInstrumented(signalFullPath.Buffer(), traceable, forcable);
nodeJson += "{\"Name\": \"In.";
EscapeJson(sname.Buffer(), nodeJson);
nodeJson += "\", \"Class\": \"InputSignal\", \"Type\": \"";
EscapeJson(stype ? stype : "Unknown", nodeJson);
nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u}", dims,
nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u", dims,
elems);
nodeJson.Printf(", \"IsTraceable\": %s, \"IsForcable\": %s}",
traceable ? "true" : "false",
forcable ? "true" : "false");
}
uint32 nOut = gam->GetNumberOfOutputSignals();
for (uint32 j = 0u; j < nOut; j++) {
@@ -832,12 +965,23 @@ uint32 DebugService::ExportTree(ReferenceContainer *container,
(void)gam->GetSignalNumberOfDimensions(OutputSignals, j, dims);
uint32 elems = 0u;
(void)gam->GetSignalNumberOfElements(OutputSignals, j, elems);
StreamString signalFullPath;
signalFullPath.Printf("%s.Out.%s", currentPath.Buffer(),
sname.Buffer());
bool traceable = false;
bool forcable = false;
(void)IsInstrumented(signalFullPath.Buffer(), traceable, forcable);
nodeJson += "{\"Name\": \"Out.";
EscapeJson(sname.Buffer(), nodeJson);
nodeJson += "\", \"Class\": \"OutputSignal\", \"Type\": \"";
EscapeJson(stype ? stype : "Unknown", nodeJson);
nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u}", dims,
nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u", dims,
elems);
nodeJson.Printf(", \"IsTraceable\": %s, \"IsForcable\": %s}",
traceable ? "true" : "false",
forcable ? "true" : "false");
}
}
nodeJson += "\n]";
@@ -908,27 +1052,169 @@ uint32 DebugService::TraceSignal(const char8 *name, bool enable,
return count;
}
bool DebugService::IsInstrumented(const char8 *fullPath, bool &traceable,
bool &forcable) {
mutex.FastLock();
bool found = false;
for (uint32 i = 0; i < aliases.Size(); i++) {
if (aliases[i].name == fullPath ||
SuffixMatch(aliases[i].name.Buffer(), fullPath)) {
found = true;
break;
}
}
mutex.FastUnLock();
traceable = found;
forcable = found;
return found;
}
uint32 DebugService::RegisterMonitorSignal(const char8 *path, uint32 periodMs) {
mutex.FastLock();
uint32 count = 0;
// Check if already monitored
for (uint32 j = 0; j < monitoredSignals.Size(); j++) {
if (monitoredSignals[j].path == path) {
monitoredSignals[j].periodMs = periodMs;
mutex.FastUnLock();
return 1;
}
}
// Path resolution: find the DataSource object
StreamString fullPath = path;
fullPath.Seek(0);
char8 term;
Vec<StreamString> parts;
StreamString token;
while (fullPath.GetToken(token, ".", term)) {
parts.Push(token);
token = "";
}
if (parts.Size() >= 2) {
StreamString signalName = parts[parts.Size() - 1u];
StreamString dsPath;
for (uint32 i = 0; i < parts.Size() - 1u; i++) {
dsPath += parts[i];
if (i < parts.Size() - 2u)
dsPath += ".";
}
ReferenceT<DataSourceI> ds =
ObjectRegistryDatabase::Instance()->Find(dsPath.Buffer());
if (ds.IsValid()) {
uint32 idx = 0;
if (ds->GetSignalIndex(idx, signalName.Buffer())) {
MonitoredSignal m;
m.dataSource = ds;
m.signalIdx = idx;
m.path = path;
m.periodMs = periodMs;
m.lastPollTime = 0;
m.size = 0;
(void)ds->GetSignalByteSize(idx, m.size);
if (m.size == 0)
m.size = 4;
// Use high-bit for polled signals to avoid conflict with brokered ones
m.internalID = 0x80000000 | monitoredSignals.Size();
// Re-use existing ID if signal is also instrumented via broker
for (uint32 i = 0; i < aliases.Size(); i++) {
if (aliases[i].name == path ||
SuffixMatch(aliases[i].name.Buffer(), path)) {
m.internalID = signals[aliases[i].signalIndex]->internalID;
break;
}
}
monitoredSignals.Push(m);
count = 1;
}
}
}
mutex.FastUnLock();
return count;
}
uint32 DebugService::UnmonitorSignal(const char8 *path) {
mutex.FastLock();
uint32 count = 0;
for (uint32 i = 0; i < monitoredSignals.Size(); i++) {
if (monitoredSignals[i].path == path ||
SuffixMatch(monitoredSignals[i].path.Buffer(), path)) {
(void)monitoredSignals.Remove(i);
i--;
count++;
}
}
mutex.FastUnLock();
return count;
}
void DebugService::Discover(BasicTCPSocket *client) {
if (client) {
StreamString header = "{\n \"Signals\": [\n";
uint32 s = header.Size();
(void)client->Write(header.Buffer(), s);
mutex.FastLock();
uint32 total = 0;
for (uint32 i = 0; i < aliases.Size(); i++) {
if (total > 0) {
uint32 commaSize = 2;
(void)client->Write(",\n", commaSize);
}
StreamString line;
DebugSignalInfo *sig = signals[aliases[i].signalIndex];
const char8 *typeName =
TypeDescriptor::GetTypeNameFromTypeDescriptor(sig->type);
line.Printf(" {\"name\": \"%s\", \"id\": %d, \"type\": \"%s\"",
line.Printf(" {\"name\": \"%s\", \"id\": %d, \"type\": \"%s\", \"dimensions\": %u, \"elements\": %u",
aliases[i].name.Buffer(), sig->internalID,
typeName ? typeName : "Unknown");
typeName ? typeName : "Unknown", sig->numberOfDimensions, sig->numberOfElements);
EnrichWithConfig(aliases[i].name.Buffer(), line);
line += "}";
if (i < aliases.Size() - 1)
line += ",";
line += "\n";
s = line.Size();
(void)client->Write(line.Buffer(), s);
total++;
}
// Export monitored signals not already in aliases
for (uint32 i = 0; i < monitoredSignals.Size(); i++) {
bool found = false;
for (uint32 j = 0; j < aliases.Size(); j++) {
if (aliases[j].name == monitoredSignals[i].path) {
found = true;
break;
}
}
if (!found) {
if (total > 0) {
uint32 commaSize = 2;
(void)client->Write(",\n", commaSize);
}
StreamString line;
const char8 *typeName = TypeDescriptor::GetTypeNameFromTypeDescriptor(
monitoredSignals[i].dataSource->GetSignalType(
monitoredSignals[i].signalIdx));
uint8 dims = 0;
uint32 elems = 1;
(void)monitoredSignals[i].dataSource->GetSignalNumberOfDimensions(monitoredSignals[i].signalIdx, dims);
(void)monitoredSignals[i].dataSource->GetSignalNumberOfElements(monitoredSignals[i].signalIdx, elems);
line.Printf(" {\"name\": \"%s\", \"id\": %u, \"type\": \"%s\", \"dimensions\": %u, \"elements\": %u",
monitoredSignals[i].path.Buffer(),
monitoredSignals[i].internalID,
typeName ? typeName : "Unknown", dims, elems);
EnrichWithConfig(monitoredSignals[i].path.Buffer(), line);
line += "}";
s = line.Size();
(void)client->Write(line.Buffer(), s);
total++;
}
}
mutex.FastUnLock();
StreamString footer = " ]\n}\nOK DISCOVER\n";

View File

@@ -16,6 +16,7 @@
namespace MARTe {
class MemoryMapBroker;
class DataSourceI;
struct SignalAlias {
StreamString name;
@@ -45,7 +46,8 @@ public:
virtual bool Initialise(StructuredDataI &data);
DebugSignalInfo *RegisterSignal(void *memoryAddress, TypeDescriptor type,
const char8 *name);
const char8 *name, uint8 numberOfDimensions = 0,
uint32 numberOfElements = 1);
void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size,
uint64 timestamp);
@@ -64,17 +66,31 @@ public:
uint32 ForceSignal(const char8 *name, const char8 *valueStr);
uint32 UnforceSignal(const char8 *name);
uint32 TraceSignal(const char8 *name, bool enable, uint32 decimation = 1);
bool IsInstrumented(const char8 *fullPath, bool &traceable, bool &forcable);
void Discover(BasicTCPSocket *client);
void InfoNode(const char8 *path, BasicTCPSocket *client);
void ListNodes(const char8 *path, BasicTCPSocket *client);
void ServeConfig(BasicTCPSocket *client);
void SetFullConfig(ConfigurationDatabase &config);
struct MonitoredSignal {
ReferenceT<DataSourceI> dataSource;
uint32 signalIdx;
uint32 internalID;
uint32 periodMs;
uint64 lastPollTime;
uint32 size;
StreamString path;
};
uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs);
uint32 UnmonitorSignal(const char8 *path);
private:
void HandleCommand(StreamString cmd, BasicTCPSocket *client);
void UpdateBrokersActiveStatus();
uint32 ExportTree(ReferenceContainer *container, StreamString &json);
uint32 ExportTree(ReferenceContainer *container, StreamString &json, const char8 *pathPrefix);
void PatchRegistry();
void EnrichWithConfig(const char8 *path, StreamString &json);
@@ -85,6 +101,7 @@ private:
uint16 controlPort;
uint16 streamPort;
uint16 logPort;
StreamString streamIP;
bool isServer;
bool suppressTimeoutLogs;
@@ -123,6 +140,7 @@ private:
Vec<DebugSignalInfo *> signals;
Vec<SignalAlias> aliases;
Vec<BrokerInfo> brokers;
Vec<MonitoredSignal> monitoredSignals;
FastPollingMutexSem mutex;
TraceRingBuffer traceBuffer;

View File

@@ -33,6 +33,7 @@ include $(MAKEDEFAULTDIR)/MakeStdLibDefs.$(TARGET)
INCLUDES += -I$(ROOT_DIR)/Source/Core/Types/Result
INCLUDES += -I$(ROOT_DIR)/Source/Core/Types/Vec
INCLUDES += -I$(ROOT_DIR)/Source/Components/Interfaces/TCPLogger
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L0Types
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L1Portability
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L2Objects

View File

@@ -17,7 +17,7 @@
}
OutputSignals = {
Counter = {
DataSource = DDB
DataSource = SyncDB
Type = uint32
}
Time = {
@@ -48,10 +48,38 @@
}
}
}
+GAM3 = {
Class = IOGAM
InputSignals = {
Counter = {
Frequency = 1
Samples = 100
Type = uint32
DataSource = SyncDB
}
}
OutputSignals = {
Counter = {
DataSource = DDB3
NumberOfElements = 100
Type = uint32
}
}
}
}
+Data = {
Class = ReferenceContainer
DefaultDataSource = DDB
+SyncDB = {
Class = RealTimeThreadSynchronisation
Timeout = 200
Signals = {
Counter = {
Type = uint32
}
}
}
+Timer = {
Class = LinuxTimer
Signals = {
@@ -94,6 +122,10 @@
}
}
}
+DDB3 = {
AllowNoProducer = 1
Class = GAMDataSource
}
+DAMS = {
Class = TimingDataSource
}
@@ -112,6 +144,10 @@
Class = RealTimeThread
Functions = {GAM2}
}
+Thread3 = {
Class = RealTimeThread
Functions = {GAM3}
}
}
}
}
@@ -125,14 +161,6 @@
Class = DebugService
ControlPort = 8080
UdpPort = 8081
LogPort = 8082
StreamIP = "127.0.0.1"
}
+LoggerService = {
Class = LoggerService
CPUs = 0x1
+DebugConsumer = {
Class = TcpLogger
Port = 8082
}
}

View File

@@ -7,8 +7,7 @@
#include "BasicUDPSocket.h"
#include "RealTimeApplication.h"
#include "StandardParser.h"
#include "StreamString.h"
#include "GlobalObjectsDatabase.h"
#include "TestCommon.h"
#include <stdio.h>
using namespace MARTe;
@@ -31,6 +30,7 @@ void TestFullTracePipeline();
void RunValidationTest();
void TestConfigCommands();
void TestGAMSignalTracing();
void TestTreeCommand();
int main() {
signal(SIGALRM, timeout_handler);
@@ -83,9 +83,9 @@ int main() {
// TestConfigCommands(); // Skipping for now
Sleep::MSec(1000);
// printf("\n--- Test 6: GAM Signal Tracing ---\n");
// TestGAMSignalTracing();
// Sleep::MSec(1000);
printf("\n--- Test 6: TREE Command Enhancement ---\n");
TestTreeCommand();
Sleep::MSec(1000);
printf("\nAll Integration Tests Finished.\n");
@@ -94,72 +94,6 @@ int main() {
// --- Test Implementation ---
const char8 * const debug_test_config =
"DebugService = {"
" Class = DebugService "
" ControlPort = 8095 "
" UdpPort = 8096 "
" StreamIP = \"127.0.0.1\" "
"}"
"App = {"
" Class = RealTimeApplication "
" +Functions = {"
" Class = ReferenceContainer "
" +GAM1 = {"
" Class = IOGAM "
" InputSignals = {"
" Counter = { DataSource = Timer Type = uint32 Frequency = 1000 }"
" }"
" OutputSignals = {"
" Counter = { DataSource = DDB Type = uint32 }"
" }"
" }"
" +GAM2 = {"
" Class = IOGAM "
" InputSignals = {"
" Counter = { DataSource = TimerSlow Type = uint32 Frequency = 10 }"
" }"
" OutputSignals = {"
" Counter = { DataSource = Logger Type = uint32 }"
" }"
" }"
" }"
" +Data = {"
" Class = ReferenceContainer "
" DefaultDataSource = DDB "
" +Timer = { Class = LinuxTimer SleepTime = 1000 Signals = { Counter = { Type = uint32 } } }"
" +TimerSlow = { Class = LinuxTimer SleepTime = 100000 Signals = { Counter = { Type = uint32 } } }"
" +Logger = { Class = LoggerDataSource 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 GAM2} } } }"
" }"
" +Scheduler = { Class = GAMScheduler TimingDataSource = DAMS }"
"}";
bool SendCommandGAM(uint16 port, const char8* cmd, StreamString &reply) {
BasicTCPSocket client;
if (!client.Open()) return false;
if (!client.Connect("127.0.0.1", port)) return false;
uint32 s = StringHelper::Length(cmd);
if (!client.Write(cmd, s)) return false;
char buffer[4096];
uint32 size = 4096;
TimeoutType timeout(2000);
if (client.Read(buffer, size, timeout)) {
reply.Write(buffer, size);
client.Close();
return true;
}
client.Close();
return false;
}
void TestGAMSignalTracing() {
printf("--- Test: GAM Signal Tracing Issue ---\n");

View File

@@ -1,4 +1,4 @@
OBJSX = SchedulerTest.x TraceTest.x ValidationTest.x ConfigCommandTest.x
OBJSX = SchedulerTest.x TraceTest.x ValidationTest.x ConfigCommandTest.x TreeCommandTest.x TestCommon.x
PACKAGE = Test/Integration
@@ -29,6 +29,8 @@ INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L4LoggerService
INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L5GAMs
INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L1Portability
INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L3Streams
INCLUDES += -I$(MARTe2_Components_DIR)/Source/Components/GAMs/IOGAM
INCLUDES += -I$(MARTe2_Components_DIR)/Source/Components/DataSources/LinuxTimer
LIBRARIES += -L$(MARTe2_DIR)/Build/$(TARGET)/Core -lMARTe2
LIBRARIES += -L$(MARTe2_Components_DIR)/Build/$(TARGET)/Components/DataSources/LinuxTimer -lLinuxTimer

View File

@@ -0,0 +1,74 @@
#include "TestCommon.h"
#include "BasicTCPSocket.h"
#include "StringHelper.h"
#include "TimeoutType.h"
namespace MARTe {
const char8 * const debug_test_config =
"DebugService = {"
" Class = DebugService "
" ControlPort = 8095 "
" UdpPort = 8096 "
" StreamIP = \"127.0.0.1\" "
"}"
"App = {"
" Class = RealTimeApplication "
" +Functions = {"
" Class = ReferenceContainer "
" +GAM1 = {"
" Class = IOGAM "
" InputSignals = {"
" Counter = { DataSource = Timer Type = uint32 Frequency = 1000 }"
" }"
" OutputSignals = {"
" Counter = { DataSource = DDB Type = uint32 }"
" }"
" }"
" +GAM2 = {"
" Class = IOGAM "
" InputSignals = {"
" Counter = { DataSource = TimerSlow Type = uint32 Frequency = 10 }"
" }"
" OutputSignals = {"
" Counter = { DataSource = Logger Type = uint32 }"
" }"
" }"
" }"
" +Data = {"
" Class = ReferenceContainer "
" DefaultDataSource = DDB "
" +Timer = { Class = LinuxTimer SleepTime = 1000 Signals = { Counter = { Type = uint32 } } }"
" +TimerSlow = { Class = LinuxTimer SleepTime = 100000 Signals = { Counter = { Type = uint32 } } }"
" +Logger = { Class = LoggerDataSource 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 GAM2} } } }"
" }"
" +Scheduler = { Class = GAMScheduler TimingDataSource = DAMS }"
"}";
bool SendCommandGAM(uint16 port, const char8* cmd, StreamString &reply) {
BasicTCPSocket client;
if (!client.Open()) return false;
if (!client.Connect("127.0.0.1", port)) return false;
uint32 s = StringHelper::Length(cmd);
if (!client.Write(cmd, s)) return false;
char buffer[16384];
uint32 size = 16384;
TimeoutType timeout(5000);
if (client.Read(buffer, size, timeout)) {
reply.Write(buffer, size);
client.Close();
return true;
}
client.Close();
return false;
}
}

View File

@@ -0,0 +1,12 @@
#ifndef TESTCOMMON_H
#define TESTCOMMON_H
#include "CompilerTypes.h"
#include "StreamString.h"
namespace MARTe {
extern const char8 * const debug_test_config;
bool SendCommandGAM(uint16 port, const char8* cmd, StreamString &reply);
}
#endif

View File

@@ -27,7 +27,7 @@ void TestFullTracePipeline() {
// 2. Register a mock signal
uint32 mockValue = 0;
DebugSignalInfo* sig = service.RegisterSignal(&mockValue, UnsignedInteger32Bit, "TraceTest.Signal");
DebugSignalInfo* sig = service.RegisterSignal(&mockValue, UnsignedInteger32Bit, "TraceTest.Signal", 0, 1);
assert(sig != NULL_PTR(DebugSignalInfo*));
printf("Signal registered with ID: %u\n", sig->internalID);

View File

@@ -0,0 +1,176 @@
#include "BasicTCPSocket.h"
#include "ConfigurationDatabase.h"
#include "DebugService.h"
#include "GlobalObjectsDatabase.h"
#include "ObjectRegistryDatabase.h"
#include "RealTimeApplication.h"
#include "StandardParser.h"
#include "StreamString.h"
#include "TestCommon.h"
#include "IOGAM.h"
#include "LinuxTimer.h"
#include "GAMDataSource.h"
#include "TimingDataSource.h"
#include "GAMScheduler.h"
#include <stdio.h>
using namespace MARTe;
void TestTreeCommand() {
printf("--- Test: TREE Command Enhancement ---\n");
ObjectRegistryDatabase::Instance()->Purge();
Sleep::MSec(2000); // Wait for sockets from previous tests to clear
ConfigurationDatabase cdb;
// Use unique ports to avoid conflict with other tests
const char8 * const tree_test_config =
"DebugService = {"
" Class = DebugService "
" ControlPort = 8110 "
" UdpPort = 8111 "
" StreamIP = \"127.0.0.1\" "
"}"
"App = {"
" Class = RealTimeApplication "
" +Functions = {"
" Class = ReferenceContainer "
" +GAM1 = {"
" Class = IOGAM "
" InputSignals = {"
" Counter = { DataSource = Timer Type = uint32 Frequency = 1000 }"
" }"
" OutputSignals = {"
" Counter = { DataSource = DDB Type = uint32 }"
" }"
" }"
" }"
" +Data = {"
" Class = ReferenceContainer "
" DefaultDataSource = DDB "
" +Timer = { Class = LinuxTimer SleepTime = 1000 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 }"
"}";
StreamString ss = tree_test_config;
ss.Seek(0);
StandardParser parser(ss, cdb);
if (!parser.Parse()) {
printf("ERROR: Failed to parse config\n");
return;
}
cdb.MoveToRoot();
uint32 n = cdb.GetNumberOfChildren();
for (uint32 i = 0; i < n; i++) {
const char8 *name = cdb.GetChildName(i);
ConfigurationDatabase child;
cdb.MoveRelative(name);
cdb.Copy(child);
cdb.MoveToAncestor(1u);
StreamString className;
child.Read("Class", className);
Reference ref(className.Buffer(),
GlobalObjectsDatabase::Instance()->GetStandardHeap());
if (!ref.IsValid()) {
printf("ERROR: Could not create object %s of class %s\n", name,
className.Buffer());
continue;
}
ref->SetName(name);
if (!ref->Initialise(child)) {
printf("ERROR: Failed to initialise object %s\n", name);
continue;
}
ObjectRegistryDatabase::Instance()->Insert(ref);
}
ReferenceT<DebugService> service =
ObjectRegistryDatabase::Instance()->Find("DebugService");
if (!service.IsValid()) {
printf("ERROR: DebugService not found\n");
return;
}
service->SetFullConfig(cdb);
ReferenceT<RealTimeApplication> app =
ObjectRegistryDatabase::Instance()->Find("App");
if (!app.IsValid()) {
printf("ERROR: App not found\n");
return;
}
if (!app->ConfigureApplication()) {
printf("ERROR: ConfigureApplication failed.\n");
return;
}
if (app->PrepareNextState("State1") != ErrorManagement::NoError) {
printf("ERROR: PrepareNextState failed.\n");
return;
}
if (app->StartNextStateExecution() != ErrorManagement::NoError) {
printf("ERROR: StartNextStateExecution failed.\n");
return;
}
printf("Application started.\n");
Sleep::MSec(1000);
// Step 1: Request TREE
StreamString reply;
if (SendCommandGAM(8110, "TREE\n", reply)) {
printf("TREE response received (len=%llu)\n", reply.Size());
// ...
}
// Step 2: SERVICE_INFO
printf("\n--- Step 2: SERVICE_INFO ---\n");
reply = "";
if (SendCommandGAM(8110, "SERVICE_INFO\n", reply)) {
printf("SERVICE_INFO response: %s", reply.Buffer());
if (StringHelper::SearchString(reply.Buffer(), "TCP_CTRL:8110") != NULL_PTR(const char8 *) &&
StringHelper::SearchString(reply.Buffer(), "UDP_STREAM:8111") != NULL_PTR(const char8 *)) {
printf("SUCCESS: SERVICE_INFO returned correct ports.\n");
} else {
printf("FAILURE: SERVICE_INFO returned incorrect data.\n");
}
}
// Step 3: MONITOR
printf("\n--- Step 3: MONITOR SIGNAL ---\n");
reply = "";
if (SendCommandGAM(8110, "MONITOR SIGNAL App.Data.Timer.Counter 10\n", reply)) {
printf("MONITOR response: %s", reply.Buffer());
if (StringHelper::SearchString(reply.Buffer(), "OK MONITOR 1") != NULL_PTR(const char8 *)) {
printf("SUCCESS: Signal monitored.\n");
} else {
printf("FAILURE: Could not monitor signal.\n");
}
}
// Step 4: UNMONITOR
printf("\n--- Step 4: UNMONITOR SIGNAL ---\n");
reply = "";
if (SendCommandGAM(8110, "UNMONITOR SIGNAL App.Data.Timer.Counter\n", reply)) {
printf("UNMONITOR response: %s", reply.Buffer());
if (StringHelper::SearchString(reply.Buffer(), "OK UNMONITOR 1") != NULL_PTR(const char8 *)) {
printf("SUCCESS: Signal unmonitored.\n");
} else {
printf("FAILURE: Could not unmonitor signal.\n");
}
}
app->StopCurrentStateExecution();
ObjectRegistryDatabase::Instance()->Purge();
}

View File

@@ -38,7 +38,7 @@ public:
// 1. Signal logic
uint32 val = 0;
service.RegisterSignal(&val, UnsignedInteger32Bit, "X.Y.Z");
service.RegisterSignal(&val, UnsignedInteger32Bit, "X.Y.Z", 0, 1);
assert(service.TraceSignal("Z", true) == 1);
assert(service.ForceSignal("Z", "123") == 1);

View File

@@ -29,8 +29,14 @@ struct Signal {
id: u32,
#[serde(rename = "type")]
sig_type: String,
#[serde(default)]
dimensions: u8,
#[serde(default = "default_elements")]
elements: u32,
}
fn default_elements() -> u32 { 1 }
#[derive(Deserialize)]
struct DiscoverResponse {
#[serde(rename = "Signals")]
@@ -51,6 +57,10 @@ struct TreeItem {
dimensions: Option<u8>,
#[serde(rename = "Elements")]
elements: Option<u32>,
#[serde(rename = "IsTraceable")]
is_traceable: Option<bool>,
#[serde(rename = "IsForcable")]
is_forcable: Option<bool>,
}
#[derive(Clone)]
@@ -65,11 +75,14 @@ struct TraceData {
last_value: f64,
recording_tx: Option<Sender<[f64; 2]>>,
recording_path: Option<String>,
is_monitored: bool,
}
struct SignalMetadata {
names: Vec<String>,
sig_type: String,
dimensions: u8,
elements: u32,
}
#[derive(Clone)]
@@ -151,13 +164,14 @@ enum InternalEvent {
Connected,
Disconnected,
InternalLog(String),
TraceRequested(String),
TraceRequested(String, bool), // Name, IsMonitored
ClearTrace(String),
UdpStats(u64),
UdpDropped(u32),
RecordPathChosen(String, String), // SignalName, FilePath
RecordingError(String, String), // SignalName, ErrorMessage
TelemMatched(u32), // Signal ID
ServiceConfig { udp_port: String, log_port: String },
}
// --- App State ---
@@ -167,6 +181,11 @@ struct ForcingDialog {
value: String,
}
struct MonitorDialog {
signal_path: String,
period_ms: String,
}
struct LogFilters {
show_debug: bool,
show_info: bool,
@@ -212,6 +231,7 @@ struct MarteDebugApp {
udp_dropped: u64,
telem_match_count: HashMap<u32, u64>,
forcing_dialog: Option<ForcingDialog>,
monitoring_dialog: Option<MonitorDialog>,
style_editor: Option<(usize, usize)>,
tx_cmd: Sender<String>,
rx_events: Receiver<InternalEvent>,
@@ -291,6 +311,7 @@ impl MarteDebugApp {
udp_dropped: 0,
telem_match_count: HashMap::new(),
forcing_dialog: None,
monitoring_dialog: None,
style_editor: None,
tx_cmd,
rx_events,
@@ -430,17 +451,56 @@ impl MarteDebugApp {
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(),
value: "".to_string(),
let elements = item.elements.unwrap_or(1);
if elements > 1 {
let header = egui::CollapsingHeader::new(format!("{} [{}] ({} elems)", label, item.class, elements))
.id_salt(&current_path);
header.show(ui, |ui| {
for i in 0..elements {
let elem_path = format!("{}[{}]", current_path, i);
ui.horizontal(|ui| {
ui.label(format!("{}[{}]", item.name, i));
if ui.button("Trace").clicked() {
let _ = self.tx_cmd.send(format!("TRACE {} 1", current_path));
let _ = self.internal_tx.send(InternalEvent::TraceRequested(elem_path.clone(), false));
}
if item.class == "Signal" {
if ui.button("Monitor").clicked() {
self.monitoring_dialog = Some(MonitorDialog {
signal_path: current_path.clone(),
period_ms: "100".to_string(),
});
// Note: internal monitoring logic will handle individual elements via naming convention
let _ = self.internal_tx.send(InternalEvent::TraceRequested(elem_path.clone(), true));
}
}
});
}
});
} else {
let traceable = item.is_traceable.unwrap_or(false);
let forcable = item.is_forcable.unwrap_or(false);
if traceable && ui.button("Trace").clicked() {
let _ = self.tx_cmd.send(format!("TRACE {} 1", current_path));
let _ = self
.internal_tx
.send(InternalEvent::TraceRequested(current_path.clone(), false));
}
if item.class == "Signal" {
if ui.button("Monitor").clicked() {
self.monitoring_dialog = Some(MonitorDialog {
signal_path: current_path.clone(),
period_ms: "100".to_string(),
});
}
}
if forcable && ui.button("⚡ Force").clicked() {
self.forcing_dialog = Some(ForcingDialog {
signal_path: current_path.clone(),
value: "".to_string(),
});
}
}
}
});
@@ -535,6 +595,26 @@ fn tcp_command_worker(
json_acc.clear();
}
} else {
if trimmed.starts_with("OK SERVICE_INFO") {
// OK SERVICE_INFO TCP_CTRL:8110 UDP_STREAM:8111 TCP_LOG:8082 STATE:RUNNING
let parts: Vec<&str> = trimmed.split_whitespace().collect();
let mut udp = String::new();
let mut log = String::new();
for p in parts {
if p.starts_with("UDP_STREAM:") {
udp = p.split(':').nth(1).unwrap_or("").to_string();
}
if p.starts_with("TCP_LOG:") {
log = p.split(':').nth(1).unwrap_or("").to_string();
}
}
if !udp.is_empty() || !log.is_empty() {
let _ = tx_events_inner.send(InternalEvent::ServiceConfig {
udp_port: udp,
log_port: log,
});
}
}
let _ = tx_events_inner
.send(InternalEvent::CommandResponse(trimmed.to_string()));
}
@@ -797,50 +877,64 @@ fn udp_worker(
if let Some(meta) = metas.get(&id) {
let _ = tx_events.send(InternalEvent::TelemMatched(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
let type_size = if meta.elements > 0 { size / meta.elements } else { size };
for i in 0..meta.elements {
let elem_offset = (i * type_size) as usize;
if elem_offset + type_size as usize > data_slice.len() { break; }
let elem_data = &data_slice[elem_offset..elem_offset + type_size as usize];
let val = match type_size {
1 => {
if t.contains('u') {
elem_data[0] as f64
} else {
(elem_data[0] as i8) as f64
}
}
}
2 => {
let b = data_slice[0..2].try_into().unwrap();
if t.contains('u') {
u16::from_le_bytes(b) as f64
} else {
i16::from_le_bytes(b) as f64
2 => {
let b = elem_data[0..2].try_into().unwrap();
if t.contains('u') {
u16::from_le_bytes(b) as f64
} else {
i16::from_le_bytes(b) as f64
}
}
}
4 => {
let b = data_slice[0..4].try_into().unwrap();
if t.contains("float") {
f32::from_le_bytes(b) as f64
} else if t.contains('u') {
u32::from_le_bytes(b) as f64
} else {
i32::from_le_bytes(b) as f64
4 => {
let b = elem_data[0..4].try_into().unwrap();
if t.contains("float") {
f32::from_le_bytes(b) as f64
} else if t.contains('u') {
u32::from_le_bytes(b) as f64
} else {
i32::from_le_bytes(b) as f64
}
}
}
8 => {
let b = data_slice[0..8].try_into().unwrap();
if t.contains("float") {
f64::from_le_bytes(b)
} else if t.contains('u') {
u64::from_le_bytes(b) as f64
} else {
i64::from_le_bytes(b) as f64
8 => {
let b = elem_data[0..8].try_into().unwrap();
if t.contains("float") {
f64::from_le_bytes(b)
} else if t.contains('u') {
u64::from_le_bytes(b) as f64
} else {
i64::from_le_bytes(b) as f64
}
}
_ => 0.0,
};
for name in &meta.names {
let target_name = if meta.elements > 1 {
format!("{}[{}]", name, i)
} else {
name.clone()
};
local_updates
.entry(target_name.clone())
.or_default()
.push([ts_s, val]);
last_values.insert(target_name, val);
}
_ => 0.0,
};
for name in &meta.names {
local_updates
.entry(name.clone())
.or_default()
.push([ts_s, val]);
last_values.insert(name.clone(), val);
}
}
offset += size as usize;
@@ -889,6 +983,8 @@ impl eframe::App for MarteDebugApp {
let meta = metas.entry(s.id).or_insert_with(|| SignalMetadata {
names: Vec::new(),
sig_type: s.sig_type.clone(),
dimensions: s.dimensions,
elements: s.elements,
});
if !meta.names.contains(&s.name) {
meta.names.push(s.name.clone());
@@ -906,14 +1002,16 @@ impl eframe::App for MarteDebugApp {
InternalEvent::NodeInfo(info) => {
self.node_info = info;
}
InternalEvent::TraceRequested(name) => {
InternalEvent::TraceRequested(name, is_monitored) => {
let mut data_map = self.traced_signals.lock().unwrap();
data_map.entry(name.clone()).or_insert_with(|| TraceData {
let entry = data_map.entry(name.clone()).or_insert_with(|| TraceData {
values: VecDeque::with_capacity(10000),
last_value: 0.0,
recording_tx: None,
recording_path: None,
is_monitored,
});
entry.is_monitored = is_monitored;
self.logs.push_back(LogEntry {
time: Local::now().format("%H:%M:%S").to_string(),
level: "GUI_INFO".to_string(),
@@ -937,11 +1035,33 @@ impl eframe::App for MarteDebugApp {
self.connected = true;
// Wait for connection to stabilize before sending commands
std::thread::sleep(std::time::Duration::from_millis(200));
let _ = self.tx_cmd.send("SERVICE_INFO".to_string());
std::thread::sleep(std::time::Duration::from_millis(100));
let _ = self.tx_cmd.send("TREE".to_string());
// Wait for TREE response before sending next command
std::thread::sleep(std::time::Duration::from_millis(500));
let _ = self.tx_cmd.send("DISCOVER".to_string());
}
InternalEvent::ServiceConfig { udp_port, log_port } => {
let mut changed = false;
if !udp_port.is_empty() && self.config.udp_port != udp_port {
self.config.udp_port = udp_port;
changed = true;
}
if !log_port.is_empty() && self.config.log_port != log_port {
self.config.log_port = log_port;
changed = true;
}
if changed {
self.config.version += 1;
*self.shared_config.lock().unwrap() = self.config.clone();
self.logs.push_back(LogEntry {
time: Local::now().format("%H:%M:%S").to_string(),
level: "GUI_INFO".to_string(),
message: format!("Config updated from server: UDP={}, LOG={}", self.config.udp_port, self.config.log_port),
});
}
}
InternalEvent::Disconnected => {
self.connected = false;
}
@@ -1025,6 +1145,63 @@ impl eframe::App for MarteDebugApp {
}
}
if let Some(dialog) = &mut self.monitoring_dialog {
let mut close = false;
egui::Window::new("Monitor Signal").show(ctx, |ui| {
ui.label(&dialog.signal_path);
ui.horizontal(|ui| {
ui.label("Period (ms):");
ui.text_edit_singleline(&mut dialog.period_ms);
});
ui.horizontal(|ui| {
if ui.button("Apply").clicked() {
let period = dialog.period_ms.parse::<u32>().unwrap_or(100);
let _ = self
.tx_cmd
.send(format!("MONITOR SIGNAL {} {}", dialog.signal_path, period));
let _ = self.tx_cmd.send("DISCOVER".to_string());
// Check if it's an array signal to add all elements to view
let mut elements = 1;
if let Some(tree) = &self.app_tree {
// Helper to find item in tree
fn find_item<'a>(item: &'a TreeItem, target: &str, current: &str) -> Option<&'a TreeItem> {
let path = if current.is_empty() { item.name.clone() } else { format!("{}.{}", current, item.name) };
if path == target || (current.is_empty() && item.name == "Root" && target.is_empty()) { return Some(item); }
if let Some(children) = &item.children {
for child in children {
if let Some(found) = find_item(child, target, &path) { return Some(found); }
}
}
None
}
if let Some(found) = find_item(tree, &dialog.signal_path, "") {
elements = found.elements.unwrap_or(1);
}
}
if elements > 1 {
for i in 0..elements {
let elem_path = format!("{}[{}]", dialog.signal_path, i);
let _ = self.internal_tx.send(InternalEvent::TraceRequested(elem_path, true));
}
} else {
let _ = self
.internal_tx
.send(InternalEvent::TraceRequested(dialog.signal_path.clone(), true));
}
close = true;
}
if ui.button("Cancel").clicked() {
close = true;
}
});
});
if close {
self.monitoring_dialog = None;
}
}
if let Some((p_idx, s_idx)) = self.style_editor {
let mut close = false;
egui::Window::new("Signal Style").show(ctx, |ui| {
@@ -1205,11 +1382,11 @@ impl eframe::App for MarteDebugApp {
ui.label("Control:");
ui.text_edit_singleline(&mut self.config.tcp_port);
ui.end_row();
ui.label("Telemetry:");
ui.text_edit_singleline(&mut self.config.udp_port);
ui.label("Telemetry (Auto):");
ui.label(&self.config.udp_port);
ui.end_row();
ui.label("Logs:");
ui.text_edit_singleline(&mut self.config.log_port);
ui.label("Logs (Auto):");
ui.label(&self.config.log_port);
ui.end_row();
});
if ui.button("🔄 Apply").clicked() {
@@ -1319,7 +1496,11 @@ impl eframe::App for MarteDebugApp {
}
});
if ui.button("").clicked() {
let _ = self.tx_cmd.send(format!("TRACE {} 0", key));
if entry.is_monitored {
let _ = self.tx_cmd.send(format!("UNMONITOR SIGNAL {}", key));
} else {
let _ = self.tx_cmd.send(format!("TRACE {} 0", key));
}
let _ = self
.internal_tx
.send(InternalEvent::ClearTrace(key.clone()));