Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/ElevatorIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ public static class ElevatorOutputs {
/** The voltage currently applied to the motors */
public MutVoltage elevatorAppliedVolts = Volts.mutable(0.0);

/** The current closed-loop output from Motion Magic */
public double elevatorClosedLoopOutput = 0.0;

/** Contribution of the p-term to motor output */
public MutVoltage pContrib = Volts.mutable(0.0);

Expand Down
3 changes: 2 additions & 1 deletion examples/ElevatorIOTalonFX.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ public void applyOutputs(ElevatorOutputs outputs) {
"elevator/referenceSlope",
leadMotor.getClosedLoopReferenceSlope().getValueAsDouble());
outputs.elevatorAppliedVolts.mut_replace(
Volts.of(leadMotor.getClosedLoopOutput().getValueAsDouble()));
leadMotor.getMotorVoltage().getValue());
outputs.elevatorClosedLoopOutput = leadMotor.getClosedLoopOutput().getValueAsDouble();
outputs.pContrib.mut_replace(
Volts.of(leadMotor.getClosedLoopProportionalOutput().getValueAsDouble()));
outputs.iContrib.mut_replace(
Expand Down
7 changes: 2 additions & 5 deletions examples/exampleconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
"name": "Elevator",
"kind": "Elevator",
"canbus": "canivore",
"motors": [
"leadMotor",
"followerMotor"
],
"motors": ["leadMotor", "followerMotor"],
"lead_motor": "leadMotor",
"encoder": "elevatorEncoder"
}
}
44 changes: 44 additions & 0 deletions src/robotvibecoder/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
Utilities for printing warnings and errors and colors
"""

from dataclasses import dataclass
import sys
from typing import TextIO


@dataclass
class Colors:
"""
ANSI Escape Codes for text colors and effects
"""

fg_red = "\x1b[31m"
fg_green = "\x1b[32m"
fg_cyan = "\x1b[36m"
bold = "\x1b[1m"
reset = "\x1b[0m"

title_str = f"{fg_cyan}RobotVibeCoder{reset}"


def print_err(message: str, file: TextIO = sys.stderr):
"""Print an error message

:param message: The error message.
:type message: str
:param file: File to print the error message to, defaults to sys.stderr
:type file: _type_, optional
"""
print(f"{Colors.fg_red}{Colors.bold}Error{Colors.reset}: {message}", file=file)


def print_warning(message: str, file: TextIO = sys.stderr):
"""Print a warning message

:param message: The warning message.
:type message: str
:param file: File to print the warning message to, defaults to sys.stderr
:type file: _type_, optional
"""
print(f"{Colors.fg_red}{Colors.bold}Warning{Colors.reset}: {message}", file=file)
28 changes: 20 additions & 8 deletions src/robotvibecoder/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import json
import sys

from robotvibecoder.cli import print_err


class MechanismKind(str, Enum):
"""
Expand Down Expand Up @@ -49,14 +51,13 @@ def generate_config_from_data(data: dict) -> MechanismConfig:
"""
for key in data:
if key not in [field.name for field in fields(MechanismConfig)]:
print(f"Error: Config contained unexpected field `{key}`", file=sys.stdout)
print_err(f"Config contained unexpected field `{key}`")
sys.exit(1)

for field in fields(MechanismConfig):
if field.name not in data:
print(
f"Error: Config missing field `{field.name}`",
file=sys.stdout,
print_err(
f"Config missing field `{field.name}`",
)
sys.exit(1)

Expand All @@ -79,10 +80,10 @@ def load_json_config(config_path: str) -> MechanismConfig:
with open(config_path, "r", encoding="utf-8") as config_file:
data = json.load(config_file)
except FileNotFoundError:
print(f"Error: Specified config file {config_path} does not exist.")
print_err(f"Specified config file {config_path} does not exist.")
sys.exit(1)
except json.JSONDecodeError:
print(f"Error: Invalid JSON format in {config_path}")
print_err(f"Invalid JSON format in {config_path}")
sys.exit(1)

return generate_config_from_data(data)
Expand All @@ -94,11 +95,22 @@ def validate_config(config: MechanismConfig) -> None:
"""

