Non-Key Mapping

How non-keyboard inputs (mouse, scroll, joystick, gyroscope) flow through the system.

Overview

Unlike keys, non-key inputs bypass the keymap entirely. They use direct HID field mapping instead of index→keymap lookup.

Physical Sensor → Driver → InputAction → ESP-NOW → Accumulate/Process → HID Report Field
     [Cat]         [Cat]      [Cat]       [Wire]        [Tower]            [Host]

Key Differences from Keys:

  • Keys: Binary state (press/release), on-change only, RMK keymap lookup
  • Non-keys: Continuous stream, polling-based, direct HID field population

Input Types

InputSlot/VariantData TypeInputActionCat Poll Rate
Mouse SensorBottom (bm)Delta X/Y (i8, i8)MouseMotion { delta }~500Hz
Scroll WheelFinger (fkw)Delta (i8)ScrollIncrement { delta }On-detent
JoystickThumb (tkj)Position X/Y (i16, i16)JoystickPosition { position }~100Hz
GyroscopeBottom (bg)Angular velocity (i16, i16, i16)GyroMotion { angular_velocity }~100Hz

Processing Model

Keys vs Non-Keys

AspectKeysNon-Keys
StateBinary (press/release)Continuous stream
LookupIndex → Keymap → KeycodeDirect HID field
RateOn-change onlyPolling-based
AccumulationN/ADeltas sum between USB polls
Layer interactionFull layer supportLimited/None
Wire formatKeyPress/Release { row, col }MouseMotion/ScrollIncrement/etc.

Cat-Side vs Tower-Side

Cat (LYNXcat):

  • Sensor polling at hardware rate
  • Driver-level conversion (raw → digital)
  • InputAction generation
  • ESP-NOW transmission

Tower (LYNXtower):

  • Channel routing by InputAction type
  • Accumulation (mouse/scroll deltas)
  • Scaling/sensitivity adjustment
  • Mode switching (joystick)
  • HID report field population

Data Flow: Mouse Sensor

ADNS-5050 SPI → MouseDriver → MouseMotion{delta:(i8,i8)} → ESP-NOW → Tower Accumulator → Mouse HID Report
   [Cat]           [Cat]              [Cat]                 [Wire]         [Tower]           [Host]

Cat side:

  1. SPI read motion registers at ~500Hz
  2. Extract dx, dy as i8 (-127 to +127)
  3. Generate InputAction::MouseMotion { delta: (dx, dy) }
  4. Create InputFrame with slot=Bottom, sender=RightCat
  5. Transmit via ESP-NOW

Tower side:

  1. Receive InputFrame, route to mouse channel
  2. Accumulate deltas in i16 (prevent overflow)
  3. Apply sensitivity scaling (future: DPI setting)
  4. On USB poll (125Hz), send HID mouse report
  5. Reset accumulator

USB HID Mouse Report Fields:

struct MouseReport {
    buttons: u8,      // Bits 0-7 for mouse buttons
    x: i8,            // Horizontal movement (-127 to +127)
    y: i8,            // Vertical movement (-127 to +127)
    wheel: i8,        // Scroll wheel (-127 to +127)
    pan: i8,          // Horizontal scroll (optional)
}

Mouse sensor populates x and y fields.

Button State (Cross-Pipeline Coordination): Mouse buttons use keyboard keycodes (MouseBtn1-5) mapped in keymap. VirtualMatrix (keyboard pipeline) detects these by querying keymap on each key event. On detection, updates MOUSE_BUTTON_STATE: AtomicU8 shared with pointer_processor. PointerProcessor reads this atomic at 125Hz when building reports. This ensures button state and motion appear in same USB report, enabling drag operations. Button state persists until key release (unlike motion which resets each report).

Data Flow: Scroll Wheel

Encoder Rotation → ScrollWheelDriver → ScrollIncrement{delta:i8} → ESP-NOW → Tower Accumulator → Mouse HID Wheel
     [Cat]              [Cat]                 [Cat]                 [Wire]        [Tower]          [Host]

Cat side:

  1. GPIO encoder detects rotation direction
  2. On each detent (physical click), emit ScrollIncrement { delta: ±1 }
  3. Create InputFrame with slot=Finger, sender=RightCat
  4. Transmit via ESP-NOW

Tower side:

  1. Receive InputFrame, route to pointer channel
  2. Accumulate scroll delta with motion deltas
  3. On USB poll (125Hz), send HID mouse report
  4. Reset accumulator

Notes:

  • Scroll wheel shares pointer channel with MouseMotion
  • Populates wheel field in Mouse HID Report
  • Direction: positive = scroll up, negative = scroll down
  • Right finger variant fkw replaces i5/i6/i7 key columns with encoder
  • Scroll-only setups work correctly (scroll wakes processor from idle)

Data Flow: Joystick

