11import subprocess
22import logging
33import typer as Typer
4+ from typer .main import get_command
45from rich import print
56from rich .live import Live
67from 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
399434if __name__ == "__main__" :
400435 with EmbeddedCommunication () as embedded_communication :
401- RobotDiagnosticsCLI (embedded_communication ).app ()
436+ RobotDiagnosticsCLI (embedded_communication ).run ()
0 commit comments