Skip to content
  • Date Created: 2025-12-12
  • Last Modified: 2025-12-12

Progress Log: Timestamp Timing Analysis – last_detection_time vs g_last_event_time_us

Task Description

Analyzed the timing of last_detection_time and g_last_event_time_us acquisition in the detection pipeline to verify synchronization requirements and identify potential timing issues.

Key Questions Investigated

  1. When is last_detection_time acquired and updated?
  2. When is g_last_event_time_us acquired and updated?
  3. Are they synchronized? Do they need to be?
  4. What are the timing deltas between them?

Outcome

Finding: The two timestamp variables are INDEPENDENT and CORRECTLY DECOUPLED

Architecture Discovery

Two completely separate timestamp systems exist at different architectural layers:

1. last_detection_time (Deadtime Filtering)

  • Location: src/detector.cpp:33 - Static member of CosmicDetector class
  • Type: unsigned long (32-bit, milliseconds)
  • Purpose: Hardware deadtime enforcement (prevent detector saturation)
  • Acquisition Point: Line 92 of detector.cpp in CosmicDetector::read()
  • Acquired AFTER GPIO polling completes (~2.01ms after detection cycle starts)
  • Acquired ONLY if signal detected
  • Update Condition: ONLY when deadtime check passes (line 100)
  • Suppressed detections do NOT update this variable
  • Represents "last time we reported a detection"

2. g_last_event_time_us (Inter-Event Timing / Trigger Time)

  • Location: src/detection_processor.cpp:50 - Static variable in module scope
  • Type: uint64_t (64-bit, microseconds)
  • Purpose: Track trigger timestamp of each detection event to calculate inter-event duration (timedelta_us field in output)
  • Acquisition Point: Line 138 of detection_processor.cpp in DetectionProcessor::add_timestamp_data()
  • Acquired DURING sensor data collection phase (~2.5ms after detection trigger)
  • Acquired for every queued event
  • Update Condition: UNCONDITIONALLY when event is queued
  • Updated regardless of deadtime state
  • Represents "timestamp when THIS detection event was triggered"

Timing Sequence Analysis

Normal Detection (deadtime=1000ms, stream enabled):

CosmicDetector::read()
├─ T+2.01ms: Acquire current_time = millis()
├─ T+2.05ms: Check deadtime
└─ T+2.1ms: UPDATE last_detection_time = current_time

DetectionProcessor::add_timestamp_data()
├─ T+2.5ms: Acquire current_micros = micros()
└─ T+2.50001ms: UPDATE g_last_event_time_us = current_micros

Timing Delta: ~400 microseconds between updates (expected and correct)

Suppressed Detection (within deadtime window):

CosmicDetector::read()
├─ Polls GPIO
├─ Checks deadtime: FAILS
└─ RETURNS with detection.detected=false (NO update to last_detection_time)

DetectionProcessor::process()
└─ RETURNS immediately (detection was suppressed)
  └─ NO sensor reading
  └─ NO event queued
  └─ NO update to g_last_event_time_us

Why They Don't Sync

Aspect last_detection_time g_last_event_time_us
Purpose Hardware deadtime User timing data
Layer Detector (HW) Application
Updated on Detection passes deadtime Event queued to stream
Resolution Milliseconds Microseconds
Visible to User No Yes (in output)

These are intentionally decoupled:

  • If deadtime suppresses an event: last_detection_time updates, but g_last_event_time_us does not
  • If stream is disabled: g_last_event_time_us still updates, but no event is sent
  • Both behaviors are correct by design

Learnings

  1. Timestamp Architecture is Sound
  2. No synchronization bugs
  3. Variables serve orthogonal purposes
  4. Decoupling is by design, not accidental

  5. Timing Deltas are Expected

  6. 400μs difference between updates is normal
  7. Reflects natural processing pipeline: GPIO → Deadtime → Sensors → Queue
  8. Not a sign of timing issues

  9. Single-Threaded Safety

  10. ESP32 is single-threaded (no RTOS in use)
  11. No race conditions possible
  12. No mutexes or synchronization primitives needed

  13. Design Pattern Clarity

  14. Detector module owns deadtime mechanism
  15. DetectionProcessor owns user-visible timing
  16. Clean separation of concerns (SRP)
  17. Each module correctly manages its own timing state

