- 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¶
- When is
last_detection_timeacquired and updated? - When is
g_last_event_time_usacquired and updated? - Are they synchronized? Do they need to be?
- 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 ofCosmicDetectorclass - Type:
unsigned long(32-bit, milliseconds) - Purpose: Hardware deadtime enforcement (prevent detector saturation)
- Acquisition Point: Line 92 of
detector.cppinCosmicDetector::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_usfield in output) - Acquisition Point: Line 138 of
detection_processor.cppinDetectionProcessor::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_timeupdates, butg_last_event_time_usdoes not - If stream is disabled:
g_last_event_time_usstill updates, but no event is sent - Both behaviors are correct by design
Learnings¶
- Timestamp Architecture is Sound
- No synchronization bugs
- Variables serve orthogonal purposes
-
Decoupling is by design, not accidental
-
Timing Deltas are Expected
- 400μs difference between updates is normal
- Reflects natural processing pipeline: GPIO → Deadtime → Sensors → Queue
-
Not a sign of timing issues
-
Single-Threaded Safety
- ESP32 is single-threaded (no RTOS in use)
- No race conditions possible
-
No mutexes or synchronization primitives needed
-
Design Pattern Clarity
- Detector module owns deadtime mechanism
- DetectionProcessor owns user-visible timing
- Clean separation of concerns (SRP)
- 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 |
Recommended Naming (Option 1)¶
last_detection_time → g_deadtime_ms
Benefits:
deadtime: Directly expresses the purpose (deadtime enforcement for this variable)g_prefix: Consistent with other global static variablesms: Explicit unit (removes ambiguity)- Concise and self-documenting: name = purpose
g_last_event_time_us → g_triggered_us
Benefits:
triggered: Clarifies this tracks the trigger timestamp of each detection eventus: 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 declarationsrc/detector.cpp- Static initialization + 2 usagessrc/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_time → g_deadtime_ms):
- Update include/detector.h:220 - Class member declaration
- Update src/detector.cpp:33 - Static initialization
- Update src/detector.cpp:100 - Update in
read() - Update src/detector.cpp:156 - Read in
check_deadtime()
Trigger time variable (g_last_event_time_us → g_triggered_us):
- Update src/detection_processor.cpp:50 - Declaration
- Update src/detection_processor.cpp:59 - Init in
init() - Update src/detection_processor.cpp:139-140 - Read and update in
add_timestamp_data() - Update src/detection_processor.cpp:143 - Assignment to event field
Verification:
- Verify with
grep -rfor any missed references - Compile and test
Recommendation¶
If analyzing detector behavior in the future, remember:
- Current
last_detection_timedetermines what gets reported to the user (deadtime filtering) - Current
g_last_event_time_usdetermines 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.