openlch.cli

Defines the CLI for the OpenLCH project.

  1"""Defines the CLI for the OpenLCH project."""
  2
  3import subprocess
  4import click
  5from .hal import HAL
  6from typing import List, Tuple
  7
  8DEFAULT_IP = "192.168.42.1"
  9
 10@click.group()
 11def cli() -> None:
 12    """OpenLCH CLI tool for interacting with MilkV boards.
 13
 14    Available commands:
 15    - ping: Ping the MilkV board
 16    - get-positions: Get current positions of all servos
 17    - set-position: Set position for a specific servo
 18    - set-wifi: Set WiFi credentials for the MilkV board
 19    - get-servo-info: Get information about a specific servo
 20    - scan-servos: Scan for connected servos
 21    - change-servo-id: Change the ID of a servo
 22    - start-calibration: Start calibration for a specific servo
 23    - cancel-calibration: Cancel ongoing calibration for a specific servo
 24    - get-calibration-status: Get the current calibration status
 25    - start-video-stream: Start the video stream
 26    - stop-video-stream: Stop the video stream
 27    - get-video-stream-urls: Get the URLs for various video stream formats
 28    - get-imu-data: Get current IMU sensor data (gyroscope and accelerometer readings)
 29
 30    Use 'openlch COMMAND --help' for more information on a specific command.
 31    """
 32    pass
 33
 34@cli.command()
 35@click.argument("ip", default=DEFAULT_IP)
 36def ping(ip: str) -> None:
 37    """Ping the MilkV board at the specified IP address."""
 38    try:
 39        result = subprocess.run(["ping", "-c", "1", ip], capture_output=True, text=True, check=False)
 40        if result.returncode == 0:
 41            click.echo(f"Successfully pinged {ip}")
 42            click.echo(result.stdout)
 43        else:
 44            click.echo(f"Failed to ping {ip}")
 45            click.echo(result.stderr)
 46    except Exception as e:
 47        click.echo(f"An error occurred: {str(e)}")
 48
 49@cli.command()
 50@click.argument("ip", default=DEFAULT_IP)
 51def get_positions(ip: str) -> None:
 52    """Get current positions and speeds of all servos."""
 53    hal = HAL(ip)
 54    try:
 55        positions = hal.servo.get_positions()
 56        click.echo("Current positions and speeds:")
 57        for id, position, speed in positions:
 58            click.echo(f"Servo {id}: Position = {position:.2f}, Speed = {speed:.2f}")
 59    except Exception as e:
 60        click.echo(f"An error occurred: {str(e)}")
 61    finally:
 62        hal.close()
 63
 64@cli.command()
 65@click.argument("id", type=int)
 66@click.argument("position", type=float)
 67@click.option("--speed", "-s", type=float, default=0, help="Movement speed in degrees per second (0 = max speed)")
 68@click.argument("ip", default=DEFAULT_IP)
 69def set_position(id: int, position: float, speed: float, ip: str) -> None:
 70    """Set position for a specific servo."""
 71    hal = HAL(ip)
 72    try:
 73        hal.servo.set_position(id, position, speed)
 74        click.echo(f"Position set for servo {id} to {position}° at speed {speed if speed > 0 else 'max'} deg/s")
 75    except Exception as e:
 76        click.echo(f"An error occurred: {str(e)}")
 77    finally:
 78        hal.close()
 79
 80@cli.command()
 81@click.argument("ssid")
 82@click.argument("password")
 83@click.argument("ip", default=DEFAULT_IP)
 84def set_wifi(ssid: str, password: str, ip: str) -> None:
 85    """Set WiFi credentials for the MilkV board."""
 86    hal = HAL(ip)
 87    try:
 88        hal.system.set_wifi_info(ssid, password)
 89        click.echo("WiFi credentials set successfully")
 90    except Exception as e:
 91        click.echo(f"An error occurred: {str(e)}")
 92    finally:
 93        hal.close()
 94
 95@cli.command()
 96@click.argument("id", type=int)
 97@click.argument("ip", default=DEFAULT_IP)
 98def get_servo_info(id: int, ip: str) -> None:
 99    """Get information about a specific servo."""
