Skip to content

Extract shared headers and migrate all variants to common/#5

Merged
f1yaw4y merged 10 commits into
f1yaw4y:mainfrom
dougborg:pr/03-common-headers
Mar 8, 2026
Merged

Extract shared headers and migrate all variants to common/#5
f1yaw4y merged 10 commits into
f1yaw4y:mainfrom
dougborg:pr/03-common-headers

Conversation

@dougborg

@dougborg dougborg commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

Stack order: 3/8 — merge after #4 (PR2: build and test)

Summary

  • Extract shared headers (DetectorTypes, Detectors, DeviceSignatures, EventBus, TelemetryReporter, ThreatAnalyzer) into common/ directory
  • Migrate all 5 remaining variants to use shared headers via -I common:
    • M5Stack FIRE, Mini12864, 128x32 OLED, 128x32 Portable, Flipper Zero
  • Remove duplicate header copies from each variant's src/ directory

Test plan

  • Run make test to verify host-side tests still pass
  • Compile each variant to verify include paths resolve correctly
  • Verify no duplicate headers remain in variant src/ directories

🤖 Generated with Claude Code

dougborg and others added 10 commits February 2, 2026 00:48
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Provides per-variant build/upload/flash/monitor targets for all 6
hardware variants, plus LittleFS data upload for variants with audio
assets. Uses GNU Make define/eval/call to generate targets from a
single template.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests compile natively with clang++/g++ — no ESP32 hardware needed.
Covers detectors, device tracker state machine, and threat analyzer
scoring pipeline (37 cases, 126 assertions) targeting the M5Stick
variant's pure-logic headers.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move DetectorTypes.h, Detectors.h, DeviceSignatures.h, EventBus.h,
ThreatAnalyzer.h, and TelemetryReporter.h from the M5Stick variant's
src/ into a new top-level common/ directory. Update Makefile to pass
-I common via build.extra_flags for all variants, update test includes,
and fix the M5Stick FQBN (m5stick_c_plus2 -> m5stack_stickc_plus2).

Merge AudioEvent (from M5Fire's EventBus.h) into the shared header so
other variants can adopt it when migrated.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Delete local copies of EventBus.h, DeviceSignatures.h, ThreatAnalyzer.h,
and TelemetryReporter.h from m5fire/src/. Replace legacy ThreatAnalyzer
(simple boolean matching) and TelemetryReporter (DynamicJsonDocument with
nested objects) implementations with the shared detector-based system.

Add ISR-safe deferred event processing with portMUX spinlocks for WiFi,
BLE, and threat events. Add ThreatAnalyzer::tick() heartbeat in loop()
and shouldAlert gate on triggerAlert().

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Delete local copies of EventBus.h, DeviceSignatures.h, ThreatAnalyzer.h,
and TelemetryReporter.h from mini12864/src/. Replace legacy ThreatAnalyzer
and TelemetryReporter implementations with shared detector-based system.

Add ISR-safe deferred event processing with portMUX spinlocks. Move
display notifications (Mini12864DisplayNotifyWifiFrame, ShowAlert) and
audio playback to the main loop's deferred handlers. Add tick() heartbeat
and shouldAlert gate.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Also fix Makefile build.extra_flags override that was clobbering
ESP32 core defines (-DESP32=ESP32 etc). Use build.defines instead,
which is included within build.extra_flags and starts empty.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move variant-specific src/ files into sketch directory to follow
Arduino convention. Keep Flipper's own TelemetryReporter (line-based
protocol for Flipper app). Fix radioType null-check for char array.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 2, 2026 08:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR consolidates duplicate code by extracting shared headers (DetectorTypes, Detectors, DeviceSignatures, EventBus, TelemetryReporter, ThreatAnalyzer) into a common/ directory, migrating all 5 remaining variants to use them via -I common. It also introduces a Makefile-based build system, comprehensive unit tests with doctest, and CLAUDE.md project documentation.

Changes:

  • Extracted 6 shared headers into common/ directory with unified detector system, device tracking, and telemetry reporting
  • Added Makefile with arduino-cli integration supporting all 6 variants, LittleFS flashing, and host-side unit tests
  • Created test infrastructure with 322 unit tests covering detectors, device tracking, and threat analysis logic
  • Updated all variant .ino files to use shared headers and implement deferred event processing for thread safety
  • Added CLAUDE.md documentation describing architecture, build system, and development patterns

Reviewed changes

Copilot reviewed 43 out of 47 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
common/DetectorTypes.h Defines detector result structures, flags, device states, and tracking constants
common/Detectors.h Implements WiFi and BLE detector functions with weighted scoring and RSSI modifiers
common/DeviceSignatures.h Contains MAC OUI prefix list for known surveillance devices
common/EventBus.h Header-only event bus with updated ThreatEvent structure (char arrays instead of const char*)
common/ThreatAnalyzer.h Unified threat analyzer with device tracker, detector registry, and heartbeat logic
common/TelemetryReporter.h Standardized JSON telemetry output with detector weight reporting
Makefile Arduino-cli build system with variant support, LittleFS upload, and host test targets
CLAUDE.md Project documentation covering architecture, build system, and thread safety patterns
test/*.cpp 322 unit tests covering detectors, device tracking, and threat analysis
test/mocks/Arduino.h Mock Arduino.h for host-side testing with minimal dependencies
test/eventbus_impl.cpp EventBus implementation for test linking with mock millis()
m5stack/flocksquawk_m5stick/*.ino Migrated to common headers, added thread-safe deferred event processing
m5stack/flocksquawk_m5fire/*.ino Migrated to common headers with deferred processing and heartbeat support
Mini12864/flocksquawk_mini12864/*.ino Migrated to common headers with FreeRTOS critical sections
128x32_OLED/flocksquawk_128x32/*.ino Migrated to common headers with shouldAlert flag handling
128x32_OLED/flocksquawk_128x32_portable/*.ino Migrated to common headers with buzzer heartbeat beeps
flipper-zero/.../flocksquawk-flipper/*.ino Migrated to common headers, fixed radioType null check
*/src/RadioScanner.h Updated CHANNEL_SWITCH_MS to 1000ms and made currentWifiChannel volatile (m5stick)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread CLAUDE.md
Comment on lines +11 to +81
This is a pure **Arduino IDE** project (no CMake, PlatformIO, or Makefile). Each hardware variant is a self-contained Arduino sketch that opens directly in the IDE.

**Critical constraint:** ESP32 board package must be **version 3.0.7 or older** (newer versions cause IRAM overflow).

### Building with arduino-cli

```bash
# Install ESP32 core
arduino-cli core install esp32:esp32@3.0.7

# Install required libraries
arduino-cli lib install ArduinoJson NimBLE-Arduino M5Unified

# Compile a variant (example: M5StickC Plus2)
arduino-cli compile --fqbn esp32:esp32:m5stick_c_plus2 m5stack/flocksquawk_m5stick/

# Upload
arduino-cli upload --fqbn esp32:esp32:m5stick_c_plus2 --port /dev/ttyUSB0 m5stack/flocksquawk_m5stick/

# Serial monitor (115200 baud for JSON telemetry)
arduino-cli monitor --port /dev/ttyUSB0 --config baudrate=115200
```

Board FQBNs vary per variant — check each variant's README for exact board settings.

## Hardware Variants

Six self-contained variants, each in its own directory with a dedicated README:

| Variant | Path | Display | Audio |
|---|---|---|---|
| M5StickC Plus2 | `m5stack/flocksquawk_m5stick/` | Built-in TFT 135x240 | Buzzer tones |
| M5Stack FIRE | `m5stack/flocksquawk_m5fire/` | Built-in TFT 320x240 | Built-in speaker |
| Mini12864 | `Mini12864/flocksquawk_mini12864/` | ST7567 LCD 128x64 | I2S (MAX98357A) |
| 128x32 OLED | `128x32_OLED/flocksquawk_128x32/` | SSD1306/SH1106 I2C | I2S (MAX98357A) |
| 128x32 Portable | `128x32_OLED/flocksquawk_128x32_portable/` | SSD1306/SH1106 I2C | GPIO buzzer |
| Flipper Zero | `flipper-zero/dev-board-firmware/` | None (UART only) | None |

Variants do **not** share source files — each has its own copy of the core headers in `src/`. Changes to shared logic (EventBus, ThreatAnalyzer, etc.) must be applied to each variant individually.

## Architecture

Data flows through a publish-subscribe pipeline:

```
RadioScannerManager (WiFi promiscuous + BLE scan)
→ EventBus (WiFiFrameEvent / BluetoothDeviceEvent)
→ ThreatAnalyzer (signature matching → ThreatEvent)
→ TelemetryReporter (JSON over Serial)
→ Display (variant-specific UI)
→ SoundEngine (alerts)
```

### Core subsystems (in each variant's `src/`)

- **EventBus.h** — Header-only static publish/subscribe. Event types: `WifiFrameCaptured`, `BluetoothDeviceFound`, `ThreatIdentified`, `SystemReady`.
- **RadioScanner.h** — WiFi promiscuous mode (channels 1-13, 1s hop interval) and BLE scanning via NimBLE (5s interval, 1s duration). Uses FreeRTOS portMUX spinlocks for thread safety between ISR callbacks and main loop.
- **ThreatAnalyzer.h** — Compares observations against `DeviceSignatures.h`. Tracks up to 32 devices with LRU eviction (states: NEW_DETECT → IN_RANGE → DEPARTED). Alert threshold: 65% certainty.
- **DeviceSignatures.h** — Static arrays of known SSIDs, MAC OUI prefixes, BLE device names, and service UUIDs.
- **TelemetryReporter.h** — Serializes detections as JSON via ArduinoJson (StaticJsonDocument<512>).
- **SoundEngine.h** — I2S WAV playback from LittleFS (Mini12864/128x32) or M5.Speaker tone generation (M5Stack variants).

### Pluggable detector system (M5Stick variant only — newest architecture)

The M5Stick variant introduces a function-pointer-based detector registry:

- **DetectorTypes.h** — Defines `DetectorResult` (matched/weight/name) and `WiFiDetectorEntry`/`BLEDetectorEntry` structs.
- **Detectors.h** — Registers detector functions. WiFi: `detectSsidFormat` (weight 75), `detectSsidKeyword` (45), `detectWifiMacOui` (20). BLE: `detectBleName` (55), `detectRavenCustomUuid` (80), `detectRavenStdUuid` (10), `detectBleMacOui` (20).
- Weighted scoring with subsumption (higher-confidence detectors override lower ones) and RSSI-based modifiers.

This detector pattern is the intended direction for all variants.

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLAUDE.md documentation is now inaccurate after this PR's changes. Line 11 states "This is a pure Arduino IDE project (no CMake, PlatformIO, or Makefile)" but this PR adds a comprehensive Makefile build system.

Line 49 states "Variants do not share source files — each has its own copy of the core headers in src/" but this PR introduces shared headers in the common/ directory that all variants now use via -I common.

Line 73 states "The M5Stick variant introduces a function-pointer-based detector registry" but this PR migrates ALL variants to use the common detector system from common/ThreatAnalyzer.h.

These sections should be updated to reflect that:

  1. A Makefile is now available for building all variants
  2. Shared headers now exist in common/ (EventBus, ThreatAnalyzer, TelemetryReporter, Detectors, DetectorTypes, DeviceSignatures)
  3. All variants now use the pluggable detector system, not just M5Stick

Copilot uses AI. Check for mistakes.
Comment thread CLAUDE.md
WiFi promiscuous callbacks and BLE scan callbacks run on different cores/tasks than `loop()`. All variants use the same pattern:

- `portMUX_TYPE` spinlocks guard shared volatile state in ISR callbacks
- `taskENTER_CRITICAL` / `taskEXIT_CRITICAL` for atomic reads/writes

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line 88 mentions "taskENTER_CRITICAL / taskEXIT_CRITICAL" but the actual code uses "portENTER_CRITICAL / portEXIT_CRITICAL" macros. These are different FreeRTOS APIs - portENTER_CRITICAL/portEXIT_CRITICAL are for synchronizing between tasks and ISRs (which is correct for this use case), while taskENTER_CRITICAL/taskEXIT_CRITICAL are for task-only synchronization. The documentation should be corrected to match the actual implementation.

Suggested change
- `taskENTER_CRITICAL` / `taskEXIT_CRITICAL` for atomic reads/writes
- `portENTER_CRITICAL` / `portEXIT_CRITICAL` for atomic reads/writes

Copilot uses AI. Check for mistakes.
threatEngine.analyzeBluetoothDevice(bleCopy);
}

threatEngine.tick(now);

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent handling of the tick() return value across variants. The ThreatAnalyzer::tick() method returns true when a heartbeat beep should be emitted, but this return value is only checked in the m5stick and m5fire variants (lines 623-625 and 1158-1160 respectively).

The 128x32 OLED variant at line 550, Mini12864 variant at line 421, and Flipper Zero variant at line 463 all ignore the return value. While this may be intentional (e.g., Flipper has no audio), the 128x32_portable variant does have buzzer capability and should probably check the return value to emit heartbeat beeps.

Consider either using the return value consistently across all variants that have audio capability, or documenting why certain variants intentionally ignore it.

Suggested change
threatEngine.tick(now);
bool heartbeat = threatEngine.tick(now);
if (heartbeat) {
AudioEvent audioEvent;
audioEvent.soundFile = "/heartbeat.wav";
EventBus::publishAudioRequest(audioEvent);
}

Copilot uses AI. Check for mistakes.
Comment thread Makefile
Comment on lines +139 to +147
DOCTEST_URL := https://raw.githubusercontent.com/doctest/doctest/v2.4.11/doctest/doctest.h

.PHONY: test test-verbose fetch-doctest

fetch-doctest: test/doctest.h

test/doctest.h:
@echo "Fetching doctest.h …"
curl -sL -o $@ $(DOCTEST_URL)

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetch-doctest target downloads and executes a third-party header from $(DOCTEST_URL) at build/test time without any integrity verification and only pins it to a mutable Git tag. If an attacker compromises the upstream repository or the tag, they could inject arbitrary C++ code into your test binary and run it in CI or developer environments with access to secrets. To reduce this supply-chain risk, vendor a known-good copy of doctest.h into the repo or pin to an immutable identifier (e.g., a specific commit hash) and verify it via checksum or signature before use.

Copilot uses AI. Check for mistakes.
@f1yaw4y f1yaw4y merged commit 856c28f into f1yaw4y:main Mar 8, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants