librescoot-vehicle (vehicle-service)¶
Description¶
The vehicle service implements the vehicle state machine, managing transitions between states like stand-by, parked, ready-to-drive, and shutting-down. It monitors GPIO inputs (kickstand, brakes, blinker switch, seatbox button, handlebar sensors), controls PWM outputs (8-channel LED system, blinker LEDs, seatbox lock, handlebar lock), and coordinates with other services via Redis to determine when the vehicle is ready to drive.
Command-Line Options¶
Usage of vehicle-service:
-log int
Service log level (0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG) (default 3)
Note: vehicle-service has minimal command-line options. Redis host and port are hardcoded to 127.0.0.1:6379. Most configuration is done via Redis and hardware interfaces.
Redis Operations¶
Hash: vehicle¶
Fields written:
state- Vehicle state (see States section below)brake:left- Left brake state ("on", "off")brake:right- Right brake state ("on", "off")blinker:state- Blinker active state ("on", "off")blinker:start_nanos- Monotonic start time of current blinker cycle (for UI sync)-
blinker:switch- Blinker switch position ("left", "right", "both", "off") (horn:buttonandseatbox:buttonare not written to thevehiclehash in this release. Each horn/seatbox edge is latched into a separatebuttonshash instead, ashorn:on/horn:off/seatbox:on/seatbox:offset to the literal"1"and never cleared.) -
seatbox:lock- Seatbox lock state ("open", "closed") kickstand- Kickstand position ("up", "down")handlebar:position- Handlebar position ("on-place", "off-place")handlebar:lock-sensor- Handlebar lock sensor ("locked", "unlocked")dbc-updating- DBC update in progress flagdashboard:power- Dashboard power state ("on", "off")update:status- Aggregated update status surface for UIauto-standby-remaining- Seconds remaining until auto-standby (absent when timer not running)auto-standby-deadline- Unix timestamp of auto-standby deadline (absent when timer not running)
Published channel: vehicle
Lists consumed (BRPOP)¶
scooter:state- State change commands ("lock", "unlock", "lock-hibernate", "force-lock")scooter:seatbox- Seatbox commands ("open")scooter:horn- Horn commands ("on", "off")scooter:blinker- Blinker commands ("left", "right", "both", "off")scooter:hop-on- Hop-on mode commands:engage- Enter the locked hop-on state (lock screen, LED cue, steering lock with the same positioning grace window as parked->stand-by: locks at once if the handlebar is already in position, otherwise the rider has the lock window to move it fully left; publishesstate=hop-on)engage-learning- Enter combo-learning state quietly (no LED cue, no steering lock, no lock screen; publishesstate=hop-on-learning)release- Exit either hop-on sub-state back toparkedscooter:led:cue- LED cue playback commands (integer cue index)scooter:led:fade- LED fade playback commands ("channel:fadeIndex")scooter:update- Update commands ("start", "complete", "start-dbc", "complete-dbc", "cycle-dashboard-power")scooter:hardware- Hardware control commands ("dashboard:on", "dashboard:off", "engine:on", "engine:off", "handlebar:lock", "handlebar:unlock")
Hashes read¶
dashboard- Readsreadyfield to check if dashboard initializedsettings- Reads behavior settings (e.g.,scooter.brake-hibernation)ble- Readsstatusfield (Bluetooth link "connected"/"disconnected") to drive lock-on-disconnectota- Reads OTA status for DBC updates (status:dbc)system- Readskeycard-master-countandkeycard-authorized-countto resolve the usb0 gate
Channels subscribed (PUBSUB)¶
dashboard- Dashboard ready notifications (payload: "ready")keycard- Keycard authentication events (payload: "authentication")ota- OTA update notificationspower-manager- Power manager eventsvehicle- Vehicle state change notificationssettings- Settings update notifications (payload: setting key that changed)ble- Bluetooth link status change notifications (payload: "status")
Channels published¶
vehicle- All vehicle state changes and sensor updatesbuttons- Raw input edges for immediate UI response (horn, seatbox, brakes, blinkers)input-events- Synthesized gestures (tap, long-tap, hold, double-tap) over the same inputs
Commands sent to other services¶
scooter:power- Hibernation command ("hibernate-manual")
Vehicle States¶
The service implements the canonical vehicle state machine with these states:
init- Initial/uninitialized statestand-by- Powered but motor disabledparked- Unlocked with kickstand downready-to-drive- All conditions met, motor enabledwaiting-seatbox- Waiting for seatbox to closeshutting-down- Transitioning to power down (~4s)updating- OTA firmware update in progresshibernation- Manual hibernation super-state (entered via brake-lever hold fromparked)hibernation-initial-hold- 15-second initial brake hold phasehibernation-awaiting-confirm- Confirmation phase (30s timeout after brakes released; continuous hold also auto-confirms)hibernation-seatbox- Confirmation blocked by open seatbox; prompts user to close ithibernation-confirm- Final 3-second non-abortable hibernation confirmationat-rest- Parent state groupingparked,hop-on,hop-on-learning. Owns the auto-standby timer; sibling transitions inside the group don't disturb it. Never the leaf, sovehicle:statenever readsat-rest.hop-on- Hop-on/hop-off mode (rider briefly off, motor disabled, optional steering lock, lock screen rendered by dashboard). Physical inputs are gated declaratively viaBlockedEventson the FSM state.hop-on-learning- Combo-learning sibling ofhop-on: same input gating (no horn / blinker / brake LED / seatbox open / hibernation hold) but no LED cue, no steering-lock attempt, no lock screen. The dashboard renders its own learn overlay.
Hibernation State Machine¶
The manual hibernation sequence works as follows:
- parked → hibernation-initial-hold (15s): Both brakes pressed continuously
- hibernation-initial-hold → hibernation-awaiting-confirm: After 15s, enter confirmation phase
- hibernation-awaiting-confirm: User can release brakes or keep holding
- If brakes released: 30s timeout starts (can be reset by touching brakes)
- If brakes held continuously for 30s total: auto-confirm (skip seatbox check)
- If brakes re-pressed after release: hold for 15s to auto-confirm
- Keycard tap or continuous hold timeout: Check safety conditions (kickstand down, seatbox closed)
- If seatbox open: transition to
hibernation-seatboxand wait for user to close it - hibernation-confirm: 3-second final countdown before hibernation
See States Documentation for complete state machine.
Hardware Interfaces¶
GPIO Inputs (via /dev/input)¶
- Input device:
/dev/input/by-path/platform-gpio-keys-event
Monitored inputs:
kickstand- Kickstand position sensor (true = down, false = up)brake_left- Left brake lever sensor (true = pressed)brake_right- Right brake lever sensor (true = pressed)blinker_left- Left blinker buttonblinker_right- Right blinker buttonhorn_button- Horn buttonseatbox_button- Seatbox unlock buttonseatbox_lock_sensor- Seatbox lock position sensor (true = closed, false = open)handlebar_position- Handlebar position sensor (true = on-place, false = off-place)handlebar_lock_sensor- Handlebar lock sensor (true = unlocked, false = locked)48v_detect- 48V power detection
GPIO Outputs (via gpiocdev)¶
Digital outputs:
seatbox_lock- Seatbox lock solenoid (GPIO chip 2, line 10) - pulsed for 200mshorn- Horn control (GPIO chip 2, line 9)engine_brake- Motor brake control (GPIO chip 2, line 11)handlebar_lock_close- Handlebar lock motor close (GPIO chip 4, line 6) - pulsed for 1100mshandlebar_lock_open- Handlebar lock motor open (GPIO chip 4, line 7) - pulsed for 1100msdashboard_power- Dashboard power enable (GPIO chip 1, line 18)engine_power- Motor controller power enable (GPIO chip 2, line 17)
PWM LED System¶
The service controls 8 PWM LED channels via the imx_pwm_led kernel module:
LED device paths: /dev/pwm_led0 through /dev/pwm_led7
PWM Configuration:
- Period: 12000 ticks
- Prescaler: 0 (default)
- Invert: 0 (default)
- Repeat: 3
LED patterns:
- Fades:
/usr/share/led-curves/fades/fade*(up to 4096 samples per fade) - Cues:
/usr/share/led-curves/cues/cue*(up to 16 cues)
Blinker timing:
- Blinker interval: 800ms
Configuration¶
Systemd Unit¶
- Unit file:
/usr/lib/systemd/system/librescoot-vehicle.service - Binary:
/usr/bin/vehicle-service - Started by: systemd at boot (after
valkey.service;redis.servicebefore Librescoot 1.2) - Restart policy: Always
- Priority: Nice value -10
- User: root
- Type: idle
Observable Behavior¶
Startup Sequence¶
- Connects to Redis at 127.0.0.1:6379
- Reads saved vehicle state from Redis (if any)
- Reads brake hibernation setting from Redis (
settingshash,scooter.brake-hibernation) - Checks if DBC update is in progress (restores
dbcUpdatingflag if needed) - Reloads PWM LED kernel module (
rmmod imx_pwm_led && modprobe imx_pwm_led) - Waits for
/dev/pwm_led0to appear (up to 1 second) - Initializes hardware:
- Opens all 8 LED device files (
/dev/pwm_led0through/dev/pwm_led7) - Configures PWM parameters for each channel
- Sets adaptive mode for non-blinker LEDs (channels 0, 1, 2, 5)
- Activates all LED channels
- Loads LED fades and cues from
/usr/share/led-curves/ - Plays initial LED cue (cue 0)
- Opens GPIO input device
- Opens GPIO output lines
- Reads
settings[scooter.usb0-policy]and bringsusb0up unless theautopolicy is already effective (see below) - Checks initial handlebar lock sensor state
- Registers input callbacks for all monitored inputs
- Publishes initial sensor states to Redis
- Restores LED state based on saved vehicle state (if parked or ready-to-drive)
- Marks system as initialized
- Handles initial dashboard ready state (if dashboard was already ready)
- Publishes initial vehicle state to Redis
- Transitions from
inittostand-byif still in init state - Starts Redis listeners (PUBSUB and BRPOP)
usb0 Link Gating¶
vehicle-service owns whether usb0 (the USB gadget link to the DBC, 192.168.7.1)
is administratively up. 10-usb0.network carries no ActivationPolicy, so
networkd configures the address and raises the link; vehicle-service takes it
down again once it applies the policy.
The scooter.usb0-policy setting picks the intent:
| Value | Meaning |
|---|---|
auto |
Default. The link tracks vehicle[dashboard:power], so it is down whenever the DBC is off. |
always-on |
The link is held up regardless of dashboard power, for installer and diagnostic reachability. |
auto is gated on keycard pairings, because usb0 is also the way back into a
scooter whose cards do not work. The gate is closed (the policy takes effect)
only once (master >= 1 AND authorized >= 1) OR authorized >= 2, read from
system[keycard-master-count] and system[keycard-authorized-count]. Below
that threshold the link is held up as if the policy were always-on.
An absent count reads as zero rather than as "not yet known", so during the
first seconds of a boot the gate resolves as if no cards were paired and the
link is simply held up. There is no waiting resolver, no system[usb0-gate]
field and no boot failsafe timer in this release.
The gate is re-read at each existing decision point (startup, a
dashboard:power change, and a scooter.usb0-policy change) rather than
subscribed to. Pairing a second card mid-install therefore does not take
usb0 away from a running installer session; the tighter policy applies from
the next transition.
State Machine Behavior¶
Ready-to-Drive Conditions¶
Vehicle enters ready-to-drive when ALL are true:
- Dashboard ready (
dashboardhashreadyfield = "true") - Kickstand up
- Current state is
parked
Manual Ready-to-Drive Activation:
In parked state, if dashboard is not ready, pressing the seatbox button while:
- Kickstand is up
- Both brakes are held
Will manually transition to ready-to-drive and blink the main light once for confirmation.
Lock/Unlock Handling¶
Lock command (LPUSH scooter:state lock):
- Must be in
parkedstate - Transitions to
shutting-down - After ~4 seconds, transitions to
stand-by - Power manager then suspends/hibernates
Unlock command (LPUSH scooter:state unlock):
- Reads kickstand state
- If dashboard ready and kickstand up: transitions to
ready-to-drive - Otherwise: transitions to
parked(if currently instand-by)
Lock-Hibernate command (LPUSH scooter:state lock-hibernate):
- Must be in
parkedstate - Sets hibernation request flag
- Transitions to
shutting-down - Sends "hibernate-manual" command to power manager via
scooter:powerlist
Force-Lock command (LPUSH scooter:state force-lock):
- Sets
forceStandbyNoLockflag - Immediately transitions to
stand-bywithout handlebar locking - Used for emergency shutdown or special cases (e.g., DBC update)
Hibernation Sequence¶
Brake-lever hibernation (can be disabled via settings hash scooter.brake-hibernation = "disabled"):
- Trigger: Both brakes pressed in
parkedstate for 15 seconds - Initial Hold (15s): Brakes must be held continuously
- If brakes released: sequence cancelled, return to
parked - After 15s: transition to
waiting-hibernationstate - Confirmation Phase:
- User can release brakes or keep holding
- If brakes released: 30-second timeout starts
- Timeout can be reset by touching brakes again
- If timeout expires: cancel hibernation, return to
parked
- If brakes held continuously for 30s total from start: auto-confirm (warehouse mode)
- If brakes re-pressed after release: hold for 15s to auto-confirm
- Keycard tap during confirmation: triggers immediate safety check and confirmation
- Safety Checks:
- Kickstand must be down (always required)
- Seatbox must be closed (skipped for auto-confirm scenarios)
- If seatbox open: transition to
waiting-hibernation-seatboxstate to notify user - Final Confirmation:
- Transition to
waiting-hibernation-confirmstate - 3-second non-abortable countdown
- Then transition to
shutting-downwith hibernation flag - Send "hibernate-manual" command to power manager
Keycard force-standby:
- Tap keycard 3 times while holding brake lever
- Immediately transitions to
stand-bywithout handlebar lock - Useful for emergency situations or servicing
Blinker Control¶
The service implements automatic blinker logic:
- Blink interval: 800ms
- Physical blinker switches only work in active states (parked, ready-to-drive, waiting states)
- In other states, blinker control comes from software commands only
- LED cues used:
- Cue 9: LED_BLINK_NONE (blinkers off)
- Cue 10: LED_BLINK_LEFT (left blinker)
- Cue 11: LED_BLINK_RIGHT (right blinker)
- Cue 12: LED_BLINK_BOTH (hazard lights)
- Publishes blinker switch and state changes to Redis
- Publishes immediate button events to
buttonschannel for UI response
Hazard switch detection - Librescoot 1.2+¶
The stock handlebar switch offers left and right only, on separate GPIO inputs
(blinker_left, blinker_right). Aftermarket switch assemblies with a physical
hazard button close both inputs at once, which the earlier code could not
represent: the press registered as left or right, never both.
handleBlinkerChange now reads the peer switch out of the activeKeys cache
when a blinker event fires. io.go updates activeKeys before invoking each
callback, so the second event's handler always sees the first switch as already
active. Both active means a combined blinker:switch of both and LED cue 12,
which drives the hazard lights. If the peer read fails the handler falls back to
the last known state rather than assuming off, since assuming off would collapse
a real hazard state into left/right/off and publish a misleading edge.
The buttons PUBSUB event always reflects the triggering channel's own edge,
independently of the combined switch state. Releasing one side of a hazard pair
therefore emits blinker:right:off even though the combined state moves to
left rather than off. Consumers that track edges stay correct; consumers
that want the combined position read blinker:switch.
Stock switches are unaffected: the peer input is never active, so every transition behaves exactly as before.
Seatbox Control¶
Open command (LPUSH scooter:seatbox open):
- Pulses seatbox lock solenoid for 200ms
- Publishes seatbox opened event to
vehiclechannel (payload: "seatbox:opened")
Button press:
- In
parkedstate: pressing seatbox button opens seatbox - In other states: button press is logged but no action taken
- Button events published to
buttonschannel for UI feedback - Pressing seatbox button cancels any active hibernation sequence
Brake Control¶
Brake state handling:
- Brake pressed: plays LED cue 4 (LED_BRAKE_OFF_TO_BRAKE_ON)
- Brake released: plays LED cue 5 (LED_BRAKE_ON_TO_BRAKE_OFF)
- Engine brake logic:
- In
ready-to-drive: engine brake follows brake levers (engaged if either pressed) - In all other states: engine brake always engaged (motor disabled)
- Brake state published to Redis (
vehiclehash,brake:leftandbrake:right) - Button events published to
buttonschannel for immediate UI response
Park debounce:
- After entering
ready-to-drive, kickstand down events are ignored for 1 second - Prevents accidental transition to parked during kickstand retraction
Gesture Detection (input-events) - Librescoot Only¶
Alongside the raw edges on buttons, vehicle-service runs a gesture detector
(internal/core/input_gestures.go) over the horn button, seatbox button and
both brake levers, publishing <source>:<gesture> on the input-events
channel.
Sources: horn, seatbox, brake:left, brake:right.
| Gesture | Fires when | Threshold |
|---|---|---|
press |
Input goes active | - |
release |
Input goes inactive | - |
long-tap |
Still held after the long-tap delay | 800 ms |
hold |
Still held after the hold delay | 3 s |
tap |
Released before the long-tap threshold | - |
double-tap |
Second tap within the double-tap window |
800 ms |
long-tap and hold fire while the input is still down, so a 4-second press
emits press, long-tap, hold, release and no tap. Emitting a long-tap
or hold clears the pending tap, so a long press between two taps does not
glue them into a double-tap.
Each gesture is emitted exactly once, which makes input-events the channel to
use for anything counting or reacting to discrete user actions. buttons
carries the raw edges; the vehicle hash carries the current level. See the
Redis reference.
Command Processing¶
Commands are consumed via BRPOP with 5-second timeout:
scooter:state- State change commandsscooter:seatbox- Seatbox controlscooter:horn- Horn controlscooter:blinker- Blinker controlscooter:led:cue- LED cue playbackscooter:led:fade- LED fade playbackscooter:update- Update coordinationscooter:hardware- Direct hardware control
Fault Detection¶
The service supports fault reporting via Redis:
- Faults are added to the
vehicle:faultset (using SADD) - Fault events are logged to the
events:faultsstream (using XADD) - Fault notifications published to
vehiclechannel (payload: "fault") - Fault codes are numeric identifiers
- Cleared faults are reported with negative code values
Current implementation does not actively monitor for faults, but the infrastructure is in place for future fault detection.
Log Output¶
The service logs to journald. Common log patterns:
- State transitions (e.g., "Transitioning from PARKED to READY_TO_DRIVE")
- Input events (e.g., "Input kickstand => true")
- Command processing (e.g., "Received command from scooter:state: lock")
- Redis operations (e.g., "Publishing vehicle state: parked")
- Hardware operations (e.g., "Seatbox lock toggled for 0.2s")
- LED operations (e.g., "Playing LED cue 4")
- Hibernation sequence events
- Button events published to UI
- Settings updates
Log levels:
- 0 = NONE: No logging
- 1 = ERROR: Fatal errors only
- 2 = WARN: Warnings and errors
- 3 = INFO: Informational messages (default)
- 4 = DEBUG: Detailed debugging output
View logs:
journalctl -u librescoot-vehicle.service
journalctl -u librescoot-vehicle.service -f # Follow mode
journalctl -u librescoot-vehicle.service --since "10 minutes ago"
Dependencies¶
Required:
- Redis server - Must be running at 127.0.0.1:6379
- GPIO input device -
/dev/input/by-path/platform-gpio-keys-event - GPIO chip devices - For digital outputs (gpiochip1, gpiochip2, gpiochip4)
- PWM LED kernel module -
imx_pwm_ledmust be loadable - PWM LED device files -
/dev/pwm_led0through/dev/pwm_led7 - LED curve files -
/usr/share/led-curves/fades/and/usr/share/led-curves/cues/
Optional:
- dashboard - Service waits for
dashboard ready= "true" before allowing ready-to-drive - keycard - Authentication via keycard events
- settings-service - For persistent settings like brake-hibernation
- update-service - For OTA update coordination
- power-manager - For hibernation execution
Librescoot Implementation Details¶
The Librescoot vehicle-service is a Go-based implementation with the following architecture:
Technical Implementation¶
- Language: Go (Golang)
- Concurrency: Uses goroutines for parallel I/O handling
- GPIO library:
github.com/warthog618/go-gpiocdevfor digital I/O - Redis library:
github.com/redis/go-redis/v9for Redis operations - System calls:
golang.org/x/sys/unixfor low-level hardware access - Binary size: Optimized for ARM Cortex-A7 architecture
- Cross-compilation: Supports AMD64 for development/testing
Key Features¶
- Enhanced LED control: Full 8-channel PWM LED system with adaptive and active/inactive modes
- Robust hibernation: Multi-stage hibernation state machine with safety checks
- Settings integration: Dynamic settings updates via Redis PUBSUB
- Update coordination: Handles DBC dashboard updates with power state management
- Button feedback: Immediate button event publishing for responsive UI
- State persistence: Saves and restores vehicle state across reboots
- Graceful shutdown: Proper cleanup of hardware resources and timers
LED Channel Mapping¶
Librescoot vehicle-service controls 8 PWM LED channels:
| Index | LED Name | Description | Mode |
|---|---|---|---|
| 0 | Headlight | Main front illumination | Adaptive |
| 1 | Front ring | Front accent lighting | Adaptive |
| 2 | Brake light | Rear brake indicator | Adaptive |
| 3 | Blinker front left | Left front turn signal | Active/Inactive |
| 4 | Blinker front right | Right front turn signal | Active/Inactive |
| 5 | Number plates | License plate illumination | Adaptive |
| 6 | Blinker rear left | Left rear turn signal | Active/Inactive |
| 7 | Blinker rear right | Right rear turn signal | Active/Inactive |
LED Channel Modes¶
-
Adaptive Mode (channels 0,1,2,5): When enabled, the channel adapts fade playback by finding the first duty-cycle value in the fade that is nearest to the current duty-cycle, then starting the fade from that point. This prevents abrupt brightness jumps during transitions between different LED states. Non-blinker channels use adaptive mode for smooth transitions.
-
Active/Inactive Mode (channels 3,4,6,7): Controls whether fade values are actually output to the LED. When active, fade values are set as the channel's duty-cycle normally. When inactive, the output is forced to 0% regardless of the fade being played. Blinker channels rely on precise active/inactive control for their flashing patterns and do NOT use adaptive mode.
Kernel module: The PWM LED system is implemented via the imx_pwm_led kernel module, which provides /dev/pwm_led* character devices for each channel. The service reloads this module on startup to ensure clean state.
See i.MX PWM LED kernel module documentation for detailed mode specifications and ioctl interface.
Special Features¶
DBC Update Support: During DBC (Dashboard Controller) firmware updates:
- Dashboard power is kept on regardless of vehicle state
- Power state changes are deferred until update completes
- Dashboard power can be cycled remotely via
scooter:updatecommand - Service tracks update status via
otahashstatus:dbcfield
Force Standby: Three keycard taps while holding brake lever triggers immediate transition to standby without handlebar lock. Useful for:
- Emergency situations
- Service/warehouse operations
- Forcing standby when handlebar lock is non-functional
Settings Integration: The service responds to settings changes via PUBSUB:
scooter.brake-hibernation: "enabled" or "disabled"- Changes take effect immediately
- Active hibernation sequences are cancelled when disabled
Architecture¶
Package structure:
cmd/vehicle-service/- Main entry pointinternal/core/- Core system logic and state machineinternal/hardware/- Hardware I/O abstraction (GPIO, LED)internal/messaging/- Redis communication layerinternal/types/- Shared type definitionsinternal/logger/- Leveled logging implementation
Concurrency model:
- Main goroutine handles system coordination
- Separate goroutines for:
- Redis PUBSUB listener
- 8 Redis BRPOP command listeners (one per list)
- Blinker timer (when active)
- Various hardware timers (hibernation, shutdown, handlebar lock)
- Thread-safe state access via sync.RWMutex
Error handling:
- Hardware errors are logged but don't crash the service
- Redis disconnection causes service exit (systemd will restart)
- Invalid commands are logged and rejected
- State transition errors are logged and may be retried
Building¶
# Build for ARM Cortex-A7 (default target)
make build
# Build for AMD64 (development/testing)
make build-amd64
# Output: bin/vehicle-service
Compatibility¶
Librescoot vehicle-service maintains Redis protocol compatibility:
- Same hash fields and structure as original implementation
- Same command interface (LPUSH to command lists)
- Same PUBSUB channel usage
- Compatible with any dashboard that follows the Librescoot Redis protocol
- No changes required to other services
Integration Points¶
Librescoot vehicle-service integrates with:
- Redis - Central message bus (required)
- Dashboard service - Waits for ready signal before allowing drive
- Settings service - Reads persistent behavior settings
- Update service - Coordinates OTA firmware updates
- Power manager - Sends hibernation commands
- Keycard service - Receives authentication events
- UI/HMI - Publishes button events for immediate feedback
Related Documentation¶
- Vehicle States - Complete state machine documentation
- Redis Operations - Redis protocol and data structures
- Librescoot Services Overview - All Librescoot services
- i.MX PWM LED kernel module - LED hardware interface