Skip to content

Device Calibration

Remote calibration endpoint for managing BCGMCU calibration procedures and validation.

POST /api/v1/device/calibration/{device_id}
  • Execute remote calibration procedures
  • Validate and adjust sensor accuracy
  • Maintain measurement consistency
  • Optimize signal processing parameters
Content-Type: application/json
Authorization: Bearer {device_token}
{
"device_id": "BCGMCU_001",
"calibration": {
"procedure": "baseline_calibration",
"duration_seconds": 300,
"reference_data": {
"expected_pulse_range": [60, 100],
"expected_breathing_range": [12, 20],
"environment_type": "hospital_bed"
},
"auto_apply": true
},
"timestamp": "2025-01-27T10:30:00.000Z"
}
FieldTypeRequiredDescription
procedurestringYesCalibration procedure type
duration_secondsintegerYesCalibration duration (60-1800)
reference_dataobjectNoExpected measurement ranges
auto_applybooleanNoAutomatically apply results

Establishes measurement baseline with no subject present.

{
"procedure": "baseline_calibration",
"duration_seconds": 120,
"reference_data": {
"environment_type": "hospital_bed",
"expected_noise_level": "low"
},
"auto_apply": true
}

Adjusts sensor sensitivity for optimal signal detection.

{
"procedure": "sensitivity_calibration",
"duration_seconds": 300,
"reference_data": {
"expected_pulse_range": [60, 100],
"expected_breathing_range": [12, 20],
"subject_weight_kg": 70
},
"auto_apply": false
}

Validates current calibration accuracy without changes.

{
"procedure": "validation_test",
"duration_seconds": 180,
"reference_data": {
"reference_pulse_bpm": 72,
"reference_breathing_rpm": 16,
"tolerance_percent": 5
},
"auto_apply": false
}

Restores factory calibration settings.

{
"procedure": "factory_reset",
"duration_seconds": 60,
"reference_data": {},
"auto_apply": true
}
{
"calibration_status": "started",
"procedure_id": "cal_baseline_001",
"estimated_completion": "2025-01-27T10:35:00.000Z",
"current_phase": "data_collection",
"progress_percent": 0,
"preliminary_results": null
}
FieldTypeDescription
calibration_statusstring”started”, “in_progress”, “completed”, “failed”
procedure_idstringUnique calibration procedure identifier
estimated_completionstringExpected completion timestamp
current_phasestringCurrent calibration phase
progress_percentintegerCompletion percentage (0-100)
preliminary_resultsobjectEarly results if available
{
"calibration_status": "completed",
"procedure_id": "cal_baseline_001",
"completion_time": "2025-01-27T10:35:00.000Z",
"results": {
"baseline_established": true,
"noise_level": 0.002,
"signal_quality": "excellent",
"calibration_factors": {
"pulse_sensitivity": 1.05,
"breathing_sensitivity": 0.98,
"noise_threshold": 0.01
}
},
"applied": true,
"validation_score": 95
}
#include <ArduinoJson.h>
#include <BCGMCU.h>
class CalibrationManager {
private:
struct CalibrationSession {
String procedureId;
String procedureType;
unsigned long startTime;
int durationSeconds;
JsonObject referenceData;
bool autoApply;
bool active;
// Results
float baselineNoise = 0;
float signalAmplitude = 0;
float pulseSensitivity = 1.0;
float breathingSensitivity = 1.0;
int validationScore = 0;
};
CalibrationSession currentSession;
std::vector<float> calibrationSamples;
BCGMCU bcgmcu;
public:
void startCalibration(JsonObject& request) {
JsonObject cal = request["calibration"];
// Initialize session
currentSession.procedureId = generateProcedureId();
currentSession.procedureType = cal["procedure"];
currentSession.startTime = millis();
currentSession.durationSeconds = cal["duration_seconds"];
currentSession.referenceData = cal["reference_data"];
currentSession.autoApply = cal["auto_apply"] | false;
currentSession.active = true;
// Clear previous data
calibrationSamples.clear();
// Configure BCGMCU for calibration
configureBCGMCUForCalibration();
// Send initial response
sendCalibrationStatus("started");
Serial.printf("Started %s calibration (ID: %s)\n",
currentSession.procedureType.c_str(),
currentSession.procedureId.c_str());
}
void processCalibration() {
if (!currentSession.active) return;
unsigned long elapsed = (millis() - currentSession.startTime) / 1000;
int progress = min(100, (int)(100 * elapsed / currentSession.durationSeconds));
// Collect calibration data
BCGMCU::RawSample sample = bcgmcu.getRawSample();
calibrationSamples.push_back(sample.magnitude);
// Update progress every 10%
static int lastProgress = 0;
if (progress >= lastProgress + 10) {
sendCalibrationProgress(progress);
lastProgress = progress;
}
// Check if calibration is complete
if (elapsed >= currentSession.durationSeconds) {
completeCalibration();
}
}
private:
void configureBCGMCUForCalibration() {
if (currentSession.procedureType == "baseline_calibration") {
// High sensitivity for baseline measurement
bcgmcu.setSensitivity("high");
bcgmcu.setFilterMode("minimal");
} else if (currentSession.procedureType == "sensitivity_calibration") {
// Standard settings for sensitivity calibration
bcgmcu.setSensitivity("medium");
bcgmcu.setFilterMode("adaptive");
} else if (currentSession.procedureType == "validation_test") {
// Current production settings
bcgmcu.applySavedSettings();
}
// Set high sample rate for calibration
bcgmcu.setSampleRate(50); // 50Hz during calibration
}
void completeCalibration() {
currentSession.active = false;
// Analyze collected data
analyzeCalibrationData();
// Generate calibration results
JsonDocument results = generateCalibrationResults();
// Apply calibration if auto_apply is true
if (currentSession.autoApply) {
applyCalibrationResults();
}
// Send completion status
sendCalibrationComplete(results);
// Restore normal operation
restoreNormalOperation();
}
void analyzeCalibrationData() {
if (calibrationSamples.empty()) return;
// Calculate statistics
float sum = 0;
float min_val = calibrationSamples[0];
float max_val = calibrationSamples[0];
for (float sample : calibrationSamples) {
sum += sample;
min_val = min(min_val, sample);
max_val = max(max_val, sample);
}
float mean = sum / calibrationSamples.size();
// Calculate standard deviation
float variance = 0;
for (float sample : calibrationSamples) {
variance += pow(sample - mean, 2);
}
float stddev = sqrt(variance / calibrationSamples.size());
// Store results
currentSession.baselineNoise = stddev;
currentSession.signalAmplitude = max_val - min_val;
// Procedure-specific analysis
if (currentSession.procedureType == "baseline_calibration") {
analyzeBaselineCalibration(mean, stddev);
} else if (currentSession.procedureType == "sensitivity_calibration") {
analyzeSensitivityCalibration(mean, stddev);
} else if (currentSession.procedureType == "validation_test") {
analyzeValidationTest();
}
}
void analyzeBaselineCalibration(float mean, float stddev) {
// Establish new baseline
float baselineOffset = mean;
float noiseThreshold = stddev * 3; // 3-sigma threshold
// Update calibration factors
currentSession.pulseSensitivity = 1.0;
currentSession.breathingSensitivity = 1.0;
// Validation score based on noise level
if (stddev < 0.001) {
currentSession.validationScore = 95;
} else if (stddev < 0.005) {
currentSession.validationScore = 80;
} else if (stddev < 0.01) {
currentSession.validationScore = 65;
} else {
currentSession.validationScore = 40;
}
Serial.printf("Baseline: offset=%.4f, noise=%.4f, score=%d\n",
baselineOffset, stddev, currentSession.validationScore);
}
void analyzeSensitivityCalibration(float mean, float stddev) {
JsonObject ref = currentSession.referenceData;
if (ref.containsKey("expected_pulse_range")) {
JsonArray pulseRange = ref["expected_pulse_range"];
int expectedMin = pulseRange[0];
int expectedMax = pulseRange[1];
// Measure actual pulse detection
int detectedPulse = detectPulseDuringCalibration();
// Calculate sensitivity adjustment
if (detectedPulse > 0) {
float targetPulse = (expectedMin + expectedMax) / 2.0;
currentSession.pulseSensitivity = targetPulse / detectedPulse;
}
}
if (ref.containsKey("expected_breathing_range")) {
JsonArray breathingRange = ref["expected_breathing_range"];
int expectedMin = breathingRange[0];
int expectedMax = breathingRange[1];
// Measure actual breathing detection
int detectedBreathing = detectBreathingDuringCalibration();
// Calculate sensitivity adjustment
if (detectedBreathing > 0) {
float targetBreathing = (expectedMin + expectedMax) / 2.0;
currentSession.breathingSensitivity = targetBreathing / detectedBreathing;
}
}
// Calculate validation score
currentSession.validationScore = calculateValidationScore();
}
void analyzeValidationTest() {
JsonObject ref = currentSession.referenceData;
int score = 100;
// Test pulse accuracy
if (ref.containsKey("reference_pulse_bpm")) {
int referencePulse = ref["reference_pulse_bpm"];
int measuredPulse = detectPulseDuringCalibration();
float tolerance = ref["tolerance_percent"] | 5.0;
float error = abs(measuredPulse - referencePulse) * 100.0 / referencePulse;
if (error > tolerance) {
score -= min(30, (int)(error - tolerance) * 2);
}
}
// Test breathing accuracy
if (ref.containsKey("reference_breathing_rpm")) {
int referenceBreathing = ref["reference_breathing_rpm"];
int measuredBreathing = detectBreathingDuringCalibration();
float tolerance = ref["tolerance_percent"] | 5.0;
float error = abs(measuredBreathing - referenceBreathing) * 100.0 / referenceBreathing;
if (error > tolerance) {
score -= min(30, (int)(error - tolerance) * 2);
}
}
currentSession.validationScore = max(0, score);
}
JsonDocument generateCalibrationResults() {
JsonDocument doc;
doc["calibration_status"] = "completed";
doc["procedure_id"] = currentSession.procedureId;
doc["completion_time"] = getCurrentTimestamp();
JsonObject results = doc.createNestedObject("results");
if (currentSession.procedureType == "baseline_calibration") {
results["baseline_established"] = true;
results["noise_level"] = currentSession.baselineNoise;
results["signal_quality"] = getQualityString(currentSession.validationScore);
} else if (currentSession.procedureType == "sensitivity_calibration") {
results["sensitivity_adjusted"] = true;
results["pulse_sensitivity_factor"] = currentSession.pulseSensitivity;
results["breathing_sensitivity_factor"] = currentSession.breathingSensitivity;
} else if (currentSession.procedureType == "validation_test") {
results["validation_passed"] = (currentSession.validationScore >= 70);
results["accuracy_score"] = currentSession.validationScore;
}
JsonObject factors = results.createNestedObject("calibration_factors");
factors["pulse_sensitivity"] = currentSession.pulseSensitivity;
factors["breathing_sensitivity"] = currentSession.breathingSensitivity;
factors["noise_threshold"] = currentSession.baselineNoise * 3;
doc["applied"] = currentSession.autoApply;
doc["validation_score"] = currentSession.validationScore;
return doc;
}
void applyCalibrationResults() {
// Apply new calibration factors to BCGMCU
bcgmcu.setPulseSensitivity(currentSession.pulseSensitivity);
bcgmcu.setBreathingSensitivity(currentSession.breathingSensitivity);
bcgmcu.setNoiseThreshold(currentSession.baselineNoise * 3);
// Save calibration to persistent storage
saveCalibrationToFlash();
Serial.println("Calibration applied and saved");
}
void sendCalibrationStatus(String status) {
StaticJsonDocument<256> doc;
doc["calibration_status"] = status;
doc["procedure_id"] = currentSession.procedureId;
doc["estimated_completion"] = getEstimatedCompletion();
doc["current_phase"] = "initialization";
doc["progress_percent"] = 0;
sendStatusUpdate(doc);
}
void sendCalibrationProgress(int progress) {
StaticJsonDocument<256> doc;
doc["calibration_status"] = "in_progress";
doc["procedure_id"] = currentSession.procedureId;
doc["current_phase"] = getCurrentPhase(progress);
doc["progress_percent"] = progress;
sendStatusUpdate(doc);
}
void sendCalibrationComplete(JsonDocument& results) {
sendStatusUpdate(results);
}
String getCurrentPhase(int progress) {
if (progress < 20) return "initialization";
if (progress < 80) return "data_collection";
if (progress < 95) return "analysis";
return "finalization";
}
String getQualityString(int score) {
if (score >= 90) return "excellent";
if (score >= 75) return "good";
if (score >= 60) return "fair";
return "poor";
}
void restoreNormalOperation() {
// Restore normal BCGMCU settings
bcgmcu.setSampleRate(10); // Back to normal 10Hz
bcgmcu.applySavedSettings();
Serial.println("Returned to normal operation");
}
};
class CalibrationValidator {
public:
struct ValidationResult {
bool valid;
int score;
std::vector<String> issues;
std::vector<String> recommendations;
};
ValidationResult validateCalibration() {
ValidationResult result;
result.valid = true;
result.score = 100;
// Test pulse detection accuracy
testPulseAccuracy(result);
// Test breathing detection accuracy
testBreathingAccuracy(result);
// Test noise levels
testNoiseLevel(result);
// Test signal stability
testSignalStability(result);
result.valid = (result.score >= 70);
return result;
}
private:
void testPulseAccuracy(ValidationResult& result) {
// Run pulse detection test
std::vector<int> measurements;
for (int i = 0; i < 10; i++) {
measurements.push_back(bcgmcu.getPulseRate());
delay(1000);
}
// Calculate consistency
float mean = calculateMean(measurements);
float stddev = calculateStdDev(measurements, mean);
if (stddev > 5.0) {
result.score -= 20;
result.issues.push_back("Pulse detection inconsistent (stddev=" + String(stddev) + ")");
result.recommendations.push_back("Recalibrate pulse sensitivity");
}
// Check for reasonable values
if (mean < 30 || mean > 200) {
result.score -= 15;
result.issues.push_back("Pulse rate outside normal range: " + String(mean));
}
}
void testBreathingAccuracy(ValidationResult& result) {
// Similar test for breathing rate
std::vector<int> measurements;
for (int i = 0; i < 10; i++) {
measurements.push_back(bcgmcu.getBreathingRate());
delay(1000);
}
float mean = calculateMean(measurements);
float stddev = calculateStdDev(measurements, mean);
if (stddev > 2.0) {
result.score -= 15;
result.issues.push_back("Breathing detection inconsistent");
result.recommendations.push_back("Recalibrate breathing sensitivity");
}
}
};
  1. Controlled Environment: Perform calibration in stable conditions
  2. Sufficient Duration: Allow adequate time for accurate calibration
  3. Regular Validation: Validate calibration periodically
  4. Backup Calibration: Store previous calibration before changes
  5. Environmental Factors: Account for bed type and patient weight
  6. Documentation: Log all calibration procedures and results