100    hal = HAL(ip)
101    try:
102        info = hal.servo.get_servo_info(id)
103        click.echo(f"Servo {id} info:")
104        for key, value in info.items():
105            click.echo(f"{key}: {value}")
106    except Exception as e:
107        click.echo(f"An error occurred: {str(e)}")
108    finally:
109        hal.close()
110
111@cli.command()
112@click.argument("ip", default=DEFAULT_IP)
113def scan_servos(ip: str) -> None:
114    """Scan for connected servos."""
115    hal = HAL(ip)
116    try:
117        servo_ids = hal.servo.scan()
118        click.echo("Found servo IDs:")
119        for id in servo_ids:
120            click.echo(id)
121    except Exception as e:
122        click.echo(f"An error occurred: {str(e)}")
123    finally:
124        hal.close()
125
126@cli.command()
127@click.argument("old_id", type=int)
128@click.argument("new_id", type=int)
129@click.argument("ip", default=DEFAULT_IP)
130def change_servo_id(old_id: int, new_id: int, ip: str) -> None:
131    """Change the ID of a servo."""
132    hal = HAL(ip)
133    try:
134        success = hal.servo.change_id(old_id, new_id)
135        if success:
136            click.echo(f"Successfully changed servo ID from {old_id} to {new_id}")
137        else:
138            click.echo("Failed to change servo ID")
139    except Exception as e:
140        click.echo(f"An error occurred: {str(e)}")
141    finally:
142        hal.close()
143
144@cli.command()
145@click.argument("servo_id", type=int)
146@click.option("--speed", "-s", type=int, default=300, 
147              help="Calibration speed in degrees per second. Default: 300")
148@click.option("--current", "-c", type=float, default=600.0,
149              help="Current threshold in mA to detect end stops. Default: 600.0")
150@click.argument("ip", default=DEFAULT_IP)
151def start_calibration(servo_id: int, speed: int, current: float, ip: str) -> None:
152    """Start calibration for a specific servo.
153    
154    The calibration process will move the servo until it detects end stops based on current draw.
155    Use --speed to adjust movement speed and --current to adjust sensitivity."""
156    hal = HAL(ip)
157    try:
158        success = hal.servo.start_calibration(
159            servo_id,
160            calibration_speed=speed,
161            current_threshold=current
162        )
163        if success:
164            click.echo(f"Calibration started for servo {servo_id}")
165            click.echo(f"Speed: {speed} deg/s, Current threshold: {current} mA")
166        else:
167            click.echo(f"Failed to start calibration for servo {servo_id}")
168    except Exception as e:
169        click.echo(f"An error occurred: {str(e)}")
170    finally:
171        hal.close()
172
173@cli.command()
174@click.argument("servo_id", type=int)
175@click.argument("ip", default=DEFAULT_IP)
176def cancel_calibration(servo_id: int, ip: str) -> None:
177    """Cancel ongoing calibration for a specific servo."""
178    hal = HAL(ip)
179    try:
180        success = hal.servo.cancel_calibration(servo_id)
181        if success:
182            click.echo(f"Calibration cancelled for servo {servo_id}")
183        else:
184            click.echo(f"Failed to cancel calibration for servo {servo_id}")
185    except Exception as e:
186        click.echo(f"An error occurred: {str(e)}")
187    finally:
188        hal.close()
189
190@cli.command()
191@click.argument("ip", default=DEFAULT_IP)
192def get_calibration_status(ip: str) -> None:
193    """Get the current calibration status."""
194    hal = HAL(ip)
195    try:
196        status = hal.servo.get_calibration_status()
197        if status['is_calibrating']:
198            click.echo(f"Calibration in progress for servo {status['calibrating_servo_id']}")
199        else:
200            click.echo("No calibration in progress")
201    except Exception as e:
202        click.echo(f"An error occurred: {str(e)}")
203    finally:
204        hal.close()
205
206@cli.command()
207@click.argument("ip", default=DEFAULT_IP)
208def start_video_stream(ip: str) -> None:
209    """Start the video stream."""
210    hal = HAL(ip)
211    try:
212        hal.system.start_video_stream()
213        click.echo("Video stream started")
214    except Exception as e:
215        click.echo(f"An error occurred: {str(e)}")
216    finally:
217        hal.close()
218
219@cli.command()
220@click.argument("ip", default=DEFAULT_IP)
221def stop_video_stream(ip: str) -> None:
222    """Stop the video stream."""
223    hal = HAL(ip)
224    try:
225        hal.system.stop_video_stream()
226        click.echo("Video stream stopped")
227    except Exception as e:
228        click.echo(f"An error occurred: {str(e)}")
229    finally:
230        hal.close()
231
232@cli.command()
233@click.argument("ip", default=DEFAULT_IP)
234def get_video_stream_urls(ip: str) -> None:
235    """Get the URLs for various video stream formats."""
236    hal = HAL(ip)
237    try:
238        urls = hal.system.get_video_stream_urls()
239        click.echo("Video stream URLs:")
240        for format, url_list in urls.items():
241            click.echo(f"{format.upper()}:")
242            for url in url_list:
243                click.echo(f"  - {url}")
244    except Exception as e:
245        click.echo(f"An error occurred: {str(e)}")
246    finally:
247        hal.close()
248
249@cli.command()
250@click.option("--settings", "-s", type=(int, float), multiple=True, required=True,
251              help="Servo ID and torque value pairs (e.g., -s 1 0.5 -s 2 0.7)")
252@click.argument("ip", default=DEFAULT_IP)
253def set_torque(settings: List[Tuple[int, float]], ip: str) -> None:
254    """Set torque for multiple servos."""
255    hal = HAL(ip)
256    try:
257        hal.servo.set_torque(settings)
258        click.echo("Torque settings applied successfully:")
259        for servo_id, torque in settings:
260            click.echo(f"Servo {servo_id}: Torque = {torque:.2f}")
261    except Exception as e:
262        click.echo(f"An error occurred: {str(e)}")
263    finally:
264        hal.close()
265
266@cli.command()
267@click.option("--settings", "-s", type=(int, click.Choice(['true', 'false'])), multiple=True, required=True,
268              help="Servo ID and enable status pairs (e.g., -s 1 true -s 2 false)")
269@click.argument("ip", default=DEFAULT_IP)
270def set_torque_enable(settings: List[Tuple[int, str]], ip: str) -> None:
271    """Enable or disable torque for multiple servos."""
272    hal = HAL(ip)
273    try:
274        # Convert 'true'/'false' strings to boolean values
275        bool_settings = [(id, status.lower() == 'true') for id, status in settings]
276        hal.servo.set_torque_enable(bool_settings)
277        click.echo("Torque enable settings applied successfully:")
278        for servo_id, status in bool_settings:
279            click.echo(f"Servo {servo_id}: Torque {'enabled' if status else 'disabled'}")
280    except Exception as e:
281        click.echo(f"An error occurred: {str(e)}")
282    finally:
283        hal.close()
284
285@cli.command()
286@click.argument("ip", default=DEFAULT_IP)
287def get_imu_data(ip: str) -> None:
288    """Get current IMU sensor data (gyroscope and accelerometer readings)."""
289    hal = HAL(ip)
290    try:
291        imu_data = hal.imu.get_data()
292        click.echo("IMU Sensor Data:")
293        click.echo("\nGyroscope (degrees/second):")
294        click.echo(f"  X: {imu_data['gyro']['x']:.2f}")
295        click.echo(f"  Y: {imu_data['gyro']['y']:.2f}")
296        click.echo(f"  Z: {imu_data['gyro']['z']:.2f}")
297        click.echo("\nAccelerometer (m/s^2):")
298        click.echo(f"  X: {imu_data['accel']['x']:.2f}")
299        click.echo(f"  Y: {imu_data['accel']['y']:.2f}")
300        click.echo(f"  Z: {imu_data['accel']['z']:.2f}")
301    except Exception as e:
302        click.echo(f"An error occurred: {str(e)}")
303    finally:
304        hal.close()
305
306@cli.command()
307@click.argument("ip", default=DEFAULT_IP)
308def enable_movement(ip: str) -> None:
309    """Enable movement for all servos."""
310    hal = HAL(ip)
311    try:
312        hal.servo.enable_movement()
313        click.echo("Movement enabled for all servos")
314    except Exception as e:
315        click.echo(f"An error occurred: {str(e)}")
316    finally:
317        hal.close()
318
319@cli.command()
320@click.argument("ip", default=DEFAULT_IP)
321def disable_movement(ip: str) -> None:
322    """Disable movement for all servos."""
323    hal = HAL(ip)
324    try:
325        hal.servo.disable_movement()
326        click.echo("Movement disabled for all servos")
327    except Exception as e:
328        click.echo(f"An error occurred: {str(e)}")
329    finally:
330        hal.close()
331
332if __name__ == "__main__":
333    cli()
DEFAULT_IP = '192.168.42.1'
cli = <Group cli>