if config.lead_motor not in config.motors:
print(
f"Error in `{config.name}` config: `lead_motor` must be one of the motors listed in `motors`" # pylint: disable=line-too-long
print_err(
f"`{config.name}` config: `lead_motor` must be one of the motors listed in `motors`" # pylint: disable=line-too-long
)

print(
f" Found `{config.lead_motor}` but expected one of {', '.join(['`' + motor + '`' for motor in config.motors])}" # pylint: disable=line-too-long
)
sys.exit(1)

if len(config.motors) != len(set(config.motors)):
motors_seen: set[str] = set()

for motor in config.motors:
if motor not in motors_seen:
motors_seen.add(motor)
else:
print_err(f"`{config.name}` config: Duplicate motor {motor}")

sys.exit(1)
17 changes: 0 additions & 17 deletions src/robotvibecoder/constants.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"""
Constants that will be reused/don't need to live in code, e.g. default config
& color escape codes
"""

from dataclasses import dataclass
from robotvibecoder.config import MechanismConfig, MechanismKind


Expand All @@ -16,18 +14,3 @@
"leftMotor",
"exampleEncoder",
)


@dataclass
class Colors:
"""
ANSI Escape Codes for text colors and effects
"""

fg_red = "\x1b[31m"
fg_green = "\x1b[32m"
fg_cyan = "\x1b[36m"
bold = "\x1b[1m"
reset = "\x1b[0m"

title_str = f"{fg_cyan}RobotVibeCoder{reset}"
6 changes: 2 additions & 4 deletions src/robotvibecoder/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

import argparse
from robotvibecoder import constants
from robotvibecoder import cli
from robotvibecoder.subcommands.new import new
from robotvibecoder.subcommands.generate import generate

Expand Down Expand Up @@ -58,9 +58,7 @@ def main() -> None:
# Call the default function defined by the subcommand
args.func(args)

print(
f"{constants.Colors.fg_green}{constants.Colors.bold}Done.{constants.Colors.reset}"
)
print(f"{cli.Colors.fg_green}{cli.Colors.bold}Done.{cli.Colors.reset}")


if __name__ == "__main__":
Expand Down
19 changes: 10 additions & 9 deletions src/robotvibecoder/subcommands/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import os
import sys

