sistemato visualizzazione details messaggi
This commit is contained in:
parent
231efbf812
commit
944c2694fd
@ -63,6 +63,8 @@ class MonitorApp(tk.Frame):
|
||||
self.import_error = None
|
||||
self.create_widgets()
|
||||
self.update_loop_running = False
|
||||
# cache tree item ids by message label for incremental updates
|
||||
self._tree_items = {}
|
||||
# No background bind or retry at startup; user must press Initialize.
|
||||
|
||||
def create_widgets(self):
|
||||
@ -132,8 +134,19 @@ class MonitorApp(tk.Frame):
|
||||
details_frame = tk.LabelFrame(middle, text="Details")
|
||||
details_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=False)
|
||||
tk.Label(details_frame, text="Message details:").pack(anchor=tk.W)
|
||||
self.detail_text = scrolledtext.ScrolledText(details_frame, width=40, height=18)
|
||||
self.detail_text.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)
|
||||
# Use a Treeview for stable tabular details: parameter | value
|
||||
self.detail_tree = ttk.Treeview(details_frame, columns=("param", "value"), show="headings", height=18)
|
||||
self.detail_tree.heading("param", text="Parameter")
|
||||
self.detail_tree.heading("value", text="Value")
|
||||
self.detail_tree.column("param", width=180)
|
||||
self.detail_tree.column("value", width=320)
|
||||
vsb = ttk.Scrollbar(details_frame, orient=tk.VERTICAL, command=self.detail_tree.yview)
|
||||
self.detail_tree.configure(yscrollcommand=vsb.set)
|
||||
self.detail_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(6,0), pady=6)
|
||||
vsb.pack(side=tk.RIGHT, fill=tk.Y, padx=(0,6), pady=6)
|
||||
# map param name -> item id for quick updates
|
||||
self.detail_rows = {}
|
||||
self.detail_selected_label = None
|
||||
|
||||
# Bottom: Log area
|
||||
log_frame = tk.LabelFrame(self, text="Log")
|
||||
@ -232,6 +245,167 @@ class MonitorApp(tk.Frame):
|
||||
self.import_error = str(e)
|
||||
return None
|
||||
|
||||
def _fmt_enum(self, val, enum_names):
|
||||
"""Format a numeric `val` with possible enum name(s).
|
||||
|
||||
`enum_names` is a list of candidate enum class names to try in the
|
||||
`Grifo_E_1553lib.data_types.enums` module. Returns a string like
|
||||
"3 (RWS)" or the original value if no mapping found.
|
||||
"""
|
||||
try:
|
||||
import importlib
|
||||
enums_mod = None
|
||||
try:
|
||||
enums_mod = importlib.import_module('Grifo_E_1553lib.data_types.enums')
|
||||
except Exception:
|
||||
try:
|
||||
enums_mod = importlib.import_module('pybusmonitor1553.Grifo_E_1553lib.data_types.enums')
|
||||
except Exception:
|
||||
enums_mod = None
|
||||
if enums_mod is None:
|
||||
return str(val)
|
||||
for name in enum_names:
|
||||
enum_cls = getattr(enums_mod, name, None)
|
||||
if enum_cls is not None:
|
||||
try:
|
||||
# if val is already enum instance, get value
|
||||
v = int(val)
|
||||
try:
|
||||
enum_name = enum_cls(v).name
|
||||
except Exception:
|
||||
enum_name = str(getattr(enum_cls, v, ''))
|
||||
return f"{v} ({enum_name})"
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return str(val)
|
||||
|
||||
def _format_ctypes_obj(self, obj, indent=0, max_depth=3):
|
||||
"""Recursively format ctypes-backed message fields into readable text.
|
||||
|
||||
- Prints simple ctypes numbers as integers
|
||||
- For nested objects, recurses up to `max_depth`
|
||||
- Uses `_fmt_enum` to annotate enum-like values where possible
|
||||
"""
|
||||
pad = ' ' * indent
|
||||
out_lines = []
|
||||
if obj is None:
|
||||
return f"{pad}<None>\n"
|
||||
# primitive types
|
||||
try:
|
||||
# ctypes scalar like c_uint16 etc. -> has value attribute or can be int()
|
||||
if hasattr(obj, 'value') and not hasattr(obj, '__dict__'):
|
||||
try:
|
||||
return f"{pad}{int(obj.value)}\n"
|
||||
except Exception:
|
||||
return f"{pad}{obj}\n"
|
||||
# plain int/float/str
|
||||
if isinstance(obj, (int, float, str)):
|
||||
return f"{pad}{obj}\n"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# If object has a 'raw' attribute, prefer that as a primitive value
|
||||
if hasattr(obj, 'raw') and not hasattr(obj, '__dict__'):
|
||||
try:
|
||||
raw = int(obj.raw)
|
||||
# Try mapping by class name
|
||||
cls_name = obj.__class__.__name__
|
||||
enum_str = self._fmt_enum(raw, [cls_name])
|
||||
return f"{pad}{raw} ({enum_str if enum_str!=str(raw) else cls_name})\n"
|
||||
except Exception:
|
||||
return f"{pad}{repr(obj)}\n"
|
||||
|
||||
# If object is a ctypes-like struct or has attributes, iterate fields
|
||||
if max_depth <= 0:
|
||||
return f"{pad}{obj}\n"
|
||||
|
||||
# Try to iterate public attributes
|
||||
names = [n for n in dir(obj) if not n.startswith('_')]
|
||||
simple_fields = []
|
||||
for n in names:
|
||||
try:
|
||||
v = getattr(obj, n)
|
||||
except Exception:
|
||||
continue
|
||||
# skip methods
|
||||
if callable(v):
|
||||
continue
|
||||
# skip module/class descriptors
|
||||
if isinstance(v, (type,)):
|
||||
continue
|
||||
# format recursively
|
||||
try:
|
||||
if hasattr(v, '__dict__') or hasattr(v, 'raw') or isinstance(v, (int, float, str)):
|
||||
nested = self._format_ctypes_obj(v, indent=indent+1, max_depth=max_depth-1)
|
||||
simple_fields.append(f"{pad}{n}:\n{nested}")
|
||||
else:
|
||||
# fallback repr
|
||||
simple_fields.append(f"{pad}{n}: {v}\n")
|
||||
except Exception:
|
||||
simple_fields.append(f"{pad}{n}: <error>\n")
|
||||
|
||||
if simple_fields:
|
||||
return '\n'.join(simple_fields) + '\n'
|
||||
return f"{pad}{repr(obj)}\n"
|
||||
|
||||
def _format_value_for_table(self, val):
|
||||
"""Return a short string suitable for the details table's value column."""
|
||||
try:
|
||||
# simple primitives
|
||||
if val is None:
|
||||
return "<None>"
|
||||
if isinstance(val, (int, float, str)):
|
||||
return str(val)
|
||||
# prefer common ctypes attrs
|
||||
if hasattr(val, 'raw'):
|
||||
try:
|
||||
return self._fmt_enum(int(getattr(val, 'raw')), [val.__class__.__name__])
|
||||
except Exception:
|
||||
try:
|
||||
return str(getattr(val, 'raw'))
|
||||
except Exception:
|
||||
return repr(val)
|
||||
if hasattr(val, 'value'):
|
||||
try:
|
||||
return str(int(val.value))
|
||||
except Exception:
|
||||
try:
|
||||
return str(val.value)
|
||||
except Exception:
|
||||
return repr(val)
|
||||
# fallback: attempt a shallow ctypes-style formatting
|
||||
try:
|
||||
s = self._format_ctypes_obj(val, indent=0, max_depth=1)
|
||||
return s.strip().splitlines()[0]
|
||||
except Exception:
|
||||
return repr(val)
|
||||
except Exception:
|
||||
return '<unrepresentable>'
|
||||
|
||||
def _add_detail_row(self, name: str, value: str):
|
||||
"""Insert or update a row in the details table (param -> value)."""
|
||||
try:
|
||||
if name in self.detail_rows:
|
||||
iid = self.detail_rows[name]
|
||||
try:
|
||||
# keep the param column stable
|
||||
self.detail_tree.item(iid, values=(name, value))
|
||||
return iid
|
||||
except Exception:
|
||||
pass
|
||||
# insert new row
|
||||
iid = self.detail_tree.insert('', tk.END, values=(name, value))
|
||||
self.detail_rows[name] = iid
|
||||
return iid
|
||||
except Exception:
|
||||
try:
|
||||
# best-effort: insert without tracking
|
||||
return self.detail_tree.insert('', tk.END, values=(name, str(value)))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def on_init(self):
|
||||
# On-demand import/creation of the connection manager when the user requests initialization.
|
||||
@ -424,16 +598,14 @@ class MonitorApp(tk.Frame):
|
||||
pass
|
||||
|
||||
def refresh_messages(self):
|
||||
self.tree.delete(*self.tree.get_children())
|
||||
# Incremental tree update: preserve selection and avoid full redraw
|
||||
try:
|
||||
mdb = self._get_message_db()
|
||||
if mdb is None:
|
||||
# no message DB available (import error) -> nothing to show
|
||||
return
|
||||
msgs = mdb.getAllMessages()
|
||||
lbl_filter = self.filter_entry.get().strip().lower()
|
||||
pmin = None
|
||||
pmax = None
|
||||
pmin = pmax = None
|
||||
try:
|
||||
if self.period_min.get().strip() != "":
|
||||
pmin = float(self.period_min.get().strip())
|
||||
@ -442,55 +614,91 @@ class MonitorApp(tk.Frame):
|
||||
except Exception:
|
||||
pmin = pmax = None
|
||||
|
||||
# preserve current selection label (if any)
|
||||
cur_sel = None
|
||||
sel = self.tree.selection()
|
||||
if sel:
|
||||
try:
|
||||
cur_item = sel[0]
|
||||
cur_vals = self.tree.item(cur_item, 'values')
|
||||
if cur_vals:
|
||||
cur_sel = cur_vals[0]
|
||||
except Exception:
|
||||
cur_sel = None
|
||||
|
||||
# mark seen labels to remove stale rows afterwards
|
||||
seen = set()
|
||||
|
||||
for k, v in msgs.items():
|
||||
if lbl_filter and lbl_filter not in k.lower():
|
||||
continue
|
||||
# cw raw
|
||||
cw_raw = None
|
||||
wc = ''
|
||||
|
||||
# compute display fields
|
||||
try:
|
||||
cw_raw = getattr(v.head.cw, 'raw', None)
|
||||
wc = getattr(v.head.cw.str, 'wc', '')
|
||||
except Exception:
|
||||
cw_raw = None
|
||||
|
||||
# sw
|
||||
sw_raw = ''
|
||||
wc = ''
|
||||
try:
|
||||
sw_raw = getattr(v.head, 'sw', '')
|
||||
except Exception:
|
||||
sw_raw = ''
|
||||
|
||||
# num -> sent_count (how many times this message was sent)
|
||||
num = getattr(v, 'sent_count', getattr(v, 'size', ''))
|
||||
# errs
|
||||
errs = ''
|
||||
try:
|
||||
errs = getattr(v.head, 'errcode', '')
|
||||
except Exception:
|
||||
errs = ''
|
||||
|
||||
# period (ms)
|
||||
period = getattr(v, '_time_ms', None)
|
||||
if period is None:
|
||||
# try compute from freq
|
||||
freq = getattr(v, 'freq', None)
|
||||
if freq:
|
||||
period = 1000.0 / freq
|
||||
|
||||
if period is not None and pmin is not None and period < pmin:
|
||||
continue
|
||||
if period is not None and pmax is not None and period > pmax:
|
||||
continue
|
||||
|
||||
# mc -> recv_count (how many times we received this message)
|
||||
mc = ''
|
||||
try:
|
||||
mc = getattr(v, 'recv_count', '')
|
||||
except Exception:
|
||||
mc = ''
|
||||
|
||||
self.tree.insert('', tk.END, values=(k, hex(cw_raw) if cw_raw is not None else '', sw_raw, num, errs, f"{period:.3f}" if period else '', wc, mc))
|
||||
values = (k, hex(cw_raw) if cw_raw is not None else '', sw_raw, num, errs, f"{period:.3f}" if period else '', wc, mc)
|
||||
|
||||
# update existing row or insert new one
|
||||
if k in self._tree_items and self._tree_items[k] in self.tree.get_children(''):
|
||||
try:
|
||||
self.tree.item(self._tree_items[k], values=values)
|
||||
except Exception:
|
||||
# fallback: recreate item
|
||||
try:
|
||||
self.tree.delete(self._tree_items[k])
|
||||
except Exception:
|
||||
pass
|
||||
iid = self.tree.insert('', tk.END, values=values)
|
||||
self._tree_items[k] = iid
|
||||
else:
|
||||
iid = self.tree.insert('', tk.END, values=values)
|
||||
self._tree_items[k] = iid
|
||||
|
||||
seen.add(k)
|
||||
|
||||
# remove stale items not present anymore
|
||||
stale = [lbl for lbl in list(self._tree_items.keys()) if lbl not in seen]
|
||||
for lbl in stale:
|
||||
try:
|
||||
self.tree.delete(self._tree_items[lbl])
|
||||
except Exception:
|
||||
pass
|
||||
del self._tree_items[lbl]
|
||||
|
||||
# restore selection if possible
|
||||
if cur_sel and cur_sel in self._tree_items:
|
||||
try:
|
||||
self.tree.selection_set(self._tree_items[cur_sel])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -506,25 +714,82 @@ class MonitorApp(tk.Frame):
|
||||
self.show_message_detail(label)
|
||||
|
||||
def show_message_detail(self, label: str):
|
||||
self.detail_text.delete('1.0', tk.END)
|
||||
# build table for this label; detail_tree will be populated and updated
|
||||
try:
|
||||
self.detail_tree.delete(*self.detail_tree.get_children())
|
||||
except Exception:
|
||||
pass
|
||||
self.detail_rows = {}
|
||||
self.detail_selected_label = label
|
||||
try:
|
||||
mdb = self._get_message_db()
|
||||
if mdb is None:
|
||||
self.detail_text.insert(tk.END, f"Message DB not available (import error). Open Diagnostics for details.\n")
|
||||
try:
|
||||
self.detail_tree.insert('', tk.END, values=("Message DB not available", "Open Diagnostics for details."))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.log.insert(tk.END, "Message DB not available (import error). Open Diagnostics for details.\n")
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
msg_wrapper = mdb.getMessage(label)
|
||||
except Exception:
|
||||
self.detail_text.insert(tk.END, f"Message {label} not found in MessageDB\n")
|
||||
return
|
||||
# Try direct lookup first
|
||||
try:
|
||||
msg_wrapper = mdb.getMessage(label)
|
||||
except Exception:
|
||||
# Try alternative access patterns: getAllMessages() dict lookup
|
||||
try:
|
||||
allm = mdb.getAllMessages()
|
||||
# exact key
|
||||
if label in allm:
|
||||
msg_wrapper = allm[label]
|
||||
else:
|
||||
# case-insensitive match or startswith
|
||||
found = None
|
||||
for k in allm.keys():
|
||||
if k.lower() == label.lower() or k.startswith(label):
|
||||
found = allm[k]
|
||||
break
|
||||
msg_wrapper = found
|
||||
except Exception:
|
||||
msg_wrapper = None
|
||||
except Exception as e:
|
||||
# log diagnostic to the UI log and detail pane
|
||||
try:
|
||||
self.log.insert(tk.END, f"show_message_detail lookup error: {e}\n")
|
||||
except Exception:
|
||||
pass
|
||||
self.detail_text.insert(tk.END, f"Message {label} not found in MessageDB (error).\n")
|
||||
return
|
||||
|
||||
if not msg_wrapper:
|
||||
self.detail_text.insert(tk.END, f"No data for {label}\n")
|
||||
try:
|
||||
self.detail_tree.insert('', tk.END, values=("No data", f"{label}"))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.log.insert(tk.END, f"No wrapper found for {label} in MessageDB\n")
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
try:
|
||||
self.log.insert(tk.END, f"No wrapper found for {label} in MessageDB\n")
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
# The actual ctypes message instance
|
||||
msg = getattr(msg_wrapper, 'message', None)
|
||||
if msg is None:
|
||||
self.detail_text.insert(tk.END, "No message field\n")
|
||||
try:
|
||||
self.detail_tree.insert('', tk.END, values=("No message", "<empty>"))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.log.insert(tk.END, f"Wrapper for {label} has no 'message' attribute\n")
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
# Handle known tellbacks
|
||||
@ -538,16 +803,21 @@ class MonitorApp(tk.Frame):
|
||||
mm = rb.get_master_mode()
|
||||
except Exception:
|
||||
mm = getattr(rb, 'raw', rb)
|
||||
self.detail_text.insert(tk.END, f"B7 - RdrStatusTellback:\n")
|
||||
self.detail_text.insert(tk.END, f" master_mode: {mm}\n")
|
||||
try:
|
||||
mm_str = self._fmt_enum(mm, ['RdrModes', 'RdrModes'])
|
||||
except Exception:
|
||||
mm_str = str(mm)
|
||||
self._add_detail_row('master_mode', mm_str)
|
||||
try:
|
||||
des = rb.get_des_ctrl()
|
||||
self.detail_text.insert(tk.END, f" designation_ctrl: {des}\n")
|
||||
des_str = self._fmt_enum(des, ['DesControl', 'DesignationStatus'])
|
||||
self._add_detail_row('designation_ctrl', des_str)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
ib = rb.get_ibit()
|
||||
self.detail_text.insert(tk.END, f" ibit: {ib}\n")
|
||||
ib_str = self._fmt_enum(ib, ['IbitRequest', 'BITReportAvailable'])
|
||||
self._add_detail_row('ibit', ib_str)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -562,48 +832,73 @@ class MonitorApp(tk.Frame):
|
||||
sym = st.get_sym_intensity()
|
||||
except Exception:
|
||||
sym = ''
|
||||
self.detail_text.insert(tk.END, f"B6 - RdrSettingsTellback:\n")
|
||||
self.detail_text.insert(tk.END, f" history_level: {hist}\n")
|
||||
self.detail_text.insert(tk.END, f" symbology_intensity: {sym}\n")
|
||||
self._add_detail_row('history_level', self._fmt_enum(hist, ['TargetHistory']))
|
||||
self._add_detail_row('symbology_intensity', str(sym))
|
||||
|
||||
# param1/param2 fields (if present)
|
||||
try:
|
||||
if hasattr(msg, 'param1_tellback'):
|
||||
p1 = msg.param1_tellback
|
||||
# try common getters
|
||||
out = []
|
||||
for fn in ('get_rws_submode', 'get_spot', 'get_acm_submode', 'get_gm_submode', 'get_expand', 'get_range_scale', 'get_bars_num', 'get_scan_width'):
|
||||
for fn, enum_names in (
|
||||
('get_rws_submode', ['RwsSubmode']),
|
||||
('get_spot', ['SpotSelection','SpotSelection']),
|
||||
('get_acm_submode', ['AcmSubmode']),
|
||||
('get_gm_submode', ['GmSubmode','GmSubmode']),
|
||||
('get_expand', ['Expand']),
|
||||
('get_range_scale', ['RangeScale']),
|
||||
('get_bars_num', ['BarsNum']),
|
||||
('get_scan_width', ['ScanWidth','AzimuthScanWidth']),
|
||||
):
|
||||
if hasattr(p1, fn):
|
||||
try:
|
||||
out.append(f" {fn}: {getattr(p1, fn)()}" )
|
||||
v = getattr(p1, fn)()
|
||||
v_str = self._fmt_enum(v, enum_names)
|
||||
self._add_detail_row(fn, v_str)
|
||||
except Exception:
|
||||
pass
|
||||
if out:
|
||||
self.detail_text.insert(tk.END, "param1:\n")
|
||||
self.detail_text.insert(tk.END, "\n".join(out) + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if hasattr(msg, 'param2_tellback'):
|
||||
p2 = msg.param2_tellback
|
||||
self.detail_text.insert(tk.END, f"param2 raw: {getattr(p2,'raw', '')}\n")
|
||||
self._add_detail_row('param2_raw', str(getattr(p2,'raw', '')))
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
# Default: dump raw fields of ctypes message
|
||||
self.detail_text.insert(tk.END, f"Raw message fields for {label}:\n")
|
||||
for name in dir(msg):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
self.detail_tree.insert('', tk.END, values=(f"# {label}", ""))
|
||||
# default: show top-level public attributes as rows
|
||||
try:
|
||||
for name in dir(msg):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
try:
|
||||
val = getattr(msg, name)
|
||||
except Exception:
|
||||
continue
|
||||
if callable(val):
|
||||
continue
|
||||
# add row and remember item id
|
||||
vstr = self._format_value_for_table(val)
|
||||
iid = self.detail_tree.insert('', tk.END, values=(name, vstr))
|
||||
self.detail_rows[name] = iid
|
||||
except Exception as e:
|
||||
# fallback: ensure at least one row
|
||||
try:
|
||||
val = getattr(msg, name)
|
||||
self.detail_text.insert(tk.END, f"{name}: {val}\n")
|
||||
self.detail_tree.insert('', tk.END, values=("<error>", str(e)))
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.detail_text.insert(tk.END, f"Error while decoding: {e}\n")
|
||||
try:
|
||||
self.detail_tree.insert('', tk.END, values=("<error>", str(e)))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.log.insert(tk.END, f"Error while decoding: {e}\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def periodic_update(self):
|
||||
while self.manager.is_running():
|
||||
|
||||
Loading…
Reference in New Issue
Block a user