Skip to content

Add a third ESP-NOW peer: keyboard input node #20

Description

@ap0ught

Feature

Add a true third ESP-NOW peer — a separate ESP32 with an attached physical keyboard — that joins the existing encrypted two-CYD mesh as a fully authenticated, immutable-input node.

Architecture: third ESP-NOW peer (not a USB-serial bridge)

  • The two CYDs communicate over encrypted ESP-NOW, not TCP/IP Wi-Fi (esp_now.h, esp_now_send, esp_now_set_pmk, encrypted peers in startEspNow()). A PC Wi-Fi card cannot join an ESP-NOW conversation, so the keyboard is its own ESP32 node on the same encrypted link the CYDs already use.
  • A dedicated ESP32 + keyboard participates in the existing PMK/LMK encryption, peer binding, heartbeat, and replay infrastructure as a real wireless peer.
  • ESP-NOW between the two game CYDs is otherwise unchanged.

Hardware

  • A third ESP32 (bare ESP32-DevKitC, another CYD, or similar) with Wi-Fi.
  • A keyboard attached to that ESP32. The keyboard interface (USB-HID via USB-host, BLE-HID, PS/2, or an I2C keypad such as M5Stack CardKB) is an open implementation decision for the first cut.

Protocol changes

Today the peer model is strictly two-device and singular:

  • kKnownBoards[] holds exactly two entries.
  • expectedPeerBoardId / expectedPeerAddress are single values.
  • gamePeerBoardId is singular; configureExpectedPeer() picks "the other" board from the 2-entry table.
  • isExpectedPeer() matches that one peer.
  • localIsHost() and turnBoardId logic assume two participants.

Required changes:

  • Extend kKnownBoards[] to carry >=3 entries (two game CYDs + one keyboard peer).
  • Replace the singular expectedPeer* / gamePeerBoardId model with a peer set that separates the game peer from the optional keyboard peer.
  • isExpectedPeer() accepts the game peer or the keyboard peer.
  • esp_now_add_peer() registers both the game peer and the keyboard peer with the existing LMK.
  • Send path stays addressed (no broadcast): game-state packets go to the game peer; keyboard input/ACK/heartbeat traffic is routed to/from the keyboard peer.
  • Add MessageType::KeyboardInput. PacketHeader is unchanged — senderId already disambiguates the source.
  • Add a KeyboardInputPacket (header + scancode + modifier bitmask + repeat flag + reserved) with a matching static_assert on wire size, consistent with the existing offsetof/size pinning in protocol.h.
  • Increment the protocol version so v5 firmware cleanly rejects the new packet type.

Immutable-input model (non-negotiable)

  • A keyboard packet must never move a tile, submit a guess, advance turn ownership, or mutate authoritative game state directly.
  • The two game CYDs remain the sole authorities over turn ownership, scoring, and EXIT.
  • The receiving CYD validates keyboard packets: sender is the bound keyboard peer, valid session/epoch, not a replay, scancode in range.
  • Validated scancodes are queued (lock-free ring buffer) and consumed by the active game's input handler exactly like a touch event — input, not authority.

Active-game routing

Backward compatibility (zero regression)

  • With the keyboard peer absent or powered off, the two CYDs pair and play exactly as today: peer binding falls back to game-peer-only, and heartbeat/ACK logic skips the keyboard.
  • Keyboard absence is logged over serial and never blocks gameplay.

Acceptance criteria

  • kKnownBoards[] holds >=3 entries; a peer set replaces the singular expectedPeer* / gamePeerBoardId fields.
  • MessageType::KeyboardInput wire format defined, size-asserted, and rejected by non-keyboard handlers.
  • The keyboard peer is cryptographically authenticated with the same PMK/LMK as the game peers.
  • Heartbeat, session tracking, replay rejection, and ACK/retry cover the keyboard peer.
  • Validated scancodes flow keyboard ESP32 -> game CYD -> active game input handler without mutating authoritative state.
  • Both game CYDs behave identically when the keyboard peer is absent.
  • Pure logic passes strict host compilation with -std=c++17 -Wall -Wextra -Werror.
  • Encrypted PlatformIO firmware builds for all three devices.
  • All three devices flashed and physically verified: keystrokes appear on the active game display, touch input still works, and keyboard absence causes no regression.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions