Skip to content

v1.14.3 - DetectionProcessor Single Responsibility Principle Refactoring (2025-12-11)

What Changed?

This release applies the Single Responsibility Principle (SRP) to the DetectionProcessor class by splitting the monolithic process() method into focused helper methods. Each method now has a single, clear responsibility: detection input, sensor reading, timestamp tracking, response building, LED feedback, and cleanup. The refactoring improves code readability and maintainability while maintaining identical behavior and zero performance impact.


What's New

Main Feature: SRP-Compliant DetectionProcessor Architecture

What it does: Refactors DetectionProcessor::process() from a 100+ line monolithic method into a well-organized hierarchy of single-responsibility helper methods:

  • PHASE 1: read_detection() - GPIO detection input
  • PHASE 2: read_sensors() - Sensor orchestration
  • add_timestamp_data() - Timing field population (ENABLE_TIMESTAMP)
  • add_adcmv_data() - ADC millivolt conversion (ENABLE_ADCMV)
  • add_bme280_data() - Environmental sensor reading (ENABLE_BME280)
  • add_gnss_data() - GNSS position reading (ENABLE_GNSS)
  • PHASE 3: build_and_queue_event() - Response building and queueing
  • PHASE 4a: led_feedback() - LED feedback control
  • PHASE 4b: cleanup() - State reset

How to use it: No API changesβ€”usage remains identical:

void loop() {
  DetectionProcessor::process();  // Internally orchestrates all phases
  EventQueue::flush();
}

Architecture diagram:

process() orchestration:
β”œβ”€β”€ read_detection()
β”‚   └── return if !detected
β”œβ”€β”€ read_sensors()
β”‚   β”œβ”€β”€ add_timestamp_data()
β”‚   β”œβ”€β”€ add_adcmv_data()
β”‚   β”œβ”€β”€ add_bme280_data()
β”‚   └── add_gnss_data()
β”œβ”€β”€ build_and_queue_event()
β”œβ”€β”€ led_feedback()
β”œβ”€β”€ cleanup()
└── cosmic_detector_reset()

Benefits:

  • Single Responsibility: Each method has one clear purpose
  • Improved Readability: process() is now a 7-line high-level orchestrator
  • Easier Testing: Each phase can be tested independently
  • Cleaner Separation: Conditional compilation flags remain unaffected
  • Better Maintainability: Changes to one phase don't risk breaking others

Installation

Quick Start

# Get the release
git checkout v1.14.3

# Build all variants
task build              # Development build
task prod:build         # Production build
task next:build         # Next-generation (ENABLE_DEVICE_RESPONSE=1)

# Upload
task upload

# Check it works
task monitor

What's Different from the Last Version?

βœ… Added

  • Private helper methods (9 new private static methods):
  • read_detection(detection_result_t*) - GPIO detection reading
  • read_sensors(event_t*, const detection_result_t&) - Sensor orchestration
  • add_timestamp_data(event_t*) - Timestamp field population
  • add_adcmv_data(event_t*) - ADC millivolt conversion
  • add_bme280_data(event_t*) - Environmental sensor reading
  • add_gnss_data(event_t*) - GNSS position reading
  • build_and_queue_event(const event_t&) - Response queueing
  • led_feedback(const detection_result_t&) - LED feedback
  • cleanup() - State reset

πŸ”§ Changed

  • detection_processor.cpp refactored (196 β†’ 204 lines with added JSDoc)
  • Monolithic process() (100+ lines) β†’ high-level orchestrator (7 lines)
  • Sensor reading logic extracted to dedicated methods
  • Conditional field handling isolated in add_*() methods
  • Response building encapsulated in build_and_queue_event()

  • detection_processor.h expanded (204 β†’ 299 lines)

  • Added complete JSDoc for all 9 private helper methods
  • Documentation explains each method's purpose and phase
  • Full section for private helpers with clear organization

  • Include dependency added:

  • Added #include "event_queue.h" to detection_processor.h for event_t type

πŸ› Fixed

  • No bug fixes in this release (pure refactoring)

Architecture Improvements

Before (v1.14.2)

void DetectionProcessor::process(void) {
  cosmic_detection_t detection = cosmic_detector_read();

  if (detection.detected) {
    // ====== 100+ lines of mixed concerns ======
    // - Sensor reading (BME280, ADC, RTC, GNSS)
    // - Event building (hit_type, timestamps, etc.)
    // - Response creation
    // - Queueing logic
    // All interspersed with conditional compilation
  }

  cosmic_detector_reset();
}

After (v1.14.3)

void DetectionProcessor::process(void) {
  detection_result_t detection = {};

  // PHASE 1: Read detection
  if (!read_detection(&detection)) {
    cosmic_detector_reset();
    return;
  }

  // PHASE 2: Read sensors and build event
  event_t event = {};
  read_sensors(&event, detection);

  // PHASE 3: Build response and queue
  build_and_queue_event(event);

  // PHASE 4: LED feedback and cleanup
  led_feedback(detection);
  cleanup();

  cosmic_detector_reset();
}

Method Responsibility Map

