Skip to the content.

Home / Guides

Diagnostics

RepDetectionService emits a DetectionDiagnostic for every processed sample. Use diagnostics to understand why the detector is or isn’t firing, and to visualize the signal processing pipeline in real time.

Subscribing to Diagnostics

detector.diagnosticPublisher
    .sink { diag in
        print("[\(diag.sampleIndex)] raw=\(diag.rawValue) filtered=\(diag.filteredValue) threshold=\(diag.threshold)")
    }
    .store(in: &cancellables)

DetectionDiagnostic

Property Type Description
sampleIndex Int Sequential index of this sample since last reset
rawValue Double Axis value after polarity flip, before filtering
filteredValue Double Value after exponential smoothing
threshold Double Current dynamic threshold (mean + k * stddev)
mean Double Rolling mean of last 100 samples
stddev Double Rolling standard deviation of last 100 samples

What to Look For

Reps not detected

Too many reps detected

Signal appears flat

Visualization

Collect diagnostics into an array and chart them to see the detection pipeline:

struct DiagnosticChart: View {
    let diagnostics: [DetectionDiagnostic]

    var body: some View {
        Chart {
            ForEach(diagnostics, id: \.sampleIndex) { d in
                LineMark(
                    x: .value("Sample", d.sampleIndex),
                    y: .value("Value", d.filteredValue)
                )
                .foregroundStyle(.blue)

                LineMark(
                    x: .value("Sample", d.sampleIndex),
                    y: .value("Threshold", d.threshold)
                )
                .foregroundStyle(.red)
                .lineStyle(StrokeStyle(dash: [5, 3]))
            }
        }
    }
}

The chart shows:

Buffering for Performance

The diagnostic publisher fires for every sample (50+ times per second at default rates). For UI display, throttle or sample the stream:

detector.diagnosticPublisher
    .collect(.byTime(DispatchQueue.main, .milliseconds(100)))
    .sink { batch in
        diagnosticBuffer.append(contentsOf: batch)
        if diagnosticBuffer.count > 500 {
            diagnosticBuffer.removeFirst(diagnosticBuffer.count - 500)
        }
    }
    .store(in: &cancellables)