implemented array support on client and server
This commit is contained in:
@@ -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<String>,
|
||||
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() {
|
||||
|
||||
Reference in New Issue
Block a user