Method Lines Responsibility Complexity
read_detection() 8 GPIO detection input Minimal
read_sensors() 17 Sensor reading orchestration Medium
add_timestamp_data() 8 Timing field population Minimal
add_adcmv_data() 6 ADC conversion Minimal
add_bme280_data() 8 Environmental reading Minimal
add_gnss_data() 12 GNSS position reading Minimal
build_and_queue_event() 12 Response queueing Minimal
led_feedback() 3 LED feedback Trivial
cleanup() 1 ADC pin reset Trivial
Total 75 9 single-purpose methods Much lower

Comparison: Old monolithic process() had ~100 lines with multiple interspersed concerns; new design has same logic split into 9 focused methods with clear separation.


Is It Safe to Upgrade?

Backward Compatible: βœ… Yes (100% compatible)

  • Zero behavior changes - All detection and sensor logic identical
  • No API changes - Public interface unchanged (init() and process() signatures identical)
  • Same performance - No performance impact (private helper calls compile to inline code)
  • Same memory usage - RAM: 31.2% (102,352 / 327,680 bytes), Flash: 27.4% (359,381 / 1,310,720 bytes)
  • All build variants supported:
  • βœ… task build (development)
  • βœ… task prod:build (production)
  • βœ… task debug:build (debug symbols)
  • βœ… task next:build (next-generation, ENABLE_DEVICE_RESPONSE=1)

Existing users can upgrade without any code changes.


Tests Passed

  • βœ… Builds without errors (all 4 build variants)
  • esp32dev-dev (development)
  • esp32dev-release (production)
  • esp32dev-debug (debug)
  • esp32dev-next (next-generation)
  • βœ… RAM usage: 31.2% (102,352 / 327,680 bytes) - identical to v1.14.2
  • βœ… Flash usage: 27.4% (359,381 / 1,310,720 bytes) - identical to v1.14.2
  • βœ… Pre-commit hooks: All checks passed
  • βœ… Detection functionality: All features (ENABLE_TIMESTAMP, BME280, RTC, GNSS) working
  • βœ… Event queueing: EventQueue integration verified
  • βœ… Stream control: Command::getInstance().get_stream() respected

Release Details

  • Date: 2025-12-11
  • Version: v1.14.3
  • Commits: 1 (SRP refactoring)
  • 2e2e268 - refactor(detection): apply single responsibility principle to DetectionProcessor methods
  • Files Changed: 2
  • MODIFIED: include/detection_processor.h (+95 lines, JSDoc for private helpers)
  • MODIFIED: src/detection_processor.cpp (+8 lines, split into helper methods)

Design Principles Applied

Single Responsibility Principle (SRP)

Each method has one reason to change:

  • read_detection() - Only if GPIO polling changes
  • add_timestamp_data() - Only if timestamp logic changes
  • add_bme280_data() - Only if BME280 reading changes
  • build_and_queue_event() - Only if queueing strategy changes
  • etc.

Before: One reason to change the entire process() method (100+ lines) After: Nine focused methods, each with one reason to change

High-Level Orchestration

The process() method now reads like a high-level specification:

// This is what detection processing does:
1. Read detection
2. If detected, read sensors
3. Build and queue response
4. Provide feedback
5. Clean up

No implementation details in the orchestratorβ€”only the workflow.

Conditional Compilation Isolation

Optional features (ENABLE_TIMESTAMP, ENABLE_BME280, etc.) are now clearly isolated in their own methods:

// Before: #if directives scattered throughout process()
// After: Each add_*() method contains its own conditional compilation

This makes it easier to understand which code is optional and which is always present.


Code Organization

Header Structure (v1.14.3)

include/detection_processor.h:
β”œβ”€β”€ DETECTION DATA STRUCTURES
β”‚   └── detection_result_t
β”œβ”€β”€ PUBLIC API
β”‚   β”œβ”€β”€ init()
β”‚   └── process()
└── PRIVATE HELPER METHODS
    β”œβ”€β”€ read_detection()
    β”œβ”€β”€ read_sensors()
    β”œβ”€β”€ add_timestamp_data()
    β”œβ”€β”€ add_adcmv_data()
    β”œβ”€β”€ add_bme280_data()
    β”œβ”€β”€ add_gnss_data()
    β”œβ”€β”€ build_and_queue_event()
    β”œβ”€β”€ led_feedback()
    └── cleanup()

Implementation Structure (v1.14.3)

src/detection_processor.cpp:
β”œβ”€β”€ PRIVATE STATE
β”‚   └── g_last_event_time_us (conditional)
β”œβ”€β”€ PUBLIC API IMPLEMENTATION
β”‚   β”œβ”€β”€ init()
β”‚   └── process()
└── PRIVATE HELPER IMPLEMENTATIONS
    β”œβ”€β”€ read_detection()
    β”œβ”€β”€ read_sensors()
    β”œβ”€β”€ add_timestamp_data()
    β”œβ”€β”€ add_adcmv_data()
    β”œβ”€β”€ add_bme280_data()
    β”œβ”€β”€ add_gnss_data()
    β”œβ”€β”€ build_and_queue_event()
    β”œβ”€β”€ led_feedback()
    └── cleanup()

Next Steps

  • v1.15.0 (planned): CommandParser class for unified command argument parsing and validation
  • Future: Consider similar SRP refactoring for EventQueue and CommandQueue classes
  • Ongoing: Monitor for opportunities to apply SRP to other monolithic methods