Skip to content

Commit c189c27

Browse files
authored
Lazy load EStop (#3810)
1 parent 276f281 commit c189c27

3 files changed

Lines changed: 74 additions & 34 deletions

File tree

src/software/embedded/robot_diagnostics_cli/embedded_communication.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,29 @@ def __init__(self):
3737
True,
3838
)
3939

40-
# initialising the estop
4140
self.estop_reader = None
4241
self.should_send_stop = False
43-
self.estop_mode, self.estop_path = get_estop_config(
44-
keyboard_estop=False, disable_communication=False
45-
)
42+
self.estop_mode = EstopMode.PHYSICAL_ESTOP
43+
self.estop_path = None
44+
45+
def setup_estop(self) -> bool:
46+
"""Lazily sets up the physical estop reader the first time a command
47+
that actuates the robot's electrical/mechanical components is run.
48+
49+
:return: True if a physical estop is connected and ready, False otherwise
50+
"""
51+
if self.estop_reader is not None:
52+
return True
4653

47-
# Always in PHYSICAL_ESTOP mode within CLI
4854
try:
55+
self.estop_mode, self.estop_path = get_estop_config(
56+
keyboard_estop=False, disable_communication=False
57+
)
4958
self.estop_reader = tbots_cpp.ThreadedEstopReader(self.estop_path, 115200)
5059
self.__update_estop_state()
51-
except Exception as e:
52-
raise Exception(f"Invalid Estop found at location {self.estop_path} as {e}")
60+
return True
61+
except Exception:
62+
return False
5363

5464
def __receive_robot_status(self, robot_status: Message) -> None:
5565
"""Updates the dynamic information with the new RobotStatus message.

src/software/embedded/robot_diagnostics_cli/embedded_data.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,9 @@ def get_kick_coeff(self) -> str:
6464
def get_chip_pulse_width(self) -> str:
6565
return self._get_value(ROBOT_CHIP_PULSE_WIDTH_CONFIG_KEY)
6666

67-
def get_current_draw(self) -> str:
68-
return self._get_value(ROBOT_CURRENT_DRAW_CONFIG_KEY)
69-
70-
def get_battery_volt(self) -> str:
71-
return self._get_value(ROBOT_BATTERY_VOLTAGE_CONFIG_KEY)
72-
73-
def get_cap_volt(self) -> str:
74-
return self._get_value(ROBOT_CAPACITOR_VOLTAGE_CONFIG_KEY)
67+
# TODO: #3809
68+
# def get_cap_volt(self) -> str:
69+
# return self._get_value(ROBOT_CAPACITOR_VOLTAGE_CONFIG_KEY)
7570

7671
def __clamp(self, val: float, min_val: float, max_val: float) -> float:
7772
"""Simple Math Clamp function (Faster than numpy & fewer dependencies)

src/software/embedded/robot_diagnostics_cli/robot_diagnostics_cli.py

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import subprocess
22
import logging
33
import typer as Typer
4+
from typer.main import get_command
45
from rich import print
56
from rich.live import Live
67
from rich.table import Table
@@ -52,7 +53,9 @@ def __init__(self, embedded_communication: EmbeddedCommunication) -> None:
5253
self.app.command(short_help="Shows TOML Config Values")(self.config)
5354
self.app.command(short_help="Prints Thunderloop Logs")(self.log)
5455
self.app.command(short_help="Prints Thunderloop Status")(self.status)
55-
self.app.command(short_help="Restarts Thunderloop")(self.restart_thunderloop)
56+
self.app.command(short_help="Restarts Thunderloop")(self.restart)
57+
self.app.command(short_help="Starts Thunderloop")(self.start)
58+
self.app.command(short_help="Stops Thunderloop")(self.stop)
5659
# Communication object responsible for proto execution/transmission
5760
self.embedded_communication = embedded_communication
5861
# Data handler responsible for disk information
@@ -101,6 +104,24 @@ def wrapper(self, *args, **kwargs):
101104

102105
return decorator
103106

