From d3077e78ec42a05ef6724c25b721b30460fa0aec Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Tue, 3 Mar 2026 21:58:32 +0100 Subject: [PATCH] implemented array support on client and server --- .../DebugService/DebugBrokerWrapper.h | 25 +-- .../Interfaces/DebugService/DebugCore.h | 2 + .../Interfaces/DebugService/DebugService.cpp | 20 +- .../Interfaces/DebugService/DebugService.h | 3 +- Test/Configurations/debug_test.cfg | 38 +++- Test/Integration/TraceTest.cpp | 2 +- Test/UnitTests/UnitTests.cpp | 2 +- Tools/gui_client/src/main.rs | 199 ++++++++++++------ 8 files changed, 208 insertions(+), 83 deletions(-) diff --git a/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h b/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h index bae4954..51abd06 100644 --- a/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h +++ b/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h @@ -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); } } diff --git a/Source/Components/Interfaces/DebugService/DebugCore.h b/Source/Components/Interfaces/DebugService/DebugCore.h index a3a32e3..49d0529 100644 --- a/Source/Components/Interfaces/DebugService/DebugCore.h +++ b/Source/Components/Interfaces/DebugService/DebugCore.h @@ -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]; diff --git a/Source/Components/Interfaces/DebugService/DebugService.cpp b/Source/Components/Interfaces/DebugService/DebugService.cpp index b296adf..1fefebc 100644 --- a/Source/Components/Interfaces/DebugService/DebugService.cpp +++ b/Source/Components/Interfaces/DebugService/DebugService.cpp @@ -272,7 +272,9 @@ void DebugService::PatchRegistry() { DebugSignalInfo *DebugService::RegisterSignal(void *memoryAddress, TypeDescriptor type, - const char8 *name) { + const char8 *name, + uint8 numberOfDimensions, + uint32 numberOfElements) { printf(" registering: %s\n", name); mutex.FastLock(); DebugSignalInfo *res = NULL_PTR(DebugSignalInfo *); @@ -290,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; @@ -1167,9 +1171,9 @@ void DebugService::Discover(BasicTCPSocket *client) { 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 += "}"; s = line.Size(); @@ -1195,10 +1199,16 @@ void DebugService::Discover(BasicTCPSocket *client) { const char8 *typeName = TypeDescriptor::GetTypeNameFromTypeDescriptor( monitoredSignals[i].dataSource->GetSignalType( 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].internalID, - typeName ? typeName : "Unknown"); + typeName ? typeName : "Unknown", dims, elems); EnrichWithConfig(monitoredSignals[i].path.Buffer(), line); line += "}"; s = line.Size(); diff --git a/Source/Components/Interfaces/DebugService/DebugService.h b/Source/Components/Interfaces/DebugService/DebugService.h index 1ee2d7d..faabddb 100644 --- a/Source/Components/Interfaces/DebugService/DebugService.h +++ b/Source/Components/Interfaces/DebugService/DebugService.h @@ -46,7 +46,8 @@ public: virtual bool Initialise(StructuredDataI &data); DebugSignalInfo *RegisterSignal(void *memoryAddress, TypeDescriptor type, - const char8 *name); + const char8 *name, uint8 numberOfDimensions = 0, + uint32 numberOfElements = 1); void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size, uint64 timestamp); diff --git a/Test/Configurations/debug_test.cfg b/Test/Configurations/debug_test.cfg index 14caf10..f7ba9f8 100644 --- a/Test/Configurations/debug_test.cfg +++ b/Test/Configurations/debug_test.cfg @@ -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} + } } } } diff --git a/Test/Integration/TraceTest.cpp b/Test/Integration/TraceTest.cpp index 86ea792..c81fa2c 100644 --- a/Test/Integration/TraceTest.cpp +++ b/Test/Integration/TraceTest.cpp @@ -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); diff --git a/Test/UnitTests/UnitTests.cpp b/Test/UnitTests/UnitTests.cpp index f65abbc..d7db67d 100644 --- a/Test/UnitTests/UnitTests.cpp +++ b/Test/UnitTests/UnitTests.cpp @@ -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); diff --git a/Tools/gui_client/src/main.rs b/Tools/gui_client/src/main.rs index b0607a4..b7cac63 100644 --- a/Tools/gui_client/src/main.rs +++ b/Tools/gui_client/src/main.rs @@ -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")] @@ -75,6 +81,8 @@ struct TraceData { struct SignalMetadata { names: Vec, sig_type: String, + dimensions: u8, + elements: u32, } #[derive(Clone)] @@ -443,29 +451,57 @@ impl MarteDebugApp { let _ = self.tx_cmd.send(format!("INFO {}", current_path)); } if item.class.contains("Signal") { - let traceable = item.is_traceable.unwrap_or(false); - let forcable = item.is_forcable.unwrap_or(false); + let elements = item.elements.unwrap_or(1); + if elements > 1 { + let header = egui::CollapsingHeader::new(format!("{} [{}] ({} elems)", label, item.class, elements)) + .id_salt(¤t_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 { + 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(), - period_ms: "100".to_string(), + value: "".to_string(), }); } } - if forcable && ui.button("⚡ Force").clicked() { - self.forcing_dialog = Some(ForcingDialog { - signal_path: current_path.clone(), - value: "".to_string(), - }); - } } }); } @@ -841,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; @@ -933,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()); @@ -1108,9 +1160,36 @@ impl eframe::App for MarteDebugApp { .tx_cmd .send(format!("MONITOR SIGNAL {} {}", dialog.signal_path, period)); let _ = self.tx_cmd.send("DISCOVER".to_string()); - let _ = self - .internal_tx - .send(InternalEvent::TraceRequested(dialog.signal_path.clone(), true)); + + // 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() {