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:
- To allow users to use multiple apps in parallel without re-pairing the hardware.
- To provide a transparent data storage layer, as the smart ring itself has very limited capacity.
- 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:
Biomarker | Unit | Description |
---|---|---|
Heart Rate | Beats per minute (BPM) | The number of heartbeats per minute. |
Heart Rate Variability (HRV) | Milliseconds (ms) | Time variation between consecutive heartbeats (R-R intervals). |
Blood Oxygen | Percentage (%) | The percentage of oxygen-saturated hemoglobin relative to total hemoglobin. |
Temperature | Degrees Celsius (°C) | The body temperature at the ring’s contact point. |
Step Count | Steps | The 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
Method | Description |
---|---|
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.
Method | Description |
---|---|
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.
Method | Return Type | Description |
---|---|---|
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.
Method | Return Type | Description |
---|---|---|
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.
Method | Description |
---|---|
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.")
}