Skip to content

autodiscover opmodes + opmode example#260

Open
virtuald wants to merge 8 commits into
mainfrom
opmode-discovery
Open

autodiscover opmodes + opmode example#260
virtuald wants to merge 8 commits into
mainfrom
opmode-discovery

Conversation

@virtuald

@virtuald virtuald commented May 6, 2026

Copy link
Copy Markdown
Member

This is purely vibe-coded, but this is at least an initial look at what this will look like

@virtuald virtuald force-pushed the opmode-discovery branch 2 times, most recently from 98427ce to cf18107 Compare May 8, 2026 12:52
Comment thread examples/robot/ExpansionHubSample/defaulttelemode.py


@overload
def autonomous(cls: _OpModeType, /) -> _OpModeType: ...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need docstrings for these decorators, should also document them in OpModeRobot

return False


def _iter_scan_files(root: Path):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesnt really need to be a separate function -- but also, should we only scan the current directory, or maybe a specified directory?

If the user creates a .venv next to robot.py, then it will scan that entire thing and load any opmodes found. That seems bad.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user creates a .venv next to robot.py

This is the default of tools like uv, so we definitely need to make sure this doesn't walk virtualenvs ever.


def __init__(self):
super().__init__()
_discover_decorated_opmodes(self)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the associated code with this seems a bit messy, need to reorg it a bit

@virtuald virtuald marked this pull request as ready for review May 18, 2026 14:56
"""Provides one Gamepad per standard driver station port."""

def __init__(self):
self._gamepads = tuple(Gamepad(port) for port in range(6))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest using the constant JOYSTICK_PORTS instead of a magic number

Also, why a tuple instead of an array? I don't know that it matters but I was curious if there are benefits I don't know about.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AI decided it was the right fit :)

Generally, a tuple is appropriate for something that shouldn't be changed, and a list is more appropriate for things that are mutable.

is rejected with ``TypeError``.
"""

__wpilib_user_controls_type__: type | None = None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: __dunder__ names are considered reserved for use by the Python interpreter/standard library, and we shouldn't invent our own. https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers

user_controls: Any | None,
) -> dict[str, Any]:
kwargs: dict[str, Any] = {}
initializer = opmodeCls.__dict__.get("__init__")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we explicitly look for __init__? Calling inspect.signature() on a type is perfectly valid, and would mean we don't have to remove the self parameter.


def addOpMode(
self,
opmodeCls: type,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't grab __init__ out of the object dict, we can pass through a Callable instead. That should make the type checker happier below too.

Suggested change
opmodeCls: Callable[..., OpMode],

"""

constructor_kwargs = _resolve_opmode_constructor_kwargs(
opmodeCls, self, self._user_controls

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking about how we could use this in magicbot, I think we'd want to be able to dependency inject components too, rather than passing around the entire robot. I suppose we could work with it either way though.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... we just add a general purpose injection mechanism instead of something specific to user controls?

This was to support the Java thing:

@UserControlsInstance(DefaultUserControls.class)
public class Robot extends OpModeRobot {
...

I'm not sure that a general purpose injection mechanism is a great idea. This is a bit weird too though.

@virtuald virtuald force-pushed the opmode-discovery branch from 7226c87 to d4937dd Compare June 7, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants