Skip to content

Commit 9b39930

Browse files
authored
Fix bug preventing RobotInfo widget from updating and reset RobotInfo UI upon robot disconnection (UBC-Thunderbots#3476)
1 parent bd3317f commit 9b39930

6 files changed

Lines changed: 116 additions & 144 deletions

File tree

src/software/thunderscope/common/common_widgets.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@ class ColorQLabel(QLabel):
6464
def __init__(
6565
self,
6666
label_text: str,
67-
initial_value: str,
67+
initial_value: float,
6868
min_val: float = 0,
6969
max_val: float = 100,
7070
):
7171
"""Initializes the ColorQLabel with the given label, min and max bounds
7272
Or 0 and 100 as default
7373
74-
:param label_text: the text displayed within this label as a string.
75-
:param initial_value: the initial string value of the label
74+
:param label_text: the text displayed within this label as a string
75+
:param initial_value: the initial value of the label
7676
:param min_val: the minimum value of the label color (no color)
7777
:param max_val: the maxmimum value of the label color (100% red)
7878
"""
@@ -81,17 +81,15 @@ def __init__(
8181
self.min = min_val
8282
self.max = max_val
8383
self.label_text = label_text
84-
self.initial_value = initial_value
8584

86-
self.setText(label_text + initial_value)
87-
self.__update_background_color(0)
85+
self.set_float_val(initial_value)
8886

8987
def set_float_val(self, val: float) -> None:
9088
"""Sets the current value of the label to the given float value
9189
9290
:param val: the new float value
9391
"""
94-
self.setText(f"{self.label_text}{val:02d}")
92+
self.setText(f"{self.label_text} {val:d}")
9593
self.__update_background_color(val)
9694

9795
def __update_background_color(self, val: float) -> None:

src/software/thunderscope/robot_communication.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
from typing import Self, Type
1+
from typing import Self
22

33
import threading
44
import time
55
import os
66
import software.python_bindings as tbots_cpp
77

8-
from google.protobuf.message import Message
98
from proto.import_all_protos import *
109
from software.logger.logger import create_logger
1110
from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer
@@ -268,15 +267,6 @@ def __run_primitive_set(self) -> None:
268267
if IndividualRobotMode.AI not in self.robot_control_mode_map.values():
269268
time.sleep(ROBOT_COMMUNICATIONS_TIMEOUT_S)
270269

271-
def __forward_to_proto_unix_io(self, type: Type[Message], data: Message) -> None:
272-
"""Forwards to proto unix IO iff running is true
273-
274-
:param data: the data to be passed through
275-
:param type: the proto type
276-
"""
277-
if self.running:
278-
self.current_proto_unix_io.send_proto(type, data)
279-
280270
def __enter__(self) -> Self:
281271
"""Enter RobotCommunication context manager. Setup multicast listeners
282272
for RobotStatus, RobotLogs, and RobotCrash msgs, and multicast sender for PrimitiveSet
@@ -288,22 +278,6 @@ def __enter__(self) -> Self:
288278

289279
return self
290280

291-
def __receive_robot_status(self, robot_status: Message) -> None:
292-
"""Forwards the given robot status to the full system along with the round-trip time
293-
294-
:param robot_status: RobotStatus to forward to fullsystem
295-
"""
296-
round_trip_time_seconds = time.time() - (
297-
robot_status.adjusted_time_sent.epoch_timestamp_seconds
298-
)
299-
robot_statistic = RobotStatistic(
300-
robot_id=robot_status.robot_id,
301-
round_trip_time_seconds=round_trip_time_seconds,
302-
)
303-
304-
self.__forward_to_proto_unix_io(RobotStatus, robot_status)
305-
self.__forward_to_proto_unix_io(RobotStatistic, robot_statistic)
306-
307281
def __exit__(self, type, value, traceback) -> None:
308282
"""Exit RobotCommunication context manager
309283

src/software/thunderscope/robot_diagnostics/motor_fault_view.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ def __init__(self) -> None:
2727
super().__init__()
2828
self.layout = QHBoxLayout()
2929

30-
self.enabled = None
30+
self.enabled = False
3131
self.fault_count = 0
3232
self.motor_faults = {
3333
"Front Left": None,
3434
"Front Right": None,
3535
"Back Left": None,
3636
"Back Right": None,
37+
"Dribbler": None,
3738
}
3839
self.motor_fault_tooltip = ""
3940

@@ -133,7 +134,7 @@ def update_ui(self) -> None:
133134
- if all motors are still enabled, color is yellow and text is "Warning"
134135
- if any motor is disabled, color is red and text is "Error"
135136
"""
136-
if self.enabled is None:
137+
if not self.enabled:
137138
self.motor_fault_display.setStyleSheet("background: grey; color: white;")
138139
self.motor_fault_display.setText("No Signal")
139140
else:
@@ -153,6 +154,14 @@ def update_ui(self) -> None:
153154

154155
self.fault_count_label.hide()
155156

157+
def reset_ui(self) -> None:
158+
"""Resets the UI to the default uninitialized state
159+
(widget will indicate that it has not received a signal yet)
160+
"""
161+
self.motor_fault_tooltip = ""
162+
self.enabled = False
163+
self.update_ui()
164+
156165
def refresh(self, motor_status: MotorStatus, enum_descriptor: Any) -> None:
157166
"""Converts the given message into a map of motor name to its fault info
158167
And calls functions to update the main UI and the tooltip

src/software/thunderscope/robot_diagnostics/robot_info.py

Lines changed: 69 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -100,30 +100,30 @@ def __init__(
100100
self.battery_progress_bar = common_widgets.ColorProgressBar(
101101
MIN_BATTERY_VOLTAGE - self.BATTERY_MIN_OFFSET, MAX_BATTERY_VOLTAGE
102102
)
103+
103104
# Battery Voltage Label
104105
self.battery_label = QLabel()
105-
self.battery_label.setText("NAN")
106+
106107
# Label changes when voltage bar level changes
107108
self.battery_progress_bar.floatValueChanged.connect(
108109
lambda float_val: self.battery_label.setText("%.2fV" % float_val)
109110
)
110111

111112
# Stop primitive received indicator
112113
self.stop_primitive_label = QLabel()
113-
self.stop_primitive_label.setText("NA")
114114
self.stats_layout.addWidget(self.stop_primitive_label)
115115

116116
# Primitive loss rate label
117117
self.primitive_loss_rate_label = common_widgets.ColorQLabel(
118118
label_text="P%",
119-
initial_value="NA",
119+
initial_value=0,
120120
max_val=MAX_ACCEPTABLE_PACKET_LOSS_PERCENT,
121121
)
122122

123123
# Primitive round-trip time label and queue
124124
self.primitive_rtt_label = common_widgets.ColorQLabel(
125125
label_text="RTT:",
126-
initial_value="NA",
126+
initial_value=0,
127127
max_val=MAX_ACCEPTABLE_MILLISECOND_ROUND_TRIP_TIME,
128128
min_val=MIN_ACCEPTABLE_MILLISECOND_ROUND_TRIP_TIME,
129129
)
@@ -141,17 +141,21 @@ def __init__(
141141

142142
# Control mode dropdown
143143
self.control_mode_layout = QHBoxLayout()
144-
self.control_mode_menu = self.create_control_mode_menu(available_control_modes)
144+
self.control_mode_menu = self.__create_control_mode_menu(
145+
available_control_modes
146+
)
145147

146-
# Robot Status expand button
147-
self.robot_status_expand = self.create_robot_status_expand_button()
148+
# Button to expand/collapse the robot status view
149+
self.expand_robot_status_button = QPushButton()
150+
self.expand_robot_status_button.setCheckable(True)
151+
self.expand_robot_status_button.setText("INFO")
148152

149153
# motor fault visualisation for the 4 wheel motors
150154
self.motor_fault_view = MotorFaultView()
151155

152156
self.control_mode_layout.addWidget(self.motor_fault_view)
153157
self.control_mode_layout.addWidget(self.control_mode_menu)
154-
self.control_mode_layout.addWidget(self.robot_status_expand)
158+
self.control_mode_layout.addWidget(self.expand_robot_status_button)
155159

156160
self.status_layout.addLayout(self.control_mode_layout)
157161

@@ -160,10 +164,10 @@ def __init__(
160164
self.robot_model_layout.setContentsMargins(0, 5, 5, 0)
161165

162166
# Vision Pattern
163-
self.color_vision_pattern = self.create_vision_pattern(
167+
self.color_vision_pattern = self.__create_vision_pattern(
164168
Colors.ROBOT_MIDDLE_BLUE, ROBOT_RADIUS, True
165169
)
166-
self.bw_vision_pattern = self.create_vision_pattern(
170+
self.bw_vision_pattern = self.__create_vision_pattern(
167171
Colors.BW_ROBOT_MIDDLE_BLUE, ROBOT_RADIUS, False
168172
)
169173

@@ -189,17 +193,33 @@ def __init__(
189193
self.last_robot_status = None
190194
self.last_robot_statistic = None
191195

192-
def create_robot_status_expand_button(self) -> QPushButton:
193-
"""Creates the button to expand / collapse the robot status view
196+
def update_robot_status(self, robot_status: RobotStatus):
197+
"""Receives a RobotStatus message and updates the UI to reflect the new data
198+
199+
:param robot_status: The latest RobotStatus message for this robot
200+
"""
201+
self.time_of_last_robot_status = time.time()
202+
self.last_robot_status = robot_status
203+
204+
self.robot_model.setPixmap(self.color_vision_pattern)
205+
self.__update_ui()
206+
207+
# We should check in after DISCONNECT_DURATION_MS and see whether we've
208+
# received any new RobotStatus messages within the time that passed.
209+
# If not, then the robot probably disconnected.
210+
QtCore.QTimer.singleShot(
211+
int(DISCONNECT_DURATION_MS), self.__check_for_disconnection
212+
)
194213

195-
:return: QPushButton object
214+
def update_robot_statistic(self, robot_statistic: RobotStatistic):
215+
"""Receives a RobotStatistic message and updates the UI to reflect the new data
216+
217+
:param robot_statistic: The latest RobotStatistic message for this robot
196218
"""
197-
button = QPushButton()
198-
button.setCheckable(True)
199-
button.setText("INFO")
200-
return button
219+
self.last_robot_statistic = robot_statistic
220+
self.__update_ui()
201221

202-
def create_control_mode_menu(
222+
def __create_control_mode_menu(
203223
self, available_control_modes: list[IndividualRobotMode]
204224
) -> QComboBox:
205225
"""Creates the drop down menu to select the input for each robot
@@ -232,7 +252,7 @@ def create_control_mode_menu(
232252

233253
return control_mode_menu
234254

235-
def create_vision_pattern(
255+
def __create_vision_pattern(
236256
self, team_colour: QtGui.QColor, radius: int, connected: bool
237257
) -> QtGui.QPixmap:
238258
"""Given a robot id, team color and radius, draw the vision
@@ -295,56 +315,43 @@ def create_vision_pattern(
295315

296316
return pixmap
297317

298-
def update_robot_status(self, robot_status: RobotStatus):
299-
"""Receives a RobotStatus message
300-
301-
Saves the current time as the last robot status time
302-
Sets the robot UI as connected and updates the UI
303-
Then sets a timer callback to disconnect the robot if needed
304-
305-
:param robot_status: The robot status message for this robot
318+
def __check_for_disconnection(self) -> None:
319+
"""Calculates the time between the last robot status received and now.
320+
If more than our threshold, assume the robot disconnected and reset the UI.
306321
"""
307-
self.time_of_last_robot_status = time.time()
308-
309-
self.robot_model.setPixmap(self.color_vision_pattern)
310-
311-
self.last_robot_status = robot_status
312-
313-
self.__update_ui()
314-
315-
QtCore.QTimer.singleShot(int(DISCONNECT_DURATION_MS), self.disconnect_robot)
316-
317-
def update_rtt(self, robot_statistic: RobotStatistic):
318-
self.last_robot_statistic = robot_statistic
319-
320-
self.__update_ui()
321-
322-
def disconnect_robot(self) -> None:
323-
"""Calculates the time between the last robot status and now
324-
If more than our threshold, resets UI
325-
"""
326-
time_since_last_robot_status = time.time() - self.time_of_last_robot_status
327322
if (
328-
time_since_last_robot_status
329-
> DISCONNECT_DURATION_MS * SECONDS_PER_MILLISECOND
323+
time.time() - self.time_of_last_robot_status
324+
>= DISCONNECT_DURATION_MS * SECONDS_PER_MILLISECOND
330325
):
331326
self.__reset_ui()
332327

333328
def __reset_ui(self) -> None:
334329
"""Resets the UI to the default, uninitialized values"""
330+
self.__update_stop_primitive(None)
331+
335332
self.robot_model.setPixmap(self.bw_vision_pattern)
336333

334+
self.primitive_loss_rate_label.set_float_val(0)
335+
self.primitive_rtt_label.set_float_val(0)
337336
self.breakbeam_label.update_breakbeam_status(None)
337+
self.battery_progress_bar.setValue(self.battery_progress_bar.minimum())
338+
self.motor_fault_view.reset_ui()
338339

339-
def __update_stop_primitive(self, is_running: bool) -> None:
340+
def __update_stop_primitive(self, is_running: bool | None) -> None:
340341
"""Updates the stop primitive label based on the current running state
341342
342-
:param is_running: if the robot is running currently
343+
:param is_running: true if the robot is running currently, false if the
344+
robot is stopped, or None if the robot is disconnected
345+
and there is no primitive executor status
343346
"""
344-
self.stop_primitive_label.setText("RUN" if is_running else "STOP")
345-
self.stop_primitive_label.setStyleSheet(
346-
f"background-color: {'green' if is_running else 'red'}; border: 1px solid black;"
347-
)
347+
if is_running is None:
348+
self.stop_primitive_label.setVisible(False)
349+
else:
350+
self.stop_primitive_label.setVisible(True)
351+
self.stop_primitive_label.setText("RUN" if is_running else "STOP")
352+
self.stop_primitive_label.setStyleSheet(
353+
f"background-color: {'green' if is_running else 'red'}; border: 1px solid black;"
354+
)
348355

349356
def __update_ui(self) -> None:
350357
"""Receives important sections of RobotStatus proto for this robot and updates widget with alerts
@@ -357,11 +364,12 @@ def __update_ui(self) -> None:
357364
"""
358365
if not self.last_robot_status or not self.last_robot_statistic:
359366
return
360-
motor_status = robot_status.motor_status
361-
power_status = robot_status.power_status
362-
network_status = robot_status.network_status
363-
primitive_executor_status = robot_status.primitive_executor_status
364-
rtt_time_seconds = robot_statistic.round_trip_time_seconds
367+
368+
motor_status = self.last_robot_status.motor_status
369+
power_status = self.last_robot_status.power_status
370+
network_status = self.last_robot_status.network_status
371+
primitive_executor_status = self.last_robot_status.primitive_executor_status
372+
rtt_time_seconds = self.last_robot_statistic.round_trip_time_seconds
365373

366374
self.__update_stop_primitive(primitive_executor_status.running_primitive)
367375

@@ -374,6 +382,7 @@ def __update_ui(self) -> None:
374382
rtt_time_seconds * MILLISECONDS_PER_SECOND
375383
)
376384
)
385+
377386
self.breakbeam_label.update_breakbeam_status(power_status.breakbeam_tripped)
378387

379388
self.motor_fault_view.refresh(

0 commit comments

Comments
 (0)