from robotvibecoder import constants
from robotvibecoder import cli
from robotvibecoder.cli import print_err, print_warning
from robotvibecoder.config import (
MechanismConfig,
MechanismKind,
Expand All @@ -29,12 +30,12 @@ def generate(args: Namespace) -> None:
config: MechanismConfig = generate_config_from_data(data)
else:
if args.config is None:
print(
"Error: Config not specified: Either --stdin or --config [file] must be supplied to command." # pylint: disable=line-too-long
print_err(
"Config not specified: Either --stdin or --config [file] must be supplied to command." # pylint: disable=line-too-long
)
sys.exit(1)
config_path = os.path.join(args.folder, args.config)
print(f"[{constants.Colors.title_str}] Reading config file at {config_path}")
print(f"[{cli.Colors.title_str}] Reading config file at {config_path}")
config = load_json_config(config_path)

validate_config(config)
Expand All @@ -53,8 +54,8 @@ def generate(args: Namespace) -> None:
}

if not args.stdin:
print(
f"{constants.Colors.fg_red}{constants.Colors.bold}WARNING{constants.Colors.reset}: This will create/overwrite files at the following paths:" # pylint: disable=line-too-long
print_warning(
"This will create/overwrite files at the following paths:" # pylint: disable=line-too-long
)
for file_template, file_output in template_to_output_map.items():
output_path = os.path.join(
Expand All @@ -75,12 +76,12 @@ def generate(args: Namespace) -> None:
if os.path.exists(output_path) and args.stdin:
# stdin mode skips the warning prompt at the start, so files would
# be destroyed, necessitating this check
print(
f"Error: File {output_path} already exists. Please move/delete it and retry"
print_err(
f"File {output_path} already exists. Please move/delete it and retry"
)
sys.exit(1)

print(f"{constants.Colors.fg_cyan}➜{constants.Colors.reset} {output_path}")
print(f"{cli.Colors.fg_cyan}➜{cli.Colors.reset} {output_path}")
template_path = file_template
template = env.get_template(template_path)

Expand Down
10 changes: 6 additions & 4 deletions src/robotvibecoder/subcommands/new.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import sys

from robotvibecoder import constants
import robotvibecoder.cli
from robotvibecoder.config import MechanismConfig, MechanismKind


Expand Down Expand Up @@ -76,9 +77,10 @@ def new(args: Namespace) -> None:
"""
config_path = os.path.join(args.folder, args.outfile)

print(f"[{constants.Colors.title_str}] Creating a new config file")
print(
f" {constants.Colors.fg_red}{constants.Colors.bold}WARNING{constants.Colors.reset}: This will create/overwrite a file at `{constants.Colors.fg_cyan}{config_path}{constants.Colors.reset}`" # pylint: disable=line-too-long
print(f"[{robotvibecoder.cli.Colors.title_str}] Creating a new config file")
print(" ", end="", file=sys.stderr) # Indent the warning on the line below
robotvibecoder.cli.print_warning(
f"This will create/overwrite a file at `{robotvibecoder.cli.Colors.fg_cyan}{config_path}{robotvibecoder.cli.Colors.reset}`" # pylint: disable=line-too-long
)
try:
input(" Press Ctrl+C to cancel or [Enter] to continue")
Expand All @@ -92,6 +94,6 @@ def new(args: Namespace) -> None:
else:
config = constants.DEFAULT_CONFIG

print(f"[{constants.Colors.title_str}] Writing config file")
print(f"[{robotvibecoder.cli.Colors.title_str}] Writing config file")
with open(config_path, "w+", encoding="utf-8") as outfile:
json.dump(config.__dict__, fp=outfile, indent=2)
23 changes: 10 additions & 13 deletions src/robotvibecoder/templating.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from jinja2 import Environment, PackageLoader, select_autoescape

from robotvibecoder import constants
from robotvibecoder import cli
from robotvibecoder.cli import print_err


def article(word: str) -> str:
Expand Down Expand Up @@ -62,9 +63,9 @@ def hash_can_id(device: str) -> str:
if device not in GlobalTemplateState.can_id_map:
next_id = GlobalTemplateState.new_id()
GlobalTemplateState.can_id_map[device] = next_id
print(f" {constants.Colors.fg_green}➜{constants.Colors.reset} ", end="")
print(f" {cli.Colors.fg_green}➜{cli.Colors.reset} ", end="")
print(
f"Mapped device {constants.Colors.fg_cyan}{device}{constants.Colors.reset} to placeholder CAN ID {constants.Colors.fg_cyan}{next_id}{constants.Colors.reset}" # pylint: disable=line-too-long
f"Mapped device {cli.Colors.fg_cyan}{device}{cli.Colors.reset} to placeholder CAN ID {cli.Colors.fg_cyan}{next_id}{cli.Colors.reset}" # pylint: disable=line-too-long
)

return str(GlobalTemplateState.can_id_map[device])
Expand All @@ -80,8 +81,8 @@ def pos_dimension(kind: str) -> str:
return "Distance"

# Flywheels won't have a position
print(
f"{constants.Colors.fg_red}Error:{constants.Colors.reset} Invalid kind {kind} passed to pos_dimension." # pylint: disable=line-too-long
print_err(
f"Invalid kind {kind} passed to pos_dimension." # pylint: disable=line-too-long
)
print(
"This is a robotvibecoder issue, NOT a user error. Please report this on github!"
Expand All @@ -108,8 +109,8 @@ def pos_unit(kind: str) -> str:
if kind == "Elevator":
return "Meters"

print(
f"{constants.Colors.fg_red}Error:{constants.Colors.reset} Invalid kind {kind} passed to pos_unit." # pylint: disable=line-too-long
print_err(
f"Invalid kind {kind} passed to pos_unit." # pylint: disable=line-too-long
)
print(
"This is a robotvibecoder issue, NOT user error. Please report this on github!"
Expand Down Expand Up @@ -141,9 +142,7 @@ def goal(kind: str) -> str:
if kind == "Flywheel":
return "Speed"

print(
f"{constants.Colors.fg_red}Error:{constants.Colors.reset} Invalid kind {kind} passed to goal." # pylint: disable=line-too-long
)
print_err(f"Invalid kind {kind} passed to goal.") # pylint: disable=line-too-long
print(
"This is a robotvibecoder issue, NOT a user error. Please report this on github!"
)
Expand All @@ -164,9 +163,7 @@ def goal_dimension(kind: str) -> str:
if kind == "Flywheel":
return "AngularVelocity"

print(
f"{constants.Colors.fg_red}Error:{constants.Colors.reset} Invalid kind {kind} passed to goal_dimension." # pylint: disable=line-too-long
)
print_err(f"Invalid kind {kind} passed to goal_dimension.")
print(
"This is a robotvibecoder issue, NOT a user error. Please report this on github!"
)
Expand Down