tolto visualizzazione ppi da debug e rivista la visualizzazione dei dati in debug

This commit is contained in:
VALLONGOL 2025-10-24 15:37:18 +02:00
parent 0bc190a257
commit 243dc5732b
4 changed files with 241 additions and 132 deletions

View File

@ -169,6 +169,9 @@ class SimulationStateHub:
"""Clears all stored data for all targets."""
with self._lock:
self._target_data.clear()
# also clear heading caches
self._latest_real_heading.clear()
self._latest_raw_heading.clear()
def _initialize_target(self, target_id: int):
"""Internal helper to create the data structure for a new target."""
@ -177,3 +180,17 @@ class SimulationStateHub:
"simulated": collections.deque(maxlen=self._history_size),
"real": collections.deque(maxlen=self._history_size),
}
def remove_target(self, target_id: int):
"""Remove all stored data for a specific target id."""
with self._lock:
try:
tid = int(target_id)
if tid in self._target_data:
del self._target_data[tid]
if tid in self._latest_real_heading:
del self._latest_real_heading[tid]
if tid in self._latest_raw_heading:
del self._latest_raw_heading[tid]
except Exception:
pass

View File

@ -137,6 +137,19 @@ class DebugPayloadRouter:
try:
parsed_for_hub = SfpRisStatusPayload.from_buffer_copy(payload)
ts_s = parsed_for_hub.scenario.timetag / 1000.0
# First: remove any targets that the server marked as inactive (flags == 0)
try:
for i, ris_t in enumerate(parsed_for_hub.tgt.tgt):
try:
if ris_t.flags == 0 and self._hub and hasattr(self._hub, 'remove_target'):
self._hub.remove_target(i)
except Exception:
pass
except Exception:
pass
# Add real states for active targets
for target in real_targets:
state_tuple = (
getattr(target, '_pos_x_ft', 0.0),
@ -146,6 +159,7 @@ class DebugPayloadRouter:
self._hub.add_real_state(
target_id=target.target_id, timestamp=ts_s, state=state_tuple
)
# Propagate heading information (if available) into the hub so
# GUI builders that reconstruct lightweight Target objects
# from the hub can also pick up the last known heading.
@ -157,6 +171,7 @@ class DebugPayloadRouter:
except Exception:
# Never allow heading propagation to break payload handling
self._logger.debug("Failed to propagate heading to hub", exc_info=True)
if self._update_queue:
try:
self._update_queue.put_nowait([])

View File

@ -11,6 +11,7 @@ import datetime
import os
import ctypes
import math
import socket
from queue import Queue, Empty
from typing import Dict, Optional, Any, List
@ -27,7 +28,7 @@ from target_simulator.core.sfp_structures import ImageLeaderData, SFPHeader
from target_simulator.gui.payload_router import DebugPayloadRouter
from target_simulator.core.models import Target, Waypoint, ManeuverType, KNOTS_TO_FPS
from target_simulator.core import command_builder
from target_simulator.gui.ppi_display import PPIDisplay
DEF_TEST_ID = 1
DEF_TEST_RANGE = 30.0
@ -67,7 +68,6 @@ class SfpDebugWindow(tk.Toplevel):
self.payload_router.add_ris_target_listener(self._queue_ris_target_update)
self.image_area_size = 150
self._ppi_visible = False
self.ip_var = tk.StringVar(value="127.0.0.1")
self.local_port_var = tk.StringVar(value="60002")
self.server_port_var = tk.StringVar(value="60001")
@ -81,7 +81,18 @@ class SfpDebugWindow(tk.Toplevel):
self.tgt_active_var = tk.BooleanVar(value=True)
self.tgt_traceable_var = tk.BooleanVar(value=True)
self.tgt_restart_var = tk.BooleanVar(value=False)
self._master_mode_names = [ "idle_master_mode", "int_bit_master_mode", "gm_master_mode", "dbs_master_mode", "rws_master_mode", "vs_master_mode", "acm_master_mode", "tws_master_mode", "sea_low_master_mode", "sea_high_master_mode", "gmti_master_mode", "bcn_master_mode", "sam_master_mode", "ta_master_mode", "wa_master_mode", "stt_master_mode", "dtt_master_mode", "sstt_master_mode", "acq_master_mode", "ftt_master_mode", "agr_master_mode", "sar_master_mode", "invalid_master_mode_", "xtst_dummy_mode", "xtst_hw_validation_mode", "boot_master_mode", "master_mode_id_cardinality_", ]
# View settings for RIS/status presentation
self.scenario_view_mode = tk.StringVar(value='simplified')
self.simplified_decimals = tk.IntVar(value=2)
self._master_mode_names = [
"idle_master_mode", "int_bit_master_mode", "gm_master_mode", "dbs_master_mode",
"rws_master_mode", "vs_master_mode", "acm_master_mode", "tws_master_mode",
"sea_low_master_mode", "sea_high_master_mode", "gmti_master_mode", "bcn_master_mode",
"sam_master_mode", "ta_master_mode", "wa_master_mode", "stt_master_mode",
"dtt_master_mode", "sstt_master_mode", "acq_master_mode", "ftt_master_mode",
"agr_master_mode", "sar_master_mode", "invalid_master_mode_", "xtst_dummy_mode",
"xtst_hw_validation_mode", "boot_master_mode", "master_mode_id_cardinality_",
]
self._create_widgets()
@ -109,10 +120,11 @@ class SfpDebugWindow(tk.Toplevel):
"""
This method runs on the GUI thread and processes all queued updates.
"""
# 1. Process target updates for the PPI
# 1. Process target updates (no PPI here; still support queued updates)
try:
while not self.debug_update_queue.empty():
real_targets = self.debug_update_queue.get_nowait()
# kept for compatibility; this window no longer shows a PPI
self.update_ppi_targets(real_targets)
except Empty:
pass
@ -136,15 +148,18 @@ class SfpDebugWindow(tk.Toplevel):
import json
struct = json.loads(payload.decode("utf-8")) if isinstance(payload, (bytes, bytearray)) else payload
# --- START OF NEW LOGIC ---
view_mode = self.scenario_view_mode.get()
decimals = self.simplified_decimals.get()
# Helper functions for conversion
def to_deg(rad): return rad * 180.0 / math.pi
def m_s_to_ft_s(ms): return ms * 3.28084
def m_to_ft(m): return m * 3.28084
def to_deg(rad):
return rad * 180.0 / math.pi
def m_s_to_ft_s(ms):
return ms * 3.28084
def m_to_ft(m):
return m * 3.28084
def decimal_deg_to_dms(deg, is_lat):
d = abs(deg)
degrees = int(d)
@ -176,7 +191,7 @@ class SfpDebugWindow(tk.Toplevel):
elif field == 'mode' and value < len(self._master_mode_names):
display_value = f"{value} ({self._master_mode_names[value].replace('_master_mode', '')})"
elif isinstance(value, list):
display_value = str(value) # Keep arrays as string
display_value = str(value)
self.scenario_tree.insert("", tk.END, values=(field, display_value))
@ -192,7 +207,7 @@ class SfpDebugWindow(tk.Toplevel):
x_pos = f"{m_to_ft(t.get('x', 0.0)):.{decimals}f} ft"
y_pos = f"{m_to_ft(t.get('y', 0.0)):.{decimals}f} ft"
z_pos = f"{m_to_ft(t.get('z', 0.0)):.{decimals}f} ft"
else: # Raw mode
else:
heading = f"{t.get('heading', 0.0):.6f}"
x_pos = f"{t.get('x', 0.0):.3f}"
y_pos = f"{t.get('y', 0.0):.3f}"
@ -201,8 +216,6 @@ class SfpDebugWindow(tk.Toplevel):
vals = (i, flags_display, heading, x_pos, y_pos, z_pos)
self.ris_tree.insert("", tk.END, values=vals)
# --- END OF NEW LOGIC ---
except Exception:
self.logger.exception("Failed to update RIS tables from JSON payload.")
@ -238,27 +251,9 @@ class SfpDebugWindow(tk.Toplevel):
self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self._create_notebook_tabs()
def _toggle_ppi(self):
try:
if self._ppi_visible:
self.ris_ppi_container.pack_forget()
self.ris_table_container.pack(fill=tk.BOTH, expand=True)
self._ppi_visible = False
self.ppi_toggle_btn.config(text="Show PPI Map")
else:
self.ris_table_container.pack_forget()
self.ris_ppi_container.pack(fill=tk.BOTH, expand=True)
self._ppi_visible = True
self.ppi_toggle_btn.config(text="Hide PPI Map")
except Exception:
self.logger.exception("Toggle PPI failed")
def update_ppi_targets(self, targets: List[Target]):
try:
if self.ris_ppi_widget:
self.ris_ppi_widget.update_targets({"real": targets})
except Exception:
self.logger.exception("Failed to update RIS PPI targets")
# PPI removed from SFP Debug Window; keep for compatibility but no-op
return
def _create_connection_widgets(self, parent):
ttk.Label(parent, text="IP:").pack(side=tk.LEFT, padx=(4, 2))
@ -304,99 +299,125 @@ class SfpDebugWindow(tk.Toplevel):
ttk.Button(quick_cmd_frame, text="pause", command=lambda: self._on_send_simple_command(command_builder.build_pause())).pack(side=tk.LEFT, padx=4)
ttk.Button(quick_cmd_frame, text="continue", command=lambda: self._on_send_simple_command(command_builder.build_continue())).pack(side=tk.LEFT, padx=4)
ttk.Button(quick_cmd_frame, text="tgtset (cur)", command=lambda: self._on_send_tgtset()).pack(side=tk.LEFT, padx=8)
self.ppi_toggle_btn = ttk.Button(quick_cmd_frame, text="Show PPI Map", command=self._toggle_ppi)
self.ppi_toggle_btn.pack(side=tk.RIGHT, padx=4)
def _create_script_sender_widgets(self, parent):
ttk.Label(parent, text="Script to send:").pack(side=tk.LEFT, padx=(5, 2))
ttk.Entry(parent, textvariable=self.script_var, width=60).pack(side=tk.LEFT, expand=True, fill=tk.X, padx=(0, 5))
self.send_script_btn = ttk.Button(parent, text="Send script", command=self._on_send_script)
self.send_script_btn.pack(side=tk.LEFT, padx=5)
# PPI widget and toggle removed from this debug window
def _create_notebook_tabs(self):
self.log_tab = scrolledtext.ScrolledText(self.notebook, state=tk.DISABLED, wrap=tk.WORD, font=("Consolas", 9))
self.notebook.add(self.log_tab, text="Raw Log")
if _IMAGE_LIBS_AVAILABLE:
self.mfd_tab = self._create_image_tab("MFD Image")
self.notebook.add(self.mfd_tab["frame"], text="MFD Image")
self.sar_tab = self._create_image_tab("SAR Image")
self.notebook.add(self.sar_tab["frame"], text="SAR Image")
"""Create the notebook tabs used by the SFP Debug Window.
This method ensures the widgets referenced elsewhere (log_tab, raw_tab_text,
ris_tree, scenario_tree, history_tree, image tabs, bin/json views) exist.
"""
# Raw packet tab
raw_frame = ttk.Frame(self.notebook)
self.raw_tab_text = scrolledtext.ScrolledText(raw_frame, height=12, state=tk.DISABLED, wrap=tk.NONE, font=("Consolas", 9))
self.raw_tab_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.notebook.add(raw_frame, text="Raw")
# Log tab
log_frame = ttk.Frame(self.notebook)
self.log_tab = scrolledtext.ScrolledText(log_frame, height=12, state=tk.DISABLED, wrap=tk.WORD, font=("Consolas", 9))
self.log_tab.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.notebook.add(log_frame, text="Log")
# RIS tab - scenario and targets (side-by-side)
ris_frame = ttk.Frame(self.notebook)
# Controls above the paned area: view mode (raw/simplified) and decimals
controls = ttk.Frame(ris_frame)
controls.pack(side=tk.TOP, fill=tk.X, padx=5, pady=(4, 2))
ttk.Label(controls, text="View:").pack(side=tk.LEFT, padx=(2, 4))
self.scenario_view_mode_opt = ttk.OptionMenu(controls, self.scenario_view_mode, self.scenario_view_mode.get(), 'simplified', 'raw')
self.scenario_view_mode_opt.pack(side=tk.LEFT)
ttk.Label(controls, text="Decimals:").pack(side=tk.LEFT, padx=(12, 4))
self.dec_spin = ttk.Spinbox(controls, from_=0, to=6, textvariable=self.simplified_decimals, width=4)
self.dec_spin.pack(side=tk.LEFT)
# Use a paned window to place scenario (left) and targets (right) side-by-side
paned = ttk.Panedwindow(ris_frame, orient=tk.HORIZONTAL)
paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
left = ttk.Frame(paned)
right = ttk.Frame(paned)
paned.add(left, weight=1)
paned.add(right, weight=3)
# Scenario tree on the left with scrollbar
self.scenario_tree = ttk.Treeview(left, columns=("field", "value"), show="headings", height=12)
self.scenario_tree.heading("field", text="Field")
self.scenario_tree.heading("value", text="Value")
self.scenario_tree.column("field", width=140, anchor="w")
self.scenario_tree.column("value", width=160, anchor="w")
self.scenario_tree.pack(fill=tk.BOTH, expand=True)
paned.add(left, weight=1)
right = ttk.Frame(paned)
self.ris_table_container = ttk.Frame(right)
self.ris_ppi_container = ttk.Frame(right)
cols = ("idx", "flags", "heading", "x", "y", "z")
self.ris_tree = ttk.Treeview(self.ris_table_container, columns=cols, show="headings", height=12)
for c, txt in zip(cols, ("#", "flags", "heading", "x", "y", "z")):
self.ris_tree.heading(c, text=txt)
self.ris_tree.column(c, width=70, anchor="center")
self.ris_tree.pack(fill=tk.BOTH, expand=True)
self.ris_table_container.pack(fill=tk.BOTH, expand=True)
paned.add(right, weight=2)
gm = getattr(self.master, "config_manager", None)
trail_len = None
if gm:
general = gm.get_general_settings() or {}
trail_len = general.get("ppi_trail_length")
self.ris_ppi_widget = PPIDisplay(self.ris_ppi_container, max_range_nm=100, trail_length=trail_len)
self.ris_ppi_widget.pack(fill=tk.BOTH, expand=True)
btn_frame = ttk.Frame(ris_frame)
btn_frame.pack(fill=tk.X, padx=5, pady=(0, 5))
self.scenario_view_mode = tk.StringVar(value="simplified")
mode_frame = ttk.Frame(btn_frame)
mode_frame.pack(side=tk.LEFT, padx=(4, 0))
ttk.Label(mode_frame, text="View:").pack(side=tk.LEFT, padx=(0, 6))
ttk.Radiobutton(mode_frame, text="Raw", value="raw", variable=self.scenario_view_mode).pack(side=tk.LEFT)
ttk.Radiobutton(mode_frame, text="Simplified", value="simplified", variable=self.scenario_view_mode).pack(side=tk.LEFT)
self.simplified_decimals = tk.IntVar(value=4)
ttk.Label(mode_frame, text=" Decimals:").pack(side=tk.LEFT, padx=(8, 2))
ttk.Spinbox(mode_frame, from_=0, to=8, width=3, textvariable=self.simplified_decimals).pack(side=tk.LEFT)
self.ris_save_csv_btn = ttk.Button(btn_frame, text="Save CSV", command=self._on_save_ris_csv)
self.ris_save_csv_btn.pack(side=tk.RIGHT)
self.notebook.add(ris_frame, text="RIS Status")
raw_frame = ttk.Frame(self.notebook)
history_frame = ttk.Frame(raw_frame, width=380)
history_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(5, 2), pady=5)
ttk.Label(history_frame, text="History (latest)").pack(anchor=tk.W, padx=4)
list_container = ttk.Frame(history_frame)
list_container.pack(fill=tk.BOTH, expand=True, padx=4, pady=(2, 4))
columns = ("ts", "flow", "tid", "size")
self.history_tree = ttk.Treeview(list_container, columns=columns, show="headings", height=20)
self.history_tree.heading("ts", text="Timestamp")
# Set sensible column widths and stretching
self.scenario_tree.column("field", width=160, anchor=tk.W, stretch=False)
self.scenario_tree.column("value", width=220, anchor=tk.W, stretch=True)
scen_scroll = ttk.Scrollbar(left, orient=tk.VERTICAL, command=self.scenario_tree.yview)
self.scenario_tree.configure(yscrollcommand=scen_scroll.set)
scen_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.scenario_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
# RIS targets table on the right with vertical and horizontal scrollbars
cols = ("index", "flags", "heading", "x", "y", "z")
self.ris_tree = ttk.Treeview(right, columns=cols, show="headings", height=16)
self.ris_tree.heading("index", text="#")
self.ris_tree.heading("flags", text="flags")
self.ris_tree.heading("heading", text="heading")
self.ris_tree.heading("x", text="x")
self.ris_tree.heading("y", text="y")
self.ris_tree.heading("z", text="z")
# Column sizing
self.ris_tree.column("index", width=40, anchor=tk.CENTER, stretch=False)
self.ris_tree.column("flags", width=60, anchor=tk.CENTER, stretch=False)
self.ris_tree.column("heading", width=80, anchor=tk.CENTER, stretch=False)
self.ris_tree.column("x", width=120, anchor=tk.E, stretch=True)
self.ris_tree.column("y", width=120, anchor=tk.E, stretch=True)
self.ris_tree.column("z", width=100, anchor=tk.E, stretch=False)
ris_v = ttk.Scrollbar(right, orient=tk.VERTICAL, command=self.ris_tree.yview)
ris_h = ttk.Scrollbar(right, orient=tk.HORIZONTAL, command=self.ris_tree.xview)
self.ris_tree.configure(yscrollcommand=ris_v.set, xscrollcommand=ris_h.set)
ris_v.pack(side=tk.RIGHT, fill=tk.Y)
ris_h.pack(side=tk.BOTTOM, fill=tk.X)
self.ris_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.notebook.add(ris_frame, text="RIS")
# History tab
history_frame = ttk.Frame(self.notebook)
self.history_tree = ttk.Treeview(history_frame, columns=("time", "flow", "tid", "size"), show="headings", height=8)
self.history_tree.heading("time", text="Time")
self.history_tree.heading("flow", text="Flow")
self.history_tree.heading("tid", text="TID")
self.history_tree.heading("size", text="Size")
self.history_tree.column("ts", width=100, anchor="w")
self.history_tree.column("flow", width=50, anchor="w")
self.history_tree.column("tid", width=40, anchor="center")
self.history_tree.column("size", width=50, anchor="e")
self.history_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.history_vscroll = ttk.Scrollbar(list_container, orient=tk.VERTICAL, command=self.history_tree.yview)
self.history_vscroll.pack(side=tk.RIGHT, fill=tk.Y)
self.history_tree.config(yscrollcommand=self.history_vscroll.set)
hb_frame = ttk.Frame(history_frame)
hb_frame.pack(fill=tk.X, padx=4, pady=(4, 4))
self.history_settings_btn = ttk.Button(hb_frame, text="Settings", command=self._open_history_settings_dialog)
self.history_settings_btn.pack(side=tk.LEFT)
self.history_clear_btn = ttk.Button(hb_frame, text="Clear", command=self._on_clear_history)
self.history_clear_btn.pack(side=tk.RIGHT)
self.raw_tab_text = scrolledtext.ScrolledText(raw_frame, state=tk.DISABLED, wrap=tk.NONE, font=("Consolas", 9))
self.raw_tab_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(2, 5), pady=5)
self.notebook.insert(1, raw_frame, text="SFP Raw")
self.bin_tab = scrolledtext.ScrolledText(self.notebook, state=tk.DISABLED, wrap=tk.NONE, font=("Consolas", 10))
self.notebook.add(self.bin_tab, text="Binary (Hex)")
self.json_tab = scrolledtext.ScrolledText(self.notebook, state=tk.DISABLED, wrap=tk.WORD, font=("Consolas", 10))
self.notebook.add(self.json_tab, text="JSON")
self.history_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
btn_frame = ttk.Frame(history_frame)
btn_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Button(btn_frame, text="Clear", command=self._on_clear_history).pack(side=tk.LEFT)
ttk.Button(btn_frame, text="Settings", command=self._open_history_settings_dialog).pack(side=tk.LEFT, padx=4)
self.notebook.add(history_frame, text="History")
# Image tabs (MFD, SAR)
self.mfd_tab = self._create_image_tab("MFD")
self.notebook.add(self.mfd_tab["frame"], text="MFD")
self.sar_tab = self._create_image_tab("SAR")
self.notebook.add(self.sar_tab["frame"], text="SAR")
# Binary and JSON viewers
bin_frame = ttk.Frame(self.notebook)
self.bin_tab = scrolledtext.ScrolledText(bin_frame, height=12, state=tk.DISABLED, wrap=tk.NONE, font=("Consolas", 9))
self.bin_tab.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.notebook.add(bin_frame, text="BIN")
json_frame = ttk.Frame(self.notebook)
self.json_tab = scrolledtext.ScrolledText(json_frame, height=12, state=tk.DISABLED, wrap=tk.NONE, font=("Consolas", 9))
self.json_tab.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.notebook.add(json_frame, text="JSON")
def _create_script_sender_widgets(self, parent):
"""Create a small UI to enter and send ad-hoc script/commands."""
frame = ttk.Frame(parent, padding=4)
frame.pack(fill=tk.X)
ttk.Label(frame, text="Script:").pack(side=tk.LEFT, padx=(0, 4))
entry = ttk.Entry(frame, textvariable=self.script_var, width=72)
entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
send_btn = ttk.Button(frame, text="Send", command=self._on_send_script)
send_btn.pack(side=tk.LEFT, padx=(6, 0))
def _on_send_target(self):
if not self.shared_communicator or not self.shared_communicator.is_open:

