Pre-release docs for SmartSpectra SDK 3.2.0-rc.6. This RC channel may describe APIs or install commands that differ from the latest stable release.

SmartSpectra SDK
C++

Configuring Metrics

Request and read SmartSpectra metrics from the C++ SDK.

By default, C++ measurements request the breathing metric set. Add pulse rate when your app needs a basic cardio value.

Breathing and Pulse

Request Metrics

Request the breathing metrics plus MetricType::PULSE_RATE before constructing SmartSpectra:

#include <smartspectra/messages/metric_types.pb.h>
#include <smartspectra/smartspectra_config.h>

namespace spectra = presage::smartspectra;
using presage::smartspectra::MetricType;

spectra::SmartSpectraConfig config;
config.api_key = "YOUR_API_KEY";
config.requested_metrics = spectra::SmartSpectraConfig::BreathingMetrics();
config.AddMetrics({MetricType::PULSE_RATE});

Read Metrics

Read the latest breathing and pulse samples from SetOnMetrics:

#include <smartspectra/messages/metrics.h>
#include <smartspectra/smartspectra.h>
#include <glog/logging.h>
#include <utility>

spectra::SmartSpectra spectra(config);
spectra.SetOnMetrics([](const presage::smartspectra::Metrics& metrics, int64_t) {
    if (metrics.has_breathing() && metrics.breathing().rate_size() > 0) {
        const auto& rate = metrics.breathing().rate(metrics.breathing().rate_size() - 1);
        LOG(INFO) << "Breathing rate: " << rate.value();
    }

    if (metrics.has_cardio() && metrics.cardio().pulse_rate_size() > 0) {
        const auto& pulse = metrics.cardio().pulse_rate(metrics.cardio().pulse_rate_size() - 1);
        LOG(INFO) << "Pulse rate: " << pulse.value();
    }
});

If requested_metrics is empty, the SDK uses DefaultSupportedMetrics(), which returns BreathingMetrics(). Use BreathingMetrics() when you are explicitly composing a breathing request. Cardio fields are empty unless you request a cardio metric such as PULSE_RATE.

Requested metrics are validated against your subscription during SDK startup. If a metric is not authorized, it is omitted from the output. If the authorization request fails, Start() reports an error.

Metric Update Patterns

SetOnMetrics receives the latest SDK metrics payload. Each payload contains the samples that became available since the previous metrics callback; it is not guaranteed to contain every requested field every time.

Metric categoryExamplesExpected cadenceEmpty behavior
Peak/event-driven rate metricsbreathing().rate(), cardio().pulse_rate(), cardio().hrv()Updated when a new physiological event, cycle, or analysis window produces a valueRepeated fields may be empty between valid updates during active capture
Frame-driven metricsface().expression(), face().landmarks(), face().blinking(), face().talking(), breathing tracesUpdated near device frame cadence, with SDK callbacks rate-limited to about 30 HzUsually present more continuously when the metric is enabled and the input signal is valid

For example, metrics.cardio().pulse_rate_size() and metrics.breathing().rate_size() may be 0 between valid updates. This is expected and does not mean capture stopped or the metric was disabled. By contrast, face expression samples are frame-driven, so metrics.face().expression_size() can be non-zero on most callbacks while face metrics are enabled and the face signal is valid.

Recommended UI handling:

  • Keep the last valid rate sample in app state and update it only when the repeated field contains a new sample.
  • Show an initial loading or placeholder state until the first valid sample arrives.
  • Do not overwrite a displayed pulse rate or breathing rate just because one metrics payload has no new sample.
  • Clear retained values when a capture session starts, stops, or when your app intentionally changes the requested metric set.
  • Prefer sample timestamps, and stable() when present, to decide whether a retained value is fresh enough for your UI.
#include <optional>

std::optional<float> last_pulse_rate;

spectra.SetOnMetrics([&last_pulse_rate](const presage::smartspectra::Metrics& metrics, int64_t) {
    if (metrics.has_cardio() && metrics.cardio().pulse_rate_size() > 0) {
        const auto& pulse = metrics.cardio().pulse_rate(metrics.cardio().pulse_rate_size() - 1);
        if (pulse.timestamp() > 0) {
            last_pulse_rate = pulse.value();
            LOG(INFO) << "Pulse rate: " << *last_pulse_rate;
        }
    } else if (!last_pulse_rate.has_value()) {
        LOG(INFO) << "Pulse rate pending";
    }
});

Advanced

Request additional metrics only when your app needs them:

