diff --git a/bd_tools/src/bd_tools/__main__.py b/bd_tools/src/bd_tools/__main__.py index cf68db3..8c93148 100644 --- a/bd_tools/src/bd_tools/__main__.py +++ b/bd_tools/src/bd_tools/__main__.py @@ -1,70 +1,23 @@ """The betz drive tools package.""" -import argparse -import sys -from pathlib import Path - -import bd_tools - -BIN_PATH = Path(__file__).parent / "bin" +import dataclasses +from typing import Union +import dcargs -def get_tools(): - """Returns all scripts in the bin directory""" - return [tool.stem for tool in BIN_PATH.glob("[!__]*.py")] - - -def parser_args(tools): - parser = argparse.ArgumentParser(description="BetzDrive Tools Package") - parser.add_argument( - "tool", - type=str, - choices=tools, - help="Tool from the following: %(choices)s", - metavar="tool", - ) - return parser +import bd_tools -def action(args): - file_name = BIN_PATH / (args.tool + ".py") - # NOTE(greg): Shifts argv down one (and deletes the 0th arg) so the - # sub-tool does not see its own name as the 1st arg. - sys.argv = sys.argv[1:] - # Correctly make arg0 the path to the file we're executing. - sys.argv[0] = str(file_name) - tool = getattr(bd_tools.bin, args.tool) - tool.action(tool.parser_args()) +def cli_main( + cmd: Union[ + bd_tools.bin.calibrate_encoder.Calibrate, + bd_tools.bin.control_motor.Control, + ] +): + cmd.main() def main(): - # NOTE(greg): We have to hack the help to make sure it only operates on - # this argparser if its the first arg. - tool_help = False - if "-h" in sys.argv or "--help" in sys.argv: - if not (sys.argv[1] == "-h" or sys.argv[1] == "--help"): - tool_help = True - if "-h" in sys.argv: - sys.argv.remove("-h") - if "--help" in sys.argv: - sys.argv.remove("--help") - - # Need to cache args to sub-command so the parser doesn't see them as - # "unrecognized arguments" - cache_args = [] - if len(sys.argv) > 2: - cache_args = sys.argv[2:] - sys.argv = sys.argv[:2] - - args = parser_args(get_tools()).parse_args() - - sys.argv.extend(cache_args) - - # If we requested a tool help, inject it back into the sys args for the - # sub-tool to process. - if tool_help: - sys.argv.append("--help") - - action(args) + dcargs.cli(cli_main) if __name__ == "__main__": diff --git a/bd_tools/src/bd_tools/bin/calibrate_encoder.py b/bd_tools/src/bd_tools/bin/calibrate_encoder.py index 5e2c641..7696572 100755 --- a/bd_tools/src/bd_tools/bin/calibrate_encoder.py +++ b/bd_tools/src/bd_tools/bin/calibrate_encoder.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 -import argparse +import dataclasses import json import struct import time +import dcargs import matplotlib.pyplot as plt import numpy as np import serial @@ -23,6 +24,24 @@ ] +@dataclasses.dataclass +class Calibrate: + """Calibrate the encoder on a motor controller board. + + Args: + duty_cycle: fixed duty cycle through the motor for feed forward control + during calibration. + max_steps: maximum number of steps before giving up (should be greater + than or equal to erevs/mrev * 6). + delay: time between stepping phases. + """ + + serial: boards.Serial + duty_cycle: dcargs.conf.Positional[float] + max_steps: int = 126 + delay: float = 0.05 + + def parser_args(): parser = argparse.ArgumentParser( description="Calibrate the encoder on a motor controller board." diff --git a/bd_tools/src/bd_tools/bin/control_motor.py b/bd_tools/src/bd_tools/bin/control_motor.py index 674d46a..1914c48 100755 --- a/bd_tools/src/bd_tools/bin/control_motor.py +++ b/bd_tools/src/bd_tools/bin/control_motor.py @@ -3,12 +3,29 @@ import argparse import ast +import dataclasses import serial from bd_tools import boards, comms, utils +@dataclasses.dataclass +class Control: + """Drive motor module(s) with a given control mode. + + Args: + num_iters: Number of iterations to loop (default to infinity). + """ + + serial: boards.Serial + motor: boards.Motor + num_iters: int = 0 + + def main(self): + action(self) + + def parser_args(): parser = argparse.ArgumentParser( description="Drive motor module(s) with a given control mode." @@ -35,7 +52,9 @@ def make_list(x): def make_type(x, to_type): return [to_type(y) for y in x] - board_ids = make_type(make_list(ast.literal_eval(args.board_ids)), int) + board_ids = make_type( + make_list(ast.literal_eval(args.motor.board_ids)), int + ) actuations = make_list(ast.literal_eval(args.actuations)) mode = args.mode diff --git a/bd_tools/src/bd_tools/boards.py b/bd_tools/src/bd_tools/boards.py index 67b04b7..ab26080 100644 --- a/bd_tools/src/bd_tools/boards.py +++ b/bd_tools/src/bd_tools/boards.py @@ -1,7 +1,11 @@ from __future__ import print_function +import dataclasses import struct import time +from typing import List, Literal, Tuple, Union + +import dcargs from bd_tools import comms @@ -18,6 +22,45 @@ COMM_ROR_ROTOR_POS_RAW = 0x3010 +@dataclasses.dataclass +class Serial: + """Serial args. + + Args: + serial: Serial port that the motor controller is connected to. + board_ids: List of motor controllers to talk with. + baud_rate: communication frequency over serial connection. + """ + + serial: dcargs.conf.Positional[str] + board_ids: dcargs.conf.Positional[List[int]] + baud_rate: int = comms.COMM_DEFAULT_BAUD_RATE + + +@dataclasses.dataclass +class Motor: + """Motor args. + + Args: + actuation: Methods to actuate the motor. + current (Id[A], Iq[A]) + phase (dc,dc,dc) + torque (N*m) + velocity (rad/s) + position (rad) + pos_vel (rad,rad/s) + pos_ff (rad,ff[A]) + pwm (dc) + values: list of values for per-motor actuation. Should match the number + of args listed under the method of actuation. + """ + + actuation: dcargs.conf.Positional[Literal["current", "phase", + "torque", "velocity", "position", + "pos_vel", "pos_ff", "pwm"]] + values: dcargs.conf.Positional[List[Tuple[float, float, float]]] + + def addBoardArgs(parser): parser.add_argument("serial", type=str, help="Serial port") parser.add_argument("--baud_rate", type=int, help="Serial baud rate") @@ -96,7 +139,7 @@ def loadCalibrationFromJSON(client, board_id, calibration_obj): eac_table_len = len(calibration_obj["eac_table"]) slice_len = 64 for i in range(0, eac_table_len, slice_len): - table_slice = calibration_obj["eac_table"][i : i + slice_len] + table_slice = calibration_obj["eac_table"][i: i + slice_len] client.writeRegisters( [board_id], [0x1200 + i],