Variable Naming Improvements

Current Names Analysis

The two variables are functionally correct but their names are not self-documenting:

Variable Current Name Issue Purpose
Deadtime last_detection_time Vague: unclear it serves deadtime mechanism Reference timestamp for deadtime enforcement
Event timing g_last_event_time_us Unclear: "event" is ambiguous; unclear it tracks trigger time Tracks trigger timestamp of each detection to calculate inter-event intervals

last_detection_timeg_deadtime_ms

Benefits:

  • deadtime: Directly expresses the purpose (deadtime enforcement for this variable)
  • g_ prefix: Consistent with other global static variables
  • ms: Explicit unit (removes ambiguity)
  • Concise and self-documenting: name = purpose

g_last_event_time_usg_triggered_us

Benefits:

  • triggered: Clarifies this tracks the trigger timestamp of each detection event
  • us: Explicit microsecond resolution (suffix is essential for unit clarity)
  • Minimal yet self-documenting: name directly expresses the concept
  • Clearly indicates this is the reference point for calculating event-to-event time deltas

Naming Pattern Comparison

Aspect Current Proposed
Clarity ⭐⭐ Low ⭐⭐⭐⭐⭐ Excellent
Consistency Inconsistent g_ use Both use g_ prefix
Unit clarity ms implicit, us explicit Both explicit (ms, us)
Self-documenting Requires code reading Names explain: reported vs. triggered
Purpose distinction Both called "time/event" Clear: deadtime filtering vs. inter-event intervals

Example: Before vs. After Reading Code

Before (current names):

// What's the difference between these timestamps?
unsigned long last_detection_time = millis();        // ???
uint64_t g_last_event_time_us = micros();           // ???

→ Reader must search code to understand

After (proposed names):

// Purpose is immediately clear
unsigned long g_deadtime_ms = millis();   // Reference time for deadtime enforcement
uint64_t g_triggered_us = micros();       // Trigger timestamp of detection event

→ Reader understands instantly: one tracks reporting, the other tracks actual trigger times for calculating intervals

Migration Impact

Scope: 4 files affected

  • include/detector.h - Class member declaration
  • src/detector.cpp - Static initialization + 2 usages
  • src/detection_processor.cpp - 3 usages
  • Total: ~6 changed lines

Risk: Low (pure rename, no logic changes) Testing: Compile-only verification needed Commit type: refactor(naming) - Improve variable naming clarity

Next Steps (Optional Future Work)

If renaming is approved, follow this implementation order:

Deadtime variable (last_detection_timeg_deadtime_ms):

  1. Update include/detector.h:220 - Class member declaration
  2. Update src/detector.cpp:33 - Static initialization
  3. Update src/detector.cpp:100 - Update in read()
  4. Update src/detector.cpp:156 - Read in check_deadtime()

Trigger time variable (g_last_event_time_usg_triggered_us):

  1. Update src/detection_processor.cpp:50 - Declaration
  2. Update src/detection_processor.cpp:59 - Init in init()
  3. Update src/detection_processor.cpp:139-140 - Read and update in add_timestamp_data()
  4. Update src/detection_processor.cpp:143 - Assignment to event field

Verification:

  1. Verify with grep -r for any missed references
  2. Compile and test

Recommendation

If analyzing detector behavior in the future, remember:

  • Current last_detection_time determines what gets reported to the user (deadtime filtering)
  • Current g_last_event_time_us determines what timing data is included in the report (inter-event duration)
  • Both are correct and independent

Naming enhancement is optional but recommended for code clarity and maintainability.