ADC X/Y → JoystickDriver → JoystickPosition{position:(i16,i16)} → ESP-NOW → Mode Handler → HID Report
 [Cat]         [Cat]                   [Cat]                       [Wire]      [Tower]      [Host]

Cat side:

  1. ADC reads X/Y axes at ~100Hz
  2. Convert to centered range: 0 = center, ±32767 = max deflection
  3. Generate InputAction::JoystickPosition { position: (x, y) }
  4. Create InputFrame with slot=Thumb, sender=LeftCat/RightCat
  5. Transmit via ESP-NOW

Tower side (mode-dependent):

Joystick can operate in three modes:

ModeProcessingHID ReportUse Case
MousePosition → velocity → cumulative motionMouse Report (x, y)Cursor control
ScrollPosition → scroll speedMouse Report (wheel)Fast document navigation
KeyThreshold detection (4 directions)Keyboard Report (arrow keys)Menu navigation

Mouse Mode (default):

  1. Convert stick position to velocity (distance from center)
  2. Apply dead zone (ignore small movements)
  3. Generate motion deltas based on velocity
  4. Accumulate into mouse x/y
  5. Send Mouse HID Report

Scroll Mode:

  1. Y-axis position → scroll speed
  2. Faster deflection = more scroll per frame
  3. Send Mouse HID Report (wheel field)

Key Mode:

  1. Apply threshold to each axis (e.g., ±50%)
  2. Generate discrete key presses for 4 directions
  3. Inject KeyPress/KeyRelease as virtual events
  4. Send Keyboard HID Report

Configuration (conceptual):

  • Dead zone threshold (e.g., 10% center)
  • Sensitivity scaling
  • Mode selection (compile-time or runtime)

Data Flow: Gyroscope

IMU I2C → GyroDriver → GyroMotion{angular_velocity:(i16,i16,i16)} → ESP-NOW → Mode Handler → HID Report
 [Cat]       [Cat]                    [Cat]                          [Wire]      [Tower]       [Host]

Cat side:

  1. I2C read angular velocity (pitch, yaw, roll) at ~100Hz
  2. Units: degrees/second scaled to i16
  3. Generate InputAction::GyroMotion { angular_velocity: (pitch, yaw, roll) }
  4. Create InputFrame with slot=Bottom, sender=LeftCat
  5. Transmit via ESP-NOW

Tower side:

  1. Receive InputFrame, route to sensor channel
  2. Apply mode logic (implementation-dependent):
    • Tilt mode: Pitch/roll → mouse X/Y (continuous motion)
    • Gesture mode: Sharp rotation → key injection (flick to switch layers)
    • Camera mode: Yaw/pitch → mouse X/Y (FPS games)
  3. Generate appropriate HID report

Coordinate System:

  • Pitch: rotation around X-axis (tilt forward/back)
  • Yaw: rotation around Y-axis (turn left/right)
  • Roll: rotation around Z-axis (tilt sideways)

Current Status: MVP logs only, no HID generation yet.

USB HID Reports

Keyboard Report (Keys Only)

struct KeyboardReport {
    modifiers: u8,        // Ctrl, Shift, Alt, GUI bits
    reserved: u8,         // Always 0
    keycodes: [u8; 6],    // 6-key rollover (NKRO optional)
}
  • Populated by RMK keymap lookup
  • Non-key inputs do NOT use this report (except joystick key mode)

Mouse Report (Non-Keys)

struct MouseReport {
    buttons: u8,      // Mouse button bits
    x: i8,            // Horizontal motion (-127 to +127)
    y: i8,            // Vertical motion (-127 to +127)
    wheel: i8,        // Scroll wheel (-127 to +127)
    pan: i8,          // Horizontal scroll (optional)
}
  • x, y: Mouse sensor, joystick (mouse mode), gyro (tilt mode)
  • wheel: Scroll wheel, joystick (scroll mode)
  • buttons: Mouse button keycodes from keymap (cross-pipeline coordination via atomic)

Example Trace: Mouse Motion

Scenario: ADNS-5050 detects rightward movement on RightCat.

  1. Cat: SPI read returns dx=+5, dy=-3 (5 counts right, 3 counts up)
  2. Cat: Generate InputAction::MouseMotion { delta: (5, -3) }
  3. Cat: Create InputFrame with sender=RightCat, slot=Bottom, sequence=1234
  4. Wire: Serialize with postcard (~12 bytes), transmit via ESP-NOW (~0.5ms)
  5. Tower: Receive frame, deserialize, route to mouse channel
  6. Tower: Accumulate: dx_total += 5, dy_total += -3
  7. Tower: Wait for USB poll interval (8ms at 125Hz)
  8. Tower: Additional frames arrive, accumulate dx=+3, dy=-2
  9. Tower: USB poll triggers: dx_total=8, dy_total=-5
  10. Tower: Apply sensitivity scaling (e.g., 1.5x): dx=12, dy=-7
  11. Tower: Generate Mouse HID Report with x=12, y=-7, wheel=0
  12. Tower: Reset accumulator to zero
  13. Host: OS receives report, moves cursor 12px right, 7px up

