Light Logo Dark Logo

CATMOUSE

OPT-OUT

Brussels 2026

Resilient Asymmetric Channel Hopping Protocol

Build v1.1.0 Auto-Discovery Dynamic Stamina

00. Download - Install

ESP32 Hardware Compatibility:

  • 100% Variant Agnostic: Uses low-level ESP-IDF `soc` macro checks to safely lock and manipulate the Physical Layer (PHY) across the ESP32, ESP32-S2, ESP32-S3, ESP32-C3, and ESP32-C6 without crashing native hardware controllers.

Get the latest release directly from GitHub:

Download Last Release

Install via the Arduino IDE Library Manager (Sketch > Include Library > Add .ZIP Library).

01. Architecture

CATMOUSE was conceived as an experimental game of wireless hide-and-seek, but results in an incredibly resilient, zero-configuration networking strategy that actively circumvents external radio jamming by utilizing the entirety of the 2.4Ghz physical spectrum dynamically.

The CAT (Hunter)

Boots in LISTENING mode, actively scanning all 13 channels until it catches a Mouse's Squeak. Once locked on, it initiates aggressive sweeping by firing targeted payloads bearing its gameID, leveraging hardware ACKs.

Stamina: A Cat's sweep rate slows down the longer it misses (saving power and minimizing airwave congestion). When it scores a Hit, it enters a 5ms interval "Frenzy," launching hyper-aggressive strikes before the Mouse flies to a new frequency.

The MOUSE (Hider)

A passive hider. It randomly selects a Wi-Fi channel and a hiding duration. If it receives a packet from a Cat bearing the correct gameID, it triggers a 'HIT' callback and immediately flees to a new random channel.

The Squeak: The Mouse occasionally unleashes a public broadcast Squeak on its active channel. Cats in LISTENING mode can catch this Squeak, seamlessly locking onto the Mouse's MAC address without hardcoding.

02. API Reference

bool beginAsCat(uint8_t gameID, uint8_t* targetMac = nullptr, unsigned long attackRateMs = 10)

Initializes the device as the Hunter on a specific gameID. If you leave targetMac as a nullptr, the Cat enters LISTENING mode and actively scans channels until it catches a Mouse Squeak to automatically determine the MAC target. Returns false if ESP-NOW initialization fails.

bool beginAsMouse(uint8_t gameID, unsigned long hideMinMs = 100, unsigned long hideMaxMs = 500, unsigned long squeakIntervalMs = 2000)

Initializes the device as the Target locking into a gameID. Configures the min/max window for autonomous channel hopping and the Squeak broadcast interval. Returns false if ESP-NOW initialization fails.

void end()

Tears down ESP-NOW, unregisters all callbacks, and resets the internal state. Call this to cleanly stop the protocol before re-initializing or shutting down.

void update()

The main timer engine. Analyzes state changes, updates stamina, triggers Squeaks, schedules transmits, and manages hops. Must run in loop().

uint32_t getHitsScored()

Returns the internal telemetry. If Cat: successful Hits tracked. If Mouse: successful escapes.

uint32_t getSweepsMissed()

Returns the number of channels the Cat has swept without securing an ACK. Useful for monitoring hunt efficiency.

03. Event Callbacks

void onHit(HitCallback cb)

Registers an external function to trigger when contact is made. If assigned to the Cat, it fires when the target is found. If assigned to the Mouse, it fires when it is caught. The callback brings the uint8_t channel int with it.

void onMiss(MissCallback cb)

Registers an external function to trigger when a payload fails to secure an ACK (meaning the target is not on the current channel). The callback brings the uint8_t channel int with it.

04. Implementation

Example: The Cat (Listener)

#include <CatMouse.h>

CatMouse cat;

// Define your Game ID. Cats will only hunt Mice with matching IDs.
const uint8_t gameID = 42;

void onCatHit(uint8_t channel) {
  Serial.printf("HIT! Found Mouse on Channel: %d | Hits Scored: %d\n", channel,
                cat.getHitsScored());
}

void onCatMiss(uint8_t channel) {
  Serial.printf("Miss... Sweeping Channel: %d | Sweeps: %d\n", channel,
                cat.getSweepsMissed());
}

void setup() {
  Serial.begin(115200);

  // Initialize as CAT using just the Game ID (no MAC address)
  // The Cat will boot in LISTENING mode, scanning channels until it hears a Squeak
  if (!cat.beginAsCat(gameID, nullptr, 10)) {
    Serial.println("Failed to initialize Cat! Check ESP-NOW.");
    while (true) delay(1000);
  }

  // Register the result callbacks
  cat.onHit(onCatHit);
  cat.onMiss(onCatMiss);

  Serial.println("Cat is scanning channels, listening for the first Mouse squeak...");
}

void loop() {
  // Calling update() triggers listening, and eventually sweeps & stamina logic
  cat.update();
}

Example: The Mouse (Hider)

#include <CatMouse.h>

CatMouse mouse;

// Define your Game ID. The Mouse will only respond to Cats with this ID.
const uint8_t gameID = 42;

// Runs when the Cat finds the Mouse on the current channel
void onMouseCaught(uint8_t channel) {
  Serial.printf("CAUGHT! The Cat found me on Channel: %d ! Fleeing...\n",
                channel);
}

void setup() {
  Serial.begin(115200);

  // Initialize as MOUSE with the Game ID.
  // It will hide on a random channel for anywhere between 100ms and 500ms
  // Squeaks every 2000ms (default) to let Cats discover it
  if (!mouse.beginAsMouse(gameID, 100, 500)) {
    Serial.println("Failed to initialize Mouse! Check ESP-NOW.");
    while (true) delay(1000);
  }

  // Register the callback for when the Cat scores a direct hit
  mouse.onHit(onMouseCaught);

  Serial.println("Mouse is hiding and occasionally squeaking...");
}

void loop() {
  // Calling update() automatically triggers hops and periodic Squeak broadcasts
  mouse.update();
}