implemented array support on client and server

This commit is contained in:
Martino Ferrari
2026-03-03 21:58:32 +01:00
parent a941563749
commit d3077e78ec
8 changed files with 208 additions and 83 deletions

View File

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

View File

@@ -272,7 +272,9 @@ void DebugService::PatchRegistry() {
DebugSignalInfo *DebugService::RegisterSignal(void *memoryAddress, DebugSignalInfo *DebugService::RegisterSignal(void *memoryAddress,
TypeDescriptor type, TypeDescriptor type,
const char8 *name) { const char8 *name,
uint8 numberOfDimensions,
uint32 numberOfElements) {
printf("<debug> registering: %s\n", name); printf("<debug> registering: %s\n", name);
mutex.FastLock(); mutex.FastLock();
DebugSignalInfo *res = NULL_PTR(DebugSignalInfo *); DebugSignalInfo *res = NULL_PTR(DebugSignalInfo *);
@@ -290,6 +292,8 @@ DebugSignalInfo *DebugService::RegisterSignal(void *memoryAddress,
res->memoryAddress = memoryAddress; res->memoryAddress = memoryAddress;
res->type = type; res->type = type;
res->name = name; res->name = name;
res->numberOfDimensions = numberOfDimensions;
res->numberOfElements = numberOfElements;
res->isTracing = false; res->isTracing = false;
res->isForcing = false; res->isForcing = false;
res->internalID = sigIdx; res->internalID = sigIdx;
@@ -1167,9 +1171,9 @@ void DebugService::Discover(BasicTCPSocket *client) {
DebugSignalInfo *sig = signals[aliases[i].signalIndex]; DebugSignalInfo *sig = signals[aliases[i].signalIndex];
const char8 *typeName = const char8 *typeName =
TypeDescriptor::GetTypeNameFromTypeDescriptor(sig->type); 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, aliases[i].name.Buffer(), sig->internalID,
typeName ? typeName : "Unknown"); typeName ? typeName : "Unknown", sig->numberOfDimensions, sig->numberOfElements);
EnrichWithConfig(aliases[i].name.Buffer(), line); EnrichWithConfig(aliases[i].name.Buffer(), line);
line += "}"; line += "}";
s = line.Size(); s = line.Size();
@@ -1195,10 +1199,16 @@ void DebugService::Discover(BasicTCPSocket *client) {
const char8 *typeName = TypeDescriptor::GetTypeNameFromTypeDescriptor( const char8 *typeName = TypeDescriptor::GetTypeNameFromTypeDescriptor(
monitoredSignals[i].dataSource->GetSignalType( monitoredSignals[i].dataSource->GetSignalType(
monitoredSignals[i].signalIdx)); monitoredSignals[i].signalIdx));
line.Printf(" {\"name\": \"%s\", \"id\": %u, \"type\": \"%s\"",
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].path.Buffer(),
monitoredSignals[i].internalID, monitoredSignals[i].internalID,
typeName ? typeName : "Unknown"); typeName ? typeName : "Unknown", dims, elems);
EnrichWithConfig(monitoredSignals[i].path.Buffer(), line); EnrichWithConfig(monitoredSignals[i].path.Buffer(), line);
line += "}"; line += "}";
s = line.Size(); s = line.Size();

View File

@@ -46,7 +46,8 @@ public:
virtual bool Initialise(StructuredDataI &data); virtual bool Initialise(StructuredDataI &data);
DebugSignalInfo *RegisterSignal(void *memoryAddress, TypeDescriptor type, DebugSignalInfo *RegisterSignal(void *memoryAddress, TypeDescriptor type,
const char8 *name); const char8 *name, uint8 numberOfDimensions = 0,
uint32 numberOfElements = 1);
void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size, void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size,
uint64 timestamp); uint64 timestamp);

View File

@@ -17,7 +17,7 @@
} }
OutputSignals = { OutputSignals = {
Counter = { Counter = {
DataSource = DDB DataSource = SyncDB
Type = uint32 Type = uint32
} }
Time = { 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 = { +Data = {
Class = ReferenceContainer Class = ReferenceContainer
DefaultDataSource = DDB DefaultDataSource = DDB
+SyncDB = {
Class = RealTimeThreadSynchronisation
Timeout = 200
Signals = {
Counter = {
Type = uint32
}
}
}
+Timer = { +Timer = {
Class = LinuxTimer Class = LinuxTimer
Signals = { Signals = {
@@ -94,6 +122,10 @@
} }
} }
} }
+DDB3 = {
AllowNoProducer = 1
Class = GAMDataSource
}
+DAMS = { +DAMS = {
Class = TimingDataSource Class = TimingDataSource
} }
@@ -112,6 +144,10 @@
Class = RealTimeThread Class = RealTimeThread
Functions = {GAM2} Functions = {GAM2}
} }
+Thread3 = {
Class = RealTimeThread
Functions = {GAM3}
}
} }
} }
} }

View File

@@ -27,7 +27,7 @@ void TestFullTracePipeline() {
// 2. Register a mock signal // 2. Register a mock signal
uint32 mockValue = 0; 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*)); assert(sig != NULL_PTR(DebugSignalInfo*));
printf("Signal registered with ID: %u\n", sig->internalID); printf("Signal registered with ID: %u\n", sig->internalID);

View File