config.requested_metrics = spectra::SmartSpectraConfig::BreathingMetrics();
config.AddMetrics({
    MetricType::PULSE_RATE,
    MetricType::ARTERIAL_PRESSURE_TRACE,
    MetricType::HRV,
    MetricType::EDA_TRACE,
    MetricType::FACE_LANDMARKS,
    MetricType::BLINKING,
    MetricType::TALKING,
    MetricType::EXPRESSIONS,
});

Read the advanced fields from the same metrics callback:

spectra.SetOnMetrics([](const presage::smartspectra::Metrics& metrics, int64_t) {
    if (metrics.has_cardio() && metrics.cardio().arterial_pressure_trace_size() > 0) {
        const auto& pressure = metrics.cardio().arterial_pressure_trace(
            metrics.cardio().arterial_pressure_trace_size() - 1);
        LOG(INFO) << "Arterial pressure trace: " << pressure.value();
    }

    if (metrics.has_cardio() && metrics.cardio().hrv_size() > 0) {
        const auto& hrv = metrics.cardio().hrv(metrics.cardio().hrv_size() - 1);
        LOG(INFO) << "HRV RMSSD: " << hrv.rmssd();
    }

    if (metrics.has_eda() && metrics.eda().trace_size() > 0) {
        const auto& eda = metrics.eda().trace(metrics.eda().trace_size() - 1);
        LOG(INFO) << "EDA trace: " << eda.value();
    }

    if (metrics.has_face() && metrics.face().landmarks_size() > 0) {
        const auto& landmarks = metrics.face().landmarks(metrics.face().landmarks_size() - 1);
        LOG(INFO) << "Face landmark count: " << landmarks.value_size();
    }
    if (metrics.has_face() && metrics.face().blinking_size() > 0) {
        const auto& blinking = metrics.face().blinking(metrics.face().blinking_size() - 1);
        LOG(INFO) << "Blinking: " << blinking.detected();
    }
    if (metrics.has_face() && metrics.face().talking_size() > 0) {
        const auto& talking = metrics.face().talking(metrics.face().talking_size() - 1);
        LOG(INFO) << "Talking: " << talking.detected();
    }
    if (metrics.has_face() && metrics.face().expression_size() > 0) {
        const auto& expression = metrics.face().expression(metrics.face().expression_size() - 1);
        LOG(INFO) << "Expression score count: " << expression.scores_size();
    }

});

Advanced Payload Classes

C++ uses the generated protobuf classes. Requested advanced metrics populate these fields:

presage::smartspectra::Metrics {
    Breathing breathing;
    Eda eda;
    Face face;
    Cardio cardio;
}

Cardio {
    repeated MeasurementWithConfidence pulse_rate;
    repeated MeasurementWithConfidence arterial_pressure_trace;
    repeated Hrv hrv;
}

Hrv {
    double rmssd;
    double mean_nn;
    double sdnn;
    double baevsky;
    int64 timestamp;
    float confidence;
}

Eda {
    repeated Measurement trace;
}

Face {
    repeated Landmarks landmarks;
    repeated DetectionStatus blinking;
    repeated DetectionStatus talking;
    repeated Expression expression;
}

EDA may take longer to produce its first sample than breathing or cardio outputs. See Data Types for the complete protobuf schema.

Timing and Stability

All measurement samples use timestamp values in microseconds. Trace metrics are produced at frame cadence when the underlying signal is available; lower rate outputs such as EDA may arrive less frequently.

Measurement types expose a stable() flag. Check it before using a sample for critical decisions or user-facing summaries:

if (metrics.has_breathing() && metrics.breathing().rate_size() > 0) {
    const auto& rate = metrics.breathing().rate(metrics.breathing().rate_size() - 1);
    if (rate.stable()) {
        LOG(INFO) << "Stable breathing rate: " << rate.value();
    }
}

Face landmark samples also expose reset(), which indicates that landmark tracking was reinitialized:

if (metrics.has_face() && metrics.face().landmarks_size() > 0) {
    const auto& landmarks = metrics.face().landmarks(metrics.face().landmarks_size() - 1);
    if (landmarks.reset()) {
        LOG(INFO) << "Face landmark tracking reset";
    }
}

Serialization

Metrics are protobuf messages. Serialize them directly when you need to persist or transmit the exact SDK payload:

std::string binary;
if (metrics.SerializeToString(&binary)) {
    writeMetrics(binary);
}

For diagnostics, convert to JSON with protobuf utilities:

#include <google/protobuf/util/json_util.h>

std::string json;
google::protobuf::util::JsonPrintOptions options;
options.preserve_proto_field_names = true;

auto status = google::protobuf::util::MessageToJsonString(metrics, &json, options);
if (status.ok()) {
    LOG(INFO) << json;
}

On this page