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.

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 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
)
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.