107+
def requires_estop(func):
108+
"""Decorator that ensures a physical estop is plugged in before running a
109+
command that actuates the robot's electrical/mechanical components.
110+
"""
111+
112+
@wraps(func)
113+
def wrapper(self, *args, **kwargs):
114+
if not self.embedded_communication.setup_estop():
115+
logging.warning(
116+
"[bold yellow]No estop plugged in: this command controls the "
117+
"robot's electrical/mechanical components and requires an estop. "
118+
"Plug one in and try again. Command not executed.[/bold yellow]"
119+
)
120+
return
121+
return func(self, *args, **kwargs)
122+
123+
return wrapper
124+
104125
def __generate_stats_table(self) -> Table:
105126
"""Make a new table with robot status information."""
106127
table = Table()
@@ -161,21 +182,12 @@ def __generate_config_table(self) -> Table:
161182
f"{ROBOT_CHIP_PULSE_WIDTH_CONFIG_KEY}",
162183
self.embedded_data.get_chip_pulse_width(),
163184
)
164-
table.add_row(
165-
"Battery Voltage",
166-
f"{ROBOT_BATTERY_VOLTAGE_CONFIG_KEY}",
167-
self.embedded_data.get_battery_volt(),
168-
)
169-
table.add_row(
170-
"Battery Current Draw",
171-
f"{ROBOT_CURRENT_DRAW_CONFIG_KEY}",
172-
self.embedded_data.get_current_draw(),
173-
)
174-
table.add_row(
175-
"Capacitor Voltage",
176-
f"{ROBOT_CAPACITOR_VOLTAGE_CONFIG_KEY}",
177-
self.embedded_data.get_cap_volt(),
178-
)
185+
# TODO: #3809
186+
# table.add_row(
187+
# "Capacitor Voltage",
188+
# f"{ROBOT_CAPACITOR_VOLTAGE_CONFIG_KEY}",
189+
# self.embedded_data.get_cap_volt(),
190+
# )
179191
return table
180192

181193
def stats(self) -> None:
@@ -193,16 +205,27 @@ def status(self):
193205
log = subprocess.call(["service", "thunderloop", "status"])
194206
print(log)
195207

196-
def restart_thunderloop(self):
208+
def restart(self):
197209
"""CLI Command to restart Thunderloop service and print status"""
198-
subprocess.run(["service", "thunderloop", "restart"])
210+
subprocess.run(["sudo", "service", "thunderloop", "restart"])
211+
self.status()
212+
213+
def start(self):
214+
"""CLI Command to start Thunderloop service and print status"""
215+
subprocess.run(["sudo", "service", "thunderloop", "start"])
216+
self.status()
217+
218+
def stop(self):
219+
"""CLI Command to stop Thunderloop service and print status"""
220+
subprocess.run(["sudo", "service", "thunderloop", "stop"])
199221
self.status()
200222

201223
def log(self):
202224
"""CLI Command to print device logs"""
203225
log = subprocess.call(["sudo", "journalctl", "-f", "-n", "100"])
204226
print(log)
205227

228+
@requires_estop
206229
@catch_interrupt_exception()
207230
def rotate(
208231
self,
@@ -227,6 +250,7 @@ def rotate(
227250
description,
228251
)
229252

253+
@requires_estop
230254
@catch_interrupt_exception()
231255
def move(
232256
self,
@@ -256,6 +280,7 @@ def move(
256280
description,
257281
)
258282

283+
@requires_estop
259284
@catch_interrupt_exception()
260285
def chip(
261286
self,
@@ -289,6 +314,7 @@ def chip(
289314
self.embedded_communication.run_primitive(primitive)
290315
print(description)
291316

317+
@requires_estop
292318
@catch_interrupt_exception()
293319
def kick(
294320
self,
@@ -331,6 +357,7 @@ def kick(
331357
"Recharging...",
332358
)
333359

360+
@requires_estop
334361
@catch_interrupt_exception()
335362
def dribble(
336363
self,
@@ -358,6 +385,7 @@ def dribble(
358385
description,
359386
)
360387

388+
@requires_estop
361389
@catch_interrupt_exception()
362390
def move_wheel(
363391
self,
@@ -395,7 +423,14 @@ def emote(self):
395423
# TODO (#3434): Add an emote function!
396424
return
397425

426+
def run(self) -> None:
427+
"""Launch the interactive Diagnostics CLI shell."""
428+
self.app._add_completion = False
429+
command = get_command(self.app)
430+
command.add_help_option = False
431+
command()
432+
398433

399434
if __name__ == "__main__":
400435
with EmbeddedCommunication() as embedded_communication:
401-
RobotDiagnosticsCLI(embedded_communication).app()
436+
RobotDiagnosticsCLI(embedded_communication).run()

0 commit comments

Comments
 (0)