56
tools/test_ris_remove.py Normal file
View File

@ -0,0 +1,56 @@
# Test removal of targets with flags == 0
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s')
from target_simulator.gui.payload_router import DebugPayloadRouter
from target_simulator.analysis.simulation_state_hub import SimulationStateHub
import target_simulator.gui.payload_router as pr_mod
class FakeRisTarget:
def __init__(self, flags, x, y, z, heading):
self.flags = flags
self.x = x
self.y = y
self.z = z
self.heading = heading
class FakeScenario:
def __init__(self, timetag=123456789):
self.timetag = timetag
class FakeParsed:
def __init__(self, tgt_list):
self.tgt = type('T', (), {'tgt': tgt_list})()
self.scenario = FakeScenario()
@staticmethod
def from_buffer_copy(payload):
raise RuntimeError('Should be monkeypatched')
# first, create initial payload with 2 active targets
rads = [0.0, 1.5707964897155762]
fake_targets = []
for i, h in enumerate(rads):
fake_targets.append(FakeRisTarget(flags=1, x=1000+i, y=2000+i, z=100.0, heading=h))
original_parser = pr_mod.SfpRisStatusPayload
def make_parser(targets):
return type('P', (), {'from_buffer_copy': staticmethod(lambda payload: FakeParsed(targets))})
# inject initial
pr_mod.SfpRisStatusPayload = make_parser(fake_targets)
hub = SimulationStateHub()
router = DebugPayloadRouter(simulation_hub=hub, update_queue=None)
router._handle_ris_status(b'INJ')
print('After first injection, hub ids:', hub.get_all_target_ids())
# now create a payload where target 1 is inactive (flags=0)
fake_targets2 = [FakeRisTarget(flags=1, x=1000, y=2000, z=100.0, heading=0.0), FakeRisTarget(flags=0, x=0, y=0, z=0, heading=0.0)]
pr_mod.SfpRisStatusPayload = make_parser(fake_targets2)
router._handle_ris_status(b'INJ2')
print('After second injection (one inactive), hub ids:', hub.get_all_target_ids())
# restore
pr_mod.SfpRisStatusPayload = original_parser
print('Done')