Coordinate Systems

LayerMouseScrollJoystickGyroscope
PhysicalSensor orientationRotation CW/CCWStick deflectionBoard tilt
ElectricalSPI motion registersEncoder pulsesADC voltage (0-3.3V)I2C registers
Digital (Cat)MouseMotion{delta:(i8,i8)}ScrollIncrement{delta:i8}JoystickPosition{position:(i16,i16)}GyroMotion{angular_velocity:(i16,i16,i16)}
WireInputFrame + FrameHeaderInputFrame + FrameHeaderInputFrame + FrameHeaderInputFrame + FrameHeader
Digital (Tower)Accumulated i16, scaledAccumulated i8Mode-processedMode-processed
HostHID mouse x,y (-127 to +127)HID wheel (-127 to +127)HID mouse/keys (mode-dependent)HID mouse/keys (mode-dependent)

Key Observations:

  • Cat uses fixed-width integer types matched to sensor ranges
  • Wire protocol preserves exact values (no loss)
  • Tower performs accumulation in wider types (i8→i16 for mouse)
  • Host sees final 8-bit HID values (clamped if needed)

Timing & Accumulation

Mouse Sensor

Cat Poll Rate: ~500Hz (2ms intervals)

  • High-frequency polling for responsive cursor
  • Each poll generates InputAction (even if dx=0, dy=0)

Tower Accumulation Window: 8ms (125Hz USB poll)

  • Up to 4 motion frames can accumulate per report
  • Example: 4 frames of (dx=2, dy=1) → HID report (x=8, y=4)

Latency Budget:

StageTimeCumulative
Sensor poll2ms2ms
InputAction gen<0.1ms2.1ms
ESP-NOW0.5ms2.6ms
Tower routing0.5ms3.1ms
USB poll (worst)8ms11.1ms
USB poll (best)1ms4.1ms

Observed: 4-11ms sensor-to-cursor.

Scroll Wheel

Cat Event Rate: On-detent only

  • Physical encoder has ~24 detents per rotation
  • Only generates InputAction when detent clicks

Tower Accumulation: Same 8ms window

  • Multiple rapid scrolls within 8ms sum together
  • Fast scroll: 3 detents in 8ms → HID wheel=3

Joystick

Cat Poll Rate: ~100Hz (10ms intervals)

  • Lower rate acceptable for analog control
  • Position reported continuously (even if not moving)

Tower Processing: Mode-dependent

  • Mouse mode: generates motion every USB poll
  • Scroll mode: generates scroll every USB poll
  • Key mode: generates KeyPress on threshold cross

Gyroscope

Cat Poll Rate: ~100Hz (10ms intervals)

  • IMU reports angular velocity
  • Only transmit if velocity exceeds threshold (reduce wireless traffic)

Tower Processing: MVP logs only

  • Future: Apply sensitivity, dead zone, mode logic

Configuration Parameters

Current implementation is hardcoded. Future enhancements:

ParameterInputPurposeDefault
DPI/SensitivityMouseScale motion magnitude1.0x
Dead zoneJoystick, GyroIgnore noise near zero10%
Scroll directionScrollInvert natural/reverseNormal
Joystick modeJoystickMouse/Scroll/KeyMouse
Gyro axis mapGyroWhich axes control X/YYaw→X, Pitch→Y
Poll rateAllCat-side sampling freqPer-sensor

Configuration Storage:

  • Compile-time: Cargo features (current)
  • Runtime: Vial GUI settings (future)

Relationship to RMK

RMK is keyboard-focused. Non-key inputs bypass most RMK logic:

RMK Handles:

  • Keyboard HID report generation
  • Keymap lookups (keys only)
  • Layer switching (triggered by keys)
  • Macros (triggered by keys)

Tower Custom Handlers:

  • Pointer processing (motion + scroll accumulation) (mouse_processor.rs)
  • Sensor processing (sensor_handler.rs)

Hybrid Case: Joystick Key Mode

  • Joystick generates virtual KeyPress/KeyRelease
  • Injected into RMK's event queue
  • RMK processes like normal keys (supports layers, macros)
  • Key mapping (complementary): firmware/docs/key-mapping.md
  • Event pipeline (complete flow): firmware/docs/event-pipeline.md
  • InputAction definitions: firmware/common/src/input_actions.rs
  • Pointer processor implementation: firmware/tower/src/pipelines/mouse_processor.rs
  • Sensor handler implementation: firmware/tower/src/pipelines/sensor_handler.rs
  • Cat event generation: firmware/cat/src/event_gen.rs