OpenLCH CLI tool for interacting with MilkV boards.

Available commands:

  • ping: Ping the MilkV board
  • get-positions: Get current positions of all servos
  • set-position: Set position for a specific servo
  • set-wifi: Set WiFi credentials for the MilkV board
  • get-servo-info: Get information about a specific servo
  • scan-servos: Scan for connected servos
  • change-servo-id: Change the ID of a servo
  • start-calibration: Start calibration for a specific servo
  • cancel-calibration: Cancel ongoing calibration for a specific servo
  • get-calibration-status: Get the current calibration status
  • start-video-stream: Start the video stream
  • stop-video-stream: Stop the video stream
  • get-video-stream-urls: Get the URLs for various video stream formats
  • get-imu-data: Get current IMU sensor data (gyroscope and accelerometer readings)

Use 'openlch COMMAND --help' for more information on a specific command.

ping = <Command ping>

Ping the MilkV board at the specified IP address.

get_positions = <Command get-positions>

Get current positions and speeds of all servos.

set_position = <Command set-position>

Set position for a specific servo.

set_wifi = <Command set-wifi>

Set WiFi credentials for the MilkV board.

get_servo_info = <Command get-servo-info>

Get information about a specific servo.

scan_servos = <Command scan-servos>

Scan for connected servos.

change_servo_id = <Command change-servo-id>

Change the ID of a servo.

start_calibration = <Command start-calibration>

Start calibration for a specific servo.

The calibration process will move the servo until it detects end stops based on current draw. Use --speed to adjust movement speed and --current to adjust sensitivity.

cancel_calibration = <Command cancel-calibration>

Cancel ongoing calibration for a specific servo.

get_calibration_status = <Command get-calibration-status>

Get the current calibration status.

start_video_stream = <Command start-video-stream>

Start the video stream.

stop_video_stream = <Command stop-video-stream>

Stop the video stream.

get_video_stream_urls = <Command get-video-stream-urls>

Get the URLs for various video stream formats.

set_torque = <Command set-torque>

Set torque for multiple servos.

set_torque_enable = <Command set-torque-enable>

Enable or disable torque for multiple servos.

get_imu_data = <Command get-imu-data>

Get current IMU sensor data (gyroscope and accelerometer readings).

enable_movement = <Command enable-movement>

Enable movement for all servos.

disable_movement = <Command disable-movement>

Disable movement for all servos.