Source code for openbricks.parameters

# SPDX-License-Identifier: MIT
"""
Enumerated parameters — the Pybricks ``pybricks.parameters`` pattern.

Every API argument that selects one of a fixed set of behaviours takes
a member of one of these classes, never a string. A string compares
equal to nothing here, so a typo (``then="cost"``) or a drifted
spelling (``"continue"`` vs ``"none"``) fails at the call with a
message naming the members, instead of silently selecting a default
somewhere downstream. Members print as ``Stop.COAST`` like Pybricks'
``_PybricksEnum``, and ``Stop`` carries the Pybricks numbering
(``COAST=0, BRAKE=1, HOLD=2, NONE=3``), which the native drivebase
stop codes share.

Members compare by (enum name, member name) rather than by object
identity: the simulator drops the firmware modules from ``sys.modules``
between runs and re-imports them, so a ``Stop.COAST`` bound before the
reload must still equal the one created after it. A string compares
equal to no member either way.

MicroPython ships no ``enum`` module, so this is the smallest class
that gives the same surface: ``.name``, ``.value``, ``repr``, and a
class-level member list.

Example::

    from openbricks.parameters import Stop, LineMode
    db.straight(300, then=Stop.BRAKE)
    qtr.set_mode(LineMode.CENTER)
"""


class _Enum:
    """Base for the parameter enums. Members are class attributes
    created once at import; ``type(member)`` is the enum class."""

    _members = ()
    # Reload-stable type marker: after the sim re-imports this module
    # ``_Enum`` is a new class, so equality can't use isinstance.
    _is_parameter_enum = True

    def __init__(self, name, value):
        self.name = name
        self.value = value

    def __repr__(self):
        return "%s.%s" % (type(self).__name__, self.name)

    __str__ = __repr__

    def __eq__(self, other):
        return (getattr(other, "_is_parameter_enum", False) is True
                and type(other).__name__ == type(self).__name__
                and other.name == self.name)

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash((type(self).__name__, self.name))

    @classmethod
    def members(cls):
        """The members, in declaration order."""
        return cls._members


def _define(cls, *pairs):
    members = []
    for name, value in pairs:
        m = cls(name, value)
        setattr(cls, name, m)
        members.append(m)
    cls._members = tuple(members)
    return cls


[docs] class Stop(_Enum): """What a motor or drivebase does when a move ends — Pybricks ``Stop``. * ``COAST`` — cut torque; the wheel free-wheels. * ``BRAKE`` — actively hold zero speed (passively resist on open-loop motors). * ``HOLD`` — keep controlling the motor to hold the reached position. * ``NONE`` — do not decelerate; the move hands its end speed to the next command (concatenate maneuvers without stopping). """
_define(Stop, ("COAST", 0), ("BRAKE", 1), ("HOLD", 2), ("NONE", 3))
[docs] class DriveMode(_Enum): """How a serial-bus wheel servo is driven by the drivebase engine. * ``DUTY`` — the servo runs open-loop and the engine's FF+PI loop is the speed controller (the default since 1.89.0). * ``WHEEL`` — the servo's own wheel-mode velocity loop closes the speed; the engine commands speed setpoints. """
_define(DriveMode, ("DUTY", 0), ("WHEEL", 1))
[docs] class LineMode(_Enum): """Which feature of the line a QTR array follows. * ``LEFT`` — the line's left edge, under the left setpoint element. * ``RIGHT`` — the line's right edge, under the right setpoint element. * ``CENTER`` — the line's centre, the weighted centroid over every element. """
_define(LineMode, ("LEFT", 0), ("RIGHT", 1), ("CENTER", 2))
[docs] def check(enum_cls, value, param, allowed=None): """Raise ``TypeError`` unless ``value`` is a member of ``enum_cls`` (and of ``allowed``, when the call accepts only a subset). The message names the accepted members; a string argument is called out explicitly, since that is the mistake this module exists to catch.""" members = allowed if allowed is not None else enum_cls.members() for m in members: if value == m: return value names = ", ".join(repr(m) for m in members) if isinstance(value, str): raise TypeError( "%s must be one of %s — not the string %r (import %s from " "openbricks.parameters)" % (param, names, value, enum_cls.__name__)) raise TypeError("%s must be one of %s, got %r" % (param, names, value))