@@ -38,7 +38,7 @@ public:
// 1. Signal logic // 1. Signal logic
uint32 val = 0; 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.TraceSignal("Z", true) == 1);
assert(service.ForceSignal("Z", "123") == 1); assert(service.ForceSignal("Z", "123") == 1);

View File

@@ -29,8 +29,14 @@ struct Signal {
id: u32, id: u32,
#[serde(rename = "type")] #[serde(rename = "type")]
sig_type: String, sig_type: String,
#[serde(default)]
dimensions: u8,
#[serde(default = "default_elements")]
elements: u32,
} }
fn default_elements() -> u32 { 1 }
#[derive(Deserialize)] #[derive(Deserialize)]
struct DiscoverResponse { struct DiscoverResponse {
#[serde(rename = "Signals")] #[serde(rename = "Signals")]
@@ -75,6 +81,8 @@ struct TraceData {
struct SignalMetadata { struct SignalMetadata {
names: Vec<String>, names: Vec<String>,
sig_type: String, sig_type: String,
dimensions: u8,
elements: u32,
} }
#[derive(Clone)] #[derive(Clone)]
@@ -443,6 +451,33 @@ impl MarteDebugApp {
let _ = self.tx_cmd.send(format!("INFO {}", current_path)); let _ = self.tx_cmd.send(format!("INFO {}", current_path));
} }
if item.class.contains("Signal") { if item.class.contains("Signal") {
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 traceable = item.is_traceable.unwrap_or(false);
let forcable = item.is_forcable.unwrap_or(false); let forcable = item.is_forcable.unwrap_or(false);
@@ -467,6 +502,7 @@ impl MarteDebugApp {
}); });
} }
} }
}
}); });
} }
} }
@@ -841,16 +877,23 @@ fn udp_worker(
if let Some(meta) = metas.get(&id) { if let Some(meta) = metas.get(&id) {
let _ = tx_events.send(InternalEvent::TelemMatched(id)); let _ = tx_events.send(InternalEvent::TelemMatched(id));
let t = meta.sig_type.as_str(); let t = meta.sig_type.as_str();
let val = match size { 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 => { 1 => {
if t.contains('u') { if t.contains('u') {
data_slice[0] as f64 elem_data[0] as f64
} else { } else {
(data_slice[0] as i8) as f64 (elem_data[0] as i8) as f64
} }
} }
2 => { 2 => {
let b = data_slice[0..2].try_into().unwrap(); let b = elem_data[0..2].try_into().unwrap();
if t.contains('u') { if t.contains('u') {
u16::from_le_bytes(b) as f64 u16::from_le_bytes(b) as f64
} else { } else {
@@ -858,7 +901,7 @@ fn udp_worker(
} }
} }
4 => { 4 => {
let b = data_slice[0..4].try_into().unwrap(); let b = elem_data[0..4].try_into().unwrap();
if t.contains("float") { if t.contains("float") {
f32::from_le_bytes(b) as f64 f32::from_le_bytes(b) as f64
} else if t.contains('u') { } else if t.contains('u') {
@@ -868,7 +911,7 @@ fn udp_worker(
} }
} }
8 => { 8 => {
let b = data_slice[0..8].try_into().unwrap(); let b = elem_data[0..8].try_into().unwrap();
if t.contains("float") { if t.contains("float") {
f64::from_le_bytes(b) f64::from_le_bytes(b)
} else if t.contains('u') { } else if t.contains('u') {
@@ -879,12 +922,19 @@ fn udp_worker(
} }
_ => 0.0, _ => 0.0,
}; };
for name in &meta.names { for name in &meta.names {
let target_name = if meta.elements > 1 {
format!("{}[{}]", name, i)
} else {
name.clone()
};
local_updates local_updates
.entry(name.clone()) .entry(target_name.clone())
.or_default() .or_default()
.push([ts_s, val]); .push([ts_s, val]);
last_values.insert(name.clone(), val); last_values.insert(target_name, val);
}
} }
} }
offset += size as usize; offset += size as usize;
@@ -933,6 +983,8 @@ impl eframe::App for MarteDebugApp {
let meta = metas.entry(s.id).or_insert_with(|| SignalMetadata { let meta = metas.entry(s.id).or_insert_with(|| SignalMetadata {
names: Vec::new(), names: Vec::new(),
sig_type: s.sig_type.clone(), sig_type: s.sig_type.clone(),
dimensions: s.dimensions,
elements: s.elements,
}); });
if !meta.names.contains(&s.name) { if !meta.names.contains(&s.name) {
meta.names.push(s.name.clone()); meta.names.push(s.name.clone());
@@ -1108,9 +1160,36 @@ impl eframe::App for MarteDebugApp {
.tx_cmd .tx_cmd
.send(format!("MONITOR SIGNAL {} {}", dialog.signal_path, period)); .send(format!("MONITOR SIGNAL {} {}", dialog.signal_path, period));
let _ = self.tx_cmd.send("DISCOVER".to_string()); 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 let _ = self
.internal_tx .internal_tx
.send(InternalEvent::TraceRequested(dialog.signal_path.clone(), true)); .send(InternalEvent::TraceRequested(dialog.signal_path.clone(), true));
}
close = true; close = true;
} }
if ui.button("Cancel").clicked() { if ui.button("Cancel").clicked() {