Skip to main content

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>Updated listener and becomes readable through the metric<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>SummaryUpdated listeners.

You do not drive this loop yourself. Your app registers the device, then reacts to the events and reads the database.

Since 2.3.0: the Data SDK also sends a usage report for the connected bracelet to the Corsano cloud every 12 hours. See SDK usage report for what it contains.

Configuration​

All DataSdk.Config parameters have defaults except licenceKey, which is mandatory.

ParameterTypeDefaultDescription
licenceKeyString?nullMandatory. See Licence Key
slotsIntervalSecsInt60Slot interval for summary generation, in seconds
ppg2ThresholdFileSizeInt500 * 1024Minimum PPG2 .wiff file size before a file is produced
bioZThresholdFileSizeInt30 * 1024Same, for BioZ
accThresholdFileSizeInt30 * 1024Same, for accelerometer
nibpSlotsIntervalSecsInt7200 (2 h)Slot interval for blood pressure summaries. Minimum 30 minutes
automaticReconnectIfNeededIntervalMinutesInt15How often the SDK attempts a background reconnect
loggerSdkLogger?nullModule logger. Falls back to the one on CorsanoSdkConfig
dataScheduler, callbackSchedulerScheduler?nullThreading 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. Use getManager().

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:

RepositoryModel
metricActivityRepositoryMetricActivity
metricPpgRepositoryMetricPpg
metricHrvRepositoryMetricHrv
metricSleepRepositoryMetricSleep
metricTemperatureRepositoryMetricTemperature
metricTemperatureRawRepositoryMetricTemperatureRaw
metricEmographyRepositoryMetricEmography

Summaries:

RepositoryModel
stepsSummaryRepositoryStepsSummaryModel
heartRateSummaryRepositoryHeartRateSummaryModel
respirationSummaryRepositoryRespirationSummaryModel
spo2SummaryRepositorySpO2SummaryModel
temperatureSummaryRepositoryTemperatureSummaryModel
sleepSummaryRepositorySleepSummaryModel
bloodPressureSummaryRepositoryMetricBloodPressureSummary
emographySummaryRepositoryEmographySummaryModel

Measurements and device state:

RepositoryContents
ecgRepositoryECG measurements
nibpCalibrationRepositoryNIBP calibration sessions
spotMeasurementRepositorySpO2 spot measurements
woRepositoryWearing Optimization measurements
rawFileRepository.wiff file chunks
braceletInfoRepositoryBracelet information
localSettingRepositoryLocal 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​

UpdaterDelivers
stepsSummaryUpdatedStepsSummaryModel
heartRateSummaryUpdatedHeartRateSummaryModel
respirationSummaryUpdatedRespirationSummaryModel
spo2SummaryUpdatedSpO2SummaryModel
temperatureSummaryUpdatedTemperatureSummaryModel
sleepSummaryUpdatedSleepSummaryModel
bloodPressureSummaryUpdatedMetricBloodPressureSummary
emographySummaryUpdatedEmographySummaryModel

Raw metric updaters​

These deliver a list of the records just saved.

UpdaterDelivers
metricActivityUpdatedList<MetricActivity>
metricHrvUpdatedList<MetricHrv>
metricPpgUpdatedList<MetricPpg>
metricSleepUpdatedList<MetricSleep>
metricTemperatureUpdatedList<MetricTemperature>
metricTemperatureRawUpdatedList<MetricTemperatureRaw>
metricEdaUpdatedList<MetricEmography>

Note: The EmoGraphy updater is metricEdaUpdated, not metricEmographyUpdated — it does not follow the naming of its repository.

File updaters​

UpdaterDeliversFires when
wiffFileUpdatedRawFileChunkA new PPG2, ACC or BioZ .wiff chunk is written
ecgWiffFileUpdatedEcgMeasurementModelA 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​

Changed in 2.2.0: Sleep is now computed on the bracelet and downloaded, so there is no on-app processing event. manager.sleepProcessUpdater and the SleepProcessResult type were removed. To bring the sleep state up to date after a sync, call the suspend function manager.processSleepIfNeeded(); to read the current state for a day, call manager.getSleepState(dayDate).

See Sleep processing, including the migration steps from 2.1.x.

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
)
ParameterTypeDescription
fileTypesList<ExportType>What to export: ACTIVITY, SLEEP, HRV, PPG, TEMPERATURE, TEMPERATURE_RAW
contextContextAndroid context
documentFileDocumentFileDestination folder, obtained from the user via the system folder picker
from, toLongTime range in UTC milliseconds
onDone() -> UnitCalled when the export finishes
onError(Throwable) -> UnitCalled 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:

MethodPurpose
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 a Context.