Retrieve the Data
To set up the bracelet and the Android SDK here are the main steps to implement:
- Pair and connect to the bracelet
- Set up the bracelet parameter
- Set up the automatic data transfer between the Bracelet to the Android SDK database
- Retrieve the data
Retrieving the data is handled by the Data SDK: the module that stores raw data from the bracelet and calculates summaries.
What are raw metrics and summaries?
Data flow
Once a device is registered for automatic file transfer, each cycle runs as follows:
BleDevice → File Transfer → Raw Metric → Erase → Summary + Slots
- File Transfer — the SDK requests the file size and, if it is above zero, downloads the file in chunks of at most 10 000 bytes.
- Raw Metric — the payload is mapped to the matching metric object and saved. Once saved it is pushed to the
metric<Type>Updatedlistener and becomes readable through themetric<Type>Repository. - Erase — after a successful save the processed data is deleted from the bracelet with
EraseFileRequest, freeing space for new measurements. - Summary + Slots — the updaters recalculate the summaries and slots affected by the new data, then fire the
<type>SummaryUpdatedlisteners.
You do not drive this loop yourself. Your app registers the device, then reacts to the events and reads the database.
Configuration
All DataSdk.Config parameters have defaults except licenceKey, which is mandatory.
| Parameter | Type | Default | Description |
|---|---|---|---|
licenceKey | String? | null | Mandatory. See Licence Key |
slotsIntervalSecs | Int | 60 | Slot interval for summary generation, in seconds |
ppg2ThresholdFileSize | Int | 500 * 1024 | Minimum PPG2 .wiff file size before a file is produced |
bioZThresholdFileSize | Int | 30 * 1024 | Same, for BioZ |
accThresholdFileSize | Int | 30 * 1024 | Same, for accelerometer |
nibpSlotsIntervalSecs | Int | 7200 (2 h) | Slot interval for blood pressure summaries. Minimum 30 minutes |
automaticReconnectIfNeededIntervalMinutes | Int | 15 | How often the SDK attempts a background reconnect |
logger | SdkLogger? | null | Module logger. Falls back to the one on CorsanoSdkConfig |
dataScheduler, callbackScheduler | Scheduler? | null | Threading for data work and callbacks |
slotsIntervalSecs
The interval of slots in the summary generation. With a 60-second interval the summary slots run 00:00, 00:01, 00:02 and so on.
The minimum value is 60 seconds. A lower value is silently raised to 60.
Summaries are meant to be daily aggregates, not a second copy of the raw metrics — a short interval will bloat the database to no benefit.
File size thresholds
ppg2ThresholdFileSize, bioZThresholdFileSize and accThresholdFileSize each set the minimum size a .wiff file must reach before it is produced. Data syncs continuously from the bracelet, but a file only appears once the accumulated data passes the threshold. This avoids producing a large number of tiny files, at the cost of waiting for data to build up.
Set a threshold to zero for continuous generation: a file is then produced after every sync.
nibpSlotsIntervalSecs
The slot interval for blood pressure summaries, which is separate from slotsIntervalSecs. It defaults to 2 hours and is silently raised to 30 minutes if set lower.
automaticReconnectIfNeededIntervalMinutes
How often a background worker attempts to reconnect to a registered bracelet. Values below 15 minutes are raised to 15, which is the Android WorkManager minimum for periodic work.
Set it to Int.MAX_VALUE to cancel the periodic reconnect worker entirely.
Code example
val dataConfig = DataSdk.Config(
slotsIntervalSecs = 10 * 60,
ppg2ThresholdFileSize = 500 * 1024,
bioZThresholdFileSize = 30 * 1024,
accThresholdFileSize = 30 * 1024,
licenceKey = "my_key" // update your licence key here, if you need a key, see https://corsano.com/contact-us/
)
val config = CorsanoSdkConfig.Builder()
.addModuleConfig(dfuConfig)
.addModuleConfig(dataConfig)
.build()
CorsanoSdk.initialize(this, config)
Entry points
After CorsanoSdk is initialised:
val dataSdk = DataSdk.getInstance()
val manager = dataSdk.getManager() // database, summaries, exports, feature managers
val downloadManager = dataSdk.getDownloadManager() // file transfer control
Note:
getSummaryManager()is deprecated. UsegetManager().
The manager also exposes the feature managers, each documented on its own page: getEcgManager() (ECG), getNibpCalibrationManager() (NIBP), getWearingOptimizationManager() (Wearing Optimization) and getSpotMeasurementManager() (SpO2 spot measurement).
Reading the database
Repositories
Raw metrics:
| Repository | Model |
|---|---|
metricActivityRepository | MetricActivity |
metricPpgRepository | MetricPpg |
metricHrvRepository | MetricHrv |
metricSleepRepository | MetricSleep |
metricTemperatureRepository | MetricTemperature |
metricTemperatureRawRepository | MetricTemperatureRaw |
metricEmographyRepository | MetricEmography |
Summaries:
| Repository | Model |
|---|---|
stepsSummaryRepository | StepsSummaryModel |
heartRateSummaryRepository | HeartRateSummaryModel |
respirationSummaryRepository | RespirationSummaryModel |
spo2SummaryRepository | SpO2SummaryModel |
temperatureSummaryRepository | TemperatureSummaryModel |
sleepSummaryRepository | SleepSummaryModel |
bloodPressureSummaryRepository | MetricBloodPressureSummary |
emographySummaryRepository | EmographySummaryModel |
Measurements and device state:
| Repository | Contents |
|---|---|
ecgRepository | ECG measurements |
nibpCalibrationRepository | NIBP calibration sessions |
spotMeasurementRepository | SpO2 spot measurements |
woRepository | Wearing Optimization measurements |
rawFileRepository | .wiff file chunks |
braceletInfoRepository | Bracelet information |
localSettingRepository | Local settings |
The full method list for each is documented with the models — see Raw data models and Summaries.
Get raw metrics
Results arrive through a callback rather than being returned:
val manager = DataSdk.getInstance().getManager()
manager.metricActivityRepository.getByTimestampRange(startTimestamp, endTimestamp) { metrics ->
metrics?.forEach { Log.d("Corsano-SDK", it.toString()) }
}
Pass groupedBySeconds to get fixed-size time chunks instead of every sample. groupedBySeconds = 5 * 60 returns the data in 5-minute chunks, each an average of the samples in the window — a weighted average where a quality factor is available:
manager.metricActivityRepository.getByTimestampRange(
startTimestamp,
endTimestamp,
groupedBySeconds = 5 * 60
) { metrics ->
// one entry per 5-minute window
}
Supported on MetricActivityRepository, MetricTemperatureRepository, MetricSleepRepository, MetricHrvRepository, MetricEmographyRepository and MetricPpgRepository.
Get a summary
manager.stepsSummaryRepository.getByDate("2021-11-10") { summary ->
Log.d("Corsano-SDK", "Steps: ${summary?.stepCount}")
}
Delete data
Raw metrics grow quickly, so delete them regularly.
Warning: For metrics that feed a summary — activity, respiration, temperature, SpO2, heart rate — the calculation reads the whole day. Keep the last 2 days so summaries stay correct in every case. PPG produces no summary and can be deleted at any time.
manager.metricActivityRepository.deleteByTimestampRange(startTimestamp, endTimestamp)
manager.metricActivityRepository.deleteAll()
manager.stepsSummaryRepository.deleteByDate("2021-11-10")
manager.stepsSummaryRepository.deleteByTimestampRange(startTimestamp, endTimestamp)
manager.stepsSummaryRepository.deleteAll()
Summaries are much smaller than raw metrics, but clearing old ones still frees space.
Full data model description: Data Models.
Data events
Rather than polling, subscribe to the updaters. Each is a ValueUpdated<T> exposing addListener and removeListener.
Summary updaters
| Updater | Delivers |
|---|---|
stepsSummaryUpdated | StepsSummaryModel |
heartRateSummaryUpdated | HeartRateSummaryModel |
respirationSummaryUpdated | RespirationSummaryModel |
spo2SummaryUpdated | SpO2SummaryModel |
temperatureSummaryUpdated | TemperatureSummaryModel |
sleepSummaryUpdated | SleepSummaryModel |
bloodPressureSummaryUpdated | MetricBloodPressureSummary |
emographySummaryUpdated | EmographySummaryModel |
Raw metric updaters
These deliver a list of the records just saved.
| Updater | Delivers |
|---|---|
metricActivityUpdated | List<MetricActivity> |
metricHrvUpdated | List<MetricHrv> |
metricPpgUpdated | List<MetricPpg> |
metricSleepUpdated | List<MetricSleep> |
metricTemperatureUpdated | List<MetricTemperature> |
metricTemperatureRawUpdated | List<MetricTemperatureRaw> |
metricEdaUpdated | List<MetricEmography> |
Note: The EmoGraphy updater is
metricEdaUpdated, notmetricEmographyUpdated— it does not follow the naming of its repository.
File updaters
| Updater | Delivers | Fires when |
|---|---|---|
wiffFileUpdated | RawFileChunk | A new PPG2, ACC or BioZ .wiff chunk is written |
ecgWiffFileUpdated | EcgMeasurementModel | A new ECG measurement file is written |
Example
private val stepsSummaryListener = object : ValueUpdated.Listener<StepsSummaryModel> {
override fun onValueUpdated(model: StepsSummaryModel) {
view?.findViewById<TextView>(R.id.action_result)?.text = model.toString()
}
}
override fun onStart() {
super.onStart()
manager.stepsSummaryUpdated.addListener(stepsSummaryListener)
}
override fun onStop() {
manager.stepsSummaryUpdated.removeListener(stepsSummaryListener)
super.onStop()
}
Warning: Always remove your listener. The manager is a singleton that outlives your fragments and activities, so a listener left registered keeps them alive.
Sample app: DataActivityFragment.kt for summary updaters, DataMeasurementFragment.kt for wiffFileUpdated, DataSleepFragment.kt for sleep processing.
Sleep processing events
Sleep is processed asynchronously after the sleep file is transferred, so its result arrives separately through manager.sleepProcessUpdater.
enum class SleepProcessResult {
UNKNOWN, // Deprecated
STARTED,
SUCCESS, // processed and interpreted, a SleepSummary is available
ERROR_NO_SLEEP_FILE, // no sleep data found or synced from the bracelet
ERROR_SLEEP_FILE_PARSING_WILL_REPROCESS, // failed, will retry up to 3 times, 2 minutes apart
ERROR_SLEEP_FILE_PARSING_WILL_NOT_REPROCESS // failed, no further retry
}
See Sleep processing.
Exporting data
exportData writes the stored data to CSV files in a folder the user has granted access to.
fun exportData(
fileTypes: List<ExportType>,
context: Context,
documentFile: DocumentFile,
from: Long,
to: Long,
onDone: () -> Unit,
onError: (Throwable) -> Unit
)
| Parameter | Type | Description |
|---|---|---|
fileTypes | List<ExportType> | What to export: ACTIVITY, SLEEP, HRV, PPG, TEMPERATURE, TEMPERATURE_RAW |
context | Context | Android context |
documentFile | DocumentFile | Destination folder, obtained from the user via the system folder picker |
from, to | Long | Time range in UTC milliseconds |
onDone | () -> Unit | Called when the export finishes |
onError | (Throwable) -> Unit | Called if it fails |
A second overload takes groupedBySeconds: Int before the callbacks, down-sampling the exported rows the same way the repositories do.
manager.exportData(
fileTypes = listOf(ExportType.ACTIVITY, ExportType.HRV),
context = requireContext(),
documentFile = pickedFolder,
from = startTimestamp,
to = endTimestamp,
onDone = { Log.d("Corsano-SDK", "Export finished") },
onError = { Log.e("Corsano-SDK", "Export failed: $it") }
)
For exporting the raw .wiff files rather than CSV, see Raw File Transfer & Export.
Other manager methods
getBraceletCapacity
fun getBraceletCapacity(
bleDevice: BleDevice,
hardwareId: HardwareId,
onDone: (Double) -> Unit,
onError: (Throwable) -> Unit
)
How full the bracelet's storage is, as a percentage. Useful for warning a user before measurements are lost. A suspend variant, getBraceletCapacitySuspend(bleDevice, hardwareId), is also available.
getLogs
fun getLogs(context: Context, documentFile: DocumentFile)
Writes the SDK logs to the given folder for support purposes. See SDK logs.
Controlling downloads
DataSdk.getInstance().getDownloadManager() controls the transfer loop directly. Day-to-day setup is covered in Data transfer; the full surface is:
| Method | Purpose |
|---|---|
subscribeDeviceForDownload(address, hardwareId, plan, onSuccess, onError) | Register a device for automatic download |
unsubscribeDeviceForDownload(address, onSuccess, onError) | Stop automatic download for a device |
triggerDownload(address, hardwareId, plan, filteredFiles) | Run a single download now, optionally limited to certain files |
startContinuousDownload() | Start continuous download |
stopContinuousDownload() | Stop continuous download |
stopAll(context, address, onSuccess, onError) | Stop everything for a device — use when unpairing |
getDownloadClient(address, hardwareId, plan) | The DeviceDownloadClient for finer control |
getProgressLiveData() | LiveData<List<WorkInfo>> for transfer progress |
parseProgressLiveData(listOfWorkInfo) | Turns that into a DownloadProgress? |
Note: The three-argument
stopAll(address, onSuccess, onError)is deprecated. Use the overload that takes aContext.