Project Haltija logo Project Haltija

Haltija SDK for Android

⚠️ This project has been discontinued. The development and production cost for a reliable smart ring are too high for a side project. We decided against running this project as a full-time venture. Feel free to send an e-mail if you have any questions about what we learned.

⚠️ This API specification is currently a draft.

We use this documentation page to collect feedback and idea on the API design and capabilities with our community. The API samples here use Android/Kotlin, but we plan an API for iOS that follows the same design principles. If you have ideas or suggestions, please reach out.

Overview

This SDK allows developers to access data from a Project Haltija Smart Ring. It is possible to subscribe to live data updates, button events as well as to fetch historic data.

Pairing

The SDK does not directly communicate with the ring via bluetooth, but uses our app as a proxy. Initial pairing happens through our companion app. During initial pairing, data encryption keys are exchanged. The reasons for this design choice are:

  1. To allow users to use multiple apps in parallel without re-pairing the hardware.
  2. To provide a transparent data storage layer, as the smart ring itself has very limited capacity.
  3. To manage encryption keys in one place and provide a transparent security layer.

Accelerometer Data & Battery Lifetime Considerations

Streaming accelerometer data has a significant impact on battery lifetime. While streaming data is a great way to experiment and develop, continuous data streaming is discouraged for production use-cases. To work with live data, we are currently evaluating several ways to deploy small machine learning models directly on the ring.

Biomarkers

The SDK provides access to the following biomarkers:

BiomarkerUnitDescription
Heart RateBeats per minute (BPM)The number of heartbeats per minute.
Heart Rate Variability (HRV)Milliseconds (ms)Time variation between consecutive heartbeats (R-R intervals).
Blood OxygenPercentage (%)The percentage of oxygen-saturated hemoglobin relative to total hemoglobin.
TemperatureDegrees Celsius (°C)The body temperature at the ring’s contact point.
Step CountStepsThe total number of steps taken.
Accelerometer (X, Y, Z)Meters per second squared (m/s²)Acceleration along each axis (X, Y, Z) in 3D space.

API Reference

Initialization

The SDK only supports a single paired ring at a time. Therefore, initializing the SDK is straight-forward.

val ring = new HaltijaClient();
ring.connect();

// Do things

ring.disconnect();

Lifecycle Methods

MethodDescription
connect()Establishes a connection to the SmartRing hardware.
disconnect()Closes the connection to the SmartRing hardware.

Subscribe to Asynchronous Updates

These methods are used to set listeners for various biomarkers as well as button events. The listener is called whenever a new value becomes available.

MethodDescription
setButtonEventListener(listener: DataListener?)Sets the listener for button events.
setHeartRateListener(listener: DataListener?)Sets the listener for heart rate updates.
setHRVListener(listener: DataListener?)Sets the listener for HRV updates.
setBloodOxygenListener(listener: DataListener?)Sets the listener for blood oxygen updates.
setTemperatureListener(listener: DataListener?)Sets the listener for temperature updates.
setStepCountListener(listener: DataListener?)Sets the listener for step count updates.
setAccelerometerListener(listener: AccelerometerDataListener?)Sets the listener for accelerometer data in batches. Accelerometer data is batched because the update frequency is too high for streaming individual values. This method is intended for development purposes only.

Querying Current Measurements

Each biomarker has a method for querying the latest value, allowing the caller to specify a maximum age for the measurement. If the current value is older than the maximum age, the device will attempt to force a new measurement.

Please keep in mind that measuring biomarkers has an impact on battery life.

MethodReturn TypeDescription
currentHeartRate(maxAge: Duration): DataPoint?DataPoint?Returns the current heart rate value, or null if unavailable.
currentHRV(maxAge: Duration): DataPoint?DataPoint?Returns the current HRV value, or null if unavailable.
currentBloodOxygen(maxAge: Duration: DataPoint?)DataPoint?Returns the current blood oxygen value, or null if unavailable.
currentTemperature(maxAge: Duration): DataPoint?DataPoint?Returns the current temperature value, or null if unavailable.
currentStepCount(maxAge: Duration): DataPoint?DataPoint?Returns the current step count value, or null if unavailable.

Historical Data Retrieval

Each biomarker supports retrieving historic data. The SDK handles data storage and caching, so there is no limit on retrievable history.

Accelerometer data is not stored due to the high sampling rate.

MethodReturn TypeDescription
fetchHistoricalHeartRateData(startTime: LocalDateTime, endTime: LocalDateTime)List<DataPoint>Fetches historical heart rate data for the specified timespan.
fetchHistoricalHRVData(startTime: LocalDateTime, endTime: LocalDateTime)List<DataPoint>Fetches historical HRV data for the specified timespan.
fetchHistoricalBloodOxygenData(startTime: LocalDateTime, endTime: LocalDateTime)List<DataPoint>Fetches historical blood oxygen data for the specified timespan.
fetchHistoricalTemperatureData(startTime: LocalDateTime, endTime: LocalDateTime)List<DataPoint>Fetches historical temperature data for the specified timespan.
fetchHistoricalStepCountData(startTime: LocalDateTime, endTime: LocalDateTime)List<DataPoint>Fetches historical step count data for the specified timespan.

Advanced Configuration

These methods can be used to configure measuring and sampling parameters. Frequent measurements have a negative impact on battery life. Restrictions might be introduced in the future.

MethodDescription
setHeartRateInterval(intervalSeconds: Int)Configures the interval (in seconds) for heart rate measurements.
setHRVInterval(intervalSeconds: Int)Configures the interval (in seconds) for HRV measurements.
setBloodOxygenInterval(intervalSeconds: Int)Configures the interval (in seconds) for blood oxygen measurements.
setTemperatureInterval(intervalSeconds: Int)Configures the interval (in seconds) for temperature measurements.
setStepCountInterval(intervalSeconds: Int)Configures the interval (in seconds) for step count measurements.
setAccelerometerBufferSize(size: Int)Sets the buffer size for accelerometer data batching when streaming accelerometer data.

Data Structures and Classes

Single-Dimension Data

This class is used for heart rate, blood oxygen, HRV, temperature as well as step count.

/**
 * Represents a single measurement with a timestamp and value.
 *
 * @property timestamp The time when the measurement was taken.
 * @property value The measured value.
 */
data class DataPoint(
    val timestamp: LocalDateTime,
    val value: Float
)

Accelerometer Data

This class is used when streaming accelerometer data.

/**
 * Represents a single accelerometer measurement with a timestamp and 3D vector data.
 *
 * @property timestamp The time when the measurement was taken.
 * @property x The measurement along the X-axis.
 * @property y The measurement along the Y-axis.
 * @property z The measurement along the Z-axis.
 */
data class AccelerometerDataPoint(
    val timestamp: LocalDateTime,
    val x: Float,
    val y: Float,
    val z: Float
)

Examples

Querying Historical Data

This example demonstrates fetching and iterating over historical data for meaningful insights.

val startTime = LocalDateTime.of(2023, 12, 1, 0, 0)
val endTime = LocalDateTime.of(2023, 12, 1, 23, 59)

try {
    val heartRateData = ring.fetchHistoricalHeartRateData(startTime, endTime)
    heartRateData.forEach { dataPoint ->
        println("Heart Rate at ${dataPoint.timestamp}: ${dataPoint.value}")
    }
} catch (e: Exception) {
    println("Failed to fetch historical heart rate data: ${e.message}")
}

Setting up Listeners and Receiving Data

This example demonstrates how to subscribe to real-time data.

ring.setHeartRateListener { dataPoint ->
    println("Heart Rate Update: ${dataPoint.value} BPM at ${dataPoint.timestamp}")
}

ring.setTemperatureListener { dataPoint ->
    println("Temperature Update: ${dataPoint.value} °C at ${dataPoint.timestamp}")
}

Fetching Current Measurements

This example demonstrates how to fetch current measurements.

val maxAge = Duration.ofSeconds(10)

val currentHeartRate = ring.currentHeartRate(maxAge)
if (currentHeartRate != null) {
    println("Current Heart Rate: ${currentHeartRate.value} BPM at ${currentHeartRate.timestamp}")
} else {
    println("Heart rate data is not available or stale.")
}