Skip to content

controller.hpp

IController — the V5 game controller behind the HAL (chunk R1a, absorbing Phase T's T1): the INPUT half of driver control.

This header declares 4 types (26 members).

Extracted from include/shulib/hal/controller.hpp — this page is that header's documentation, reformatted, so it cannot disagree with the code. Prose about how to think about the API lives in the user guide; worked recipes live in the cookbook; this page is the complete, mechanical list of what exists.

Contents

enum class ControllerAxis

enum class ControllerAxis

The four analog stick channels, named by physical position.

enum class, declared at include/shulib/hal/controller.hpp:38.

ControllerAxis::LeftX

LeftX

left stick, horizontal (+ = pushed right)

enumerator, declared at include/shulib/hal/controller.hpp:39.

ControllerAxis::LeftY

LeftY

left stick, vertical (+ = pushed up)

enumerator, declared at include/shulib/hal/controller.hpp:40.

ControllerAxis::RightX

RightX

right stick, horizontal (+ = pushed right)

enumerator, declared at include/shulib/hal/controller.hpp:41.

ControllerAxis::RightY

RightY

right stick, vertical (+ = pushed up)

enumerator, declared at include/shulib/hal/controller.hpp:42.

enum class ControllerButton

enum class ControllerButton

The twelve driver-usable buttons. The power button is deliberately absent: pressing it turns the controller off, so no routine may ever bind it.

enum class, declared at include/shulib/hal/controller.hpp:47.

ControllerButton::L1

L1

left shoulder, upper trigger (PROS's "first" left trigger)

enumerator, declared at include/shulib/hal/controller.hpp:48.

ControllerButton::L2

L2

left shoulder, lower trigger (PROS's "second" left trigger)

enumerator, declared at include/shulib/hal/controller.hpp:49.

ControllerButton::R1

R1

right shoulder, upper trigger

enumerator, declared at include/shulib/hal/controller.hpp:50.

ControllerButton::R2

R2

right shoulder, lower trigger

enumerator, declared at include/shulib/hal/controller.hpp:51.

ControllerButton::Up

Up

left arrow pad, up

enumerator, declared at include/shulib/hal/controller.hpp:52.

ControllerButton::Down

Down

left arrow pad, down

enumerator, declared at include/shulib/hal/controller.hpp:53.

ControllerButton::Left

Left

left arrow pad, left

enumerator, declared at include/shulib/hal/controller.hpp:54.

ControllerButton::Right

Right

left arrow pad, right

enumerator, declared at include/shulib/hal/controller.hpp:55.

ControllerButton::X

X

Right button pad, TOP of the diamond. These four are declared X, B, Y, A — PROS's own order, which is neither alphabetical nor a walk around the pad, so take each button's position from these notes and never from the order.

enumerator, declared at include/shulib/hal/controller.hpp:59.

ControllerButton::B

B

right button pad, RIGHT of the diamond

enumerator, declared at include/shulib/hal/controller.hpp:60.

ControllerButton::Y

Y

right button pad, LEFT of the diamond

enumerator, declared at include/shulib/hal/controller.hpp:61.

ControllerButton::A

A

right button pad, BOTTOM of the diamond

enumerator, declared at include/shulib/hal/controller.hpp:62.

class IController

class IController

The INPUT half of driver control behind the HAL: normalized sticks, LEVEL buttons, and a positive connected/not signal. Canonical at the seam — the V5's raw −127…127 is scaled away in the adapter, and edge detection is refused here on purpose (see ButtonEdge below). One instance per PHYSICAL controller: VEX U's two drivers are a CONSTRUCTION fact (the PROS adapter takes a ControllerId), never an argument here.

class, declared at include/shulib/hal/controller.hpp:70.

IController::~IController

virtual ~IController() = default

Public defaulted special members on a polymorphic base: the virtual destructor makes delete through an IController* well-defined, and copy/move stay available so an adapter deriving from this is free to be value-like. This base carries no state, so copying one copies nothing — hold implementations by reference or pointer, never by value.

function, declared at include/shulib/hal/controller.hpp:77.

IController::IController

IController() = default

Covered by the comment on ~IController — one comment documents this run of special members.

function, declared at include/shulib/hal/controller.hpp:78.

IController::IController (overload 2)

IController(const IController&) = default

Covered by the comment on ~IController — one comment documents this run of special members.

function, declared at include/shulib/hal/controller.hpp:79.

IController::IController (overload 3)

IController(IController&&) = default

Covered by the comment on ~IController — one comment documents this run of special members.

function, declared at include/shulib/hal/controller.hpp:80.

IController::operator=

IController& operator=(const IController&) = default

Covered by the comment on ~IController — one comment documents this run of special members.

function, declared at include/shulib/hal/controller.hpp:81.

IController::operator= (overload 2)

IController& operator=(IController&&) = default

Covered by the comment on ~IController — one comment documents this run of special members.

function, declared at include/shulib/hal/controller.hpp:82.

IController::axis

[[nodiscard]] virtual double axis(ControllerAxis axis) const = 0

Current deflection of axis, normalized to [-1, 1] (full deflection = ±1). Reads 0.0 while disconnected — check isConnected() to tell a drop from centred sticks.

function, declared at include/shulib/hal/controller.hpp:87.

IController::pressed

[[nodiscard]] virtual bool pressed(ControllerButton button) const = 0

True while button is held down (a LEVEL, not an edge — see header: per-consumer edge detection lives in ButtonEdge, above the seam).

function, declared at include/shulib/hal/controller.hpp:91.

IController::isConnected

[[nodiscard]] virtual bool isConnected() const = 0

True while this controller is connected and its readings are live. POSITIVE polarity by convention (cf. IImu::isReady, IGps::hasFix).

function, declared at include/shulib/hal/controller.hpp:95.

class ButtonEdge

class ButtonEdge

Per-consumer rising-edge detector over IController::pressed() levels — the PROS-free replacement for get_digital_new_press(), which consumes the event on read so a second consumer silently misses every press (HA-104). Each consumer owns its own ButtonEdge, so N consumers all see the same press. Stateful by nature (it must remember the previous level); call update() exactly once per tick per consumer.

class, declared at include/shulib/hal/controller.hpp:105.

ButtonEdge::update

[[nodiscard]] bool update(bool pressedNow) noexcept

Feed this tick's level; returns true exactly on a false→true transition. The first call after construction reports a press only if the button is already down (prev starts false) — a button held across a mode switch registers once, deliberately, rather than being swallowed.

function, declared at include/shulib/hal/controller.hpp:111.

Design commentary, from the header

The header opens with the reasoning behind these shapes. It is reproduced here in full because a reference that only lists signatures teaches nobody why.

The header’s own reasoning — 32 lines
 IController — the V5 game controller behind the HAL (chunk R1a, absorbing
 Phase T's T1): the INPUT half of driver control. The frozen F6 facade already
 has the OUTPUT half — drive(ChassisSpeeds, Frame) — so this seam is what
 makes the library a one-stop shop for teleop without a second library.

 Canonical at the seam, like every other HAL edge (§7):
  * axis() is normalized to [-1, 1] — the V5's raw −127…127 never reaches the
    core (controller_conversion.hpp owns the one scale; HA-103).
  * pressed() is a plain bool LEVEL (is it down NOW). Edge detection ("was it
    JUST pressed") deliberately lives ABOVE the seam, in ButtonEdge below —
    PROS's get_digital_new_press() CONSUMES the event on read, so with two
    consumers one silently loses. The adapter binds get_digital() and NEVER
    get_digital_new_press() (HA-104); each consumer owns its own ButtonEdge.
  * isConnected() is a POSITIVE validity signal (true = usable), matching
    IImu::isReady() / IGps::hasFix() polarity (imu.hpp:31-33) — a controller
    really does drop mid-match, and the core must be able to tell that from
    "sticks centred" (PROS reports 0 on every channel when disconnected, so
    without this signal the two are indistinguishable).

 PARTNER SUPPORT is structural, not a retrofit: VEX U runs two drivers, so
 "which controller" is a CONSTRUCTION question (make two instances — the
 hal/pros adapter takes ControllerId::Master or ControllerId::Partner), never
 an interface question. Retrofitting a second driver through a single-
 controller seam is the reshape a seam exists to prevent.

 NOT part of the F4 freeze (that register row locked 2026-06-19 with the ten
 runtime interfaces; this seam is R1a's addition, outside it — the same
 additive-sibling shape F1 used for IDigitalOut, digital_out.hpp:22-24).
 Register row F13 records the NON-freeze out loud (D2's lesson: silence in
 that register reads as "frozen"). Freeze trigger: a second real consumer —
 T2's driver-control layer is the first.

Last updated 2026-08-15 — published from the main release branch, commit fa545ff. This site tracks releases, so between them it can lag the repository — that lag is deliberate, and it is what keeps the development log off the public site. For the current state of the code, see the repository.