BLE commands
A command is a single request/response exchange with the bracelet. You build a request object, enqueue it on a connected BleDevice, and receive a typed result.
This page is the command reference. For how commands are executed, queued and how errors surface, see BLE SDK.
Note:
DeviceManagerin these examples is a sample-app helper, not SDK API. See Pairing for what to use instead.
Sending a command
Callback style:
DeviceManager.getCurrentDevice()?.enqueueCommand(
request = ResetWatchRequest(),
onSuccess = { Log.d("Corsano-SDK", "Reset successful, the bracelet will restart") },
onError = { Log.e("Corsano-SDK", "Reset error $it") }
)
Coroutine style (recommended — see BLE SDK):
val result: Result<Boolean> = bleDevice.awaitCommandV2(ResetWatchRequest(), timeout = 10_000)
Sample app: DataOthersFragment.kt in the sample module (sdk/sample/src/main/java/com/corsano/sdk/sample/screens/data/tabs/) drives reset, shutdown, erase-all, battery and bracelet status from buttons — the closest thing to a command playground.
Return value conventions
Three conventions run through the whole command set, and they are worth knowing before reading the tables:
- Set commands return
Boolean. The bracelet acknowledges with the value100, which the SDK maps totrue. Afalsemeans the bracelet replied but did not acknowledge — it is not an error andonErrorwill not fire. - Some Get commands return a nullable type.
GetPlanRequest,GetBraceletStatusRequest,GetSessionStatusRequestand the vital-parameter getters returnnullwhen the bracelet's reply does not match the request (for example a different parameter than the one asked for). Treatnullas "no usable answer", not as an error. - A failed command surfaces through
onError/ a failedResult, never as afalseornull.
Device information
| Request | Type | Parameters | Returns |
|---|---|---|---|
GetFwVersionRequest() | Get | — | FirmwareVersion |
GetHwIdRequest() | Get | — | HardwareId |
GetModelIdRequest() | Get | — | Int |
GetSerialNumberRequest() | Get | — | String |
GetBrandRequest() | Get | — | String |
GetDeviceNameRequest() | Get | — | String |
GetBatteryLevelRequest() | Get | — | BatteryLevel |
GetActiveModeRequest() | Get | — | DeviceMode |
GetBraceletStatusRequest() | Get | — | Response? |
GetSessionStatusRequest() | Get | — | Response? |
BatteryLevel
data class BatteryLevel(
val level: Int, // percentage
val voltage: Int,
val isCharging: Boolean
)
Sample app: DataOthersFragment.kt, and DeviceImpl.kt which polls it to keep the device model up to date.
GetBraceletStatusRequest.Response
Wearing, charging and motion state in one call.
data class Response(
val wearingState: Int, // 0..4. 0 = not wearing, 4 = good wearing.
// 0, 1 and 2 are worth warning the user about.
val modeState: Int, // 1 = sleep-idle mode to save battery, 0 = active
val batteryPercentage: Int,
val isCharging: Boolean,
val idleState: Int // 0..4. 0 = completely still, 4 = in motion.
)
Returns null if the bracelet's reply cannot be interpreted.
Sample app: DataOthersFragment.kt.
GetSessionStatusRequest.Response
data class Response(
val mode: Int,
val sessionNumber: Int,
val startTime: Int,
val durationSecs: Int,
val status: SessionStatus // NOT_STARTED, ON_GOING, FINISHED
)
DeviceMode
Returned by GetActiveModeRequest.
enum class DeviceMode(val id: Int) {
SHIPPING(0), POWER_SAVING(1), HRM(2), SLEEP_TRACKING(3),
PREVENTICUS(4), PREVENTICUS_SLEEP(5), WORKOUT(6),
NORMAL(7), CHARGING(8), MAX_BATTERY(9)
}
HardwareId
Returned by GetHwIdRequest. See DFU SDK for the values and which bracelets are supported.
Time
| Request | Type | Parameters | Returns |
|---|---|---|---|
GetTimeRequest() | Get | — | Date |
SetTimeRequest() | Set | — | Boolean |
GetBedtimeRequest() | Get | — | HourMinuteRecord |
GetRisetimeRequest() | Get | — | HourMinuteRecord |
SetBedtimeRequest(bedtimeRecord) | Set | bedtimeRecord: HourMinuteRecord | Boolean |
SetRisetimeRequest(risetimeRecord) | Set | risetimeRecord: HourMinuteRecord | Boolean |
SetTimeRequest takes no parameter — it writes the phone's current time to the bracelet.
Bedtime and risetime are GMT, and they gate sleep monitoring. See Sleep Settings.
data class HourMinuteRecord(val hour: Int, val minute: Int)
Sample app: DataStartFragment.kt sends SetTimeRequest as part of bracelet setup.
User profile
| Request | Type | Parameters | Returns |
|---|---|---|---|
GetUserProfileRequest() | Get | — | UserProfileGetRecord |
SetUserProfileRequest(userProfile) | Set | userProfile: UserProfileSetRecord | Boolean |
GetBirthdayRequest() | Get | — | Response |
GetHeightRequest() | Get | — | Int (cm) |
SetHeightRequest(weight) | Set | weight: Int — the height in cm, see the warning below | Boolean |
GetWeightRequest() | Get | — | Int (kg) |
SetWeightRequest(weight) | Set | weight: Int (kg) | Boolean |
data class UserProfileSetRecord(
val birthdayYear: Int,
val birthdayMonth: Int,
val birthdayDay: Int,
val gender: Gender, // MALE, FEMALE, UNSPECIFIED
val wrist: Wrist, // LEFT, RIGHT
val skinType: SkinType? = null
)
data class UserProfileGetRecord(
val age: Int,
val gender: Gender,
val wrist: Wrist,
val skinType: SkinType? = null
)
Note the asymmetry: you set a birthdate but get an age.
GetBirthdayRequest returns Response(year, month, day).
Note: Height and weight go through a shared base that requires the value to be in
0..255. Passing anything outside that range throwsIllegalArgumentExceptionwhen the request is constructed, before it ever reaches the bracelet.
Warning:
SetHeightRequest's constructor parameter is namedweight, notheight— a naming slip in the SDK. It does set the height. If you use named arguments you have to writeSetHeightRequest(weight = 180); positional arguments are unaffected.
Skin type and wrist matter for measurement accuracy — see Wearing Optimization.
Sample app: UserProfileViewModel.kt.
Plan and sampling rate
| Request | Type | Parameters | Returns |
|---|---|---|---|
GetPlanRequest() | Get | — | Response? |
SetPlanRequest(plan, ppg2Frequency, sampleRateV1) | Set | see below | Boolean |
GetVitalParameterSamplingRateRequest(type) | Get | type: VitalParameterWithSamplingRateType | SetVitalParameterSamplingRateRequest? |
SetVitalParameterSamplingRateRequest(type, samplingRate) | Set | type, samplingRate: SampleRate | Boolean |
SetPlanRequest
class SetPlanRequest(
val plan: DevicePlan,
val ppg2Frequency: Ppg2Frequency = Ppg2Frequency.THIRTY_TWO_HZ,
val sampleRateV1: Int = 60 // only used by FW < 5.67
)
| Parameter | Type | Default | Description |
|---|---|---|---|
plan | DevicePlan | — | The measurement plan. See Bracelet Plans |
ppg2Frequency | Ppg2Frequency | THIRTY_TWO_HZ | PPG2 sampling frequency. B2 only |
sampleRateV1 | Int | 60 | Legacy sampling rate, only honoured by firmware older than 5.67 |
bleDevice.enqueueCommand(
request = SetPlanRequest(DevicePlan.HOSPITAL_MULTICOLOR),
onSuccess = { Log.d("Corsano-SDK", "Plan set: $it") },
onError = { Log.e("Corsano-SDK", "SetPlanRequest error: $it") }
)
GetPlanRequest returns Response(devicePlan, ppg2Frequency, sampleRateV1), all but the first nullable, or null if the reply is unusable.
Sample app: DataStartFragment.kt.
Sampling rates
GetVitalParameterSamplingRateRequest has an unusual signature: it returns a SetVitalParameterSamplingRateRequest — the setter object — rather than a plain value. Read .type and .samplingRate off it. It returns null if the bracelet answers about a different parameter, or reports a rate that does not correspond to a known SampleRate.
enum class VitalParameterWithSamplingRateType {
ACTIVITY, PULSE_RATE, RESPIRATION_RATE, SPO2, TEMPERATURE, NIBP
}
enum class SampleRate(val rateSecs: Int, val friendlyName: String) {
OFF(0, "OFF"),
THIRTY_SECS(30, "1/30 secs"),
ONE_MIN(60, "1/minute"),
FIVE_MIN(5 * 60, "1/5 minutes"),
THIRTY_MIN(30 * 60, "1/30 minutes")
}
See Set Sampling rate.
Raw stream enablement
These control whether EmoGraphy, BioZ, accelerometer and raw temperature are recorded. All of them are VitalParameterWatchType variants of the same underlying pair of commands.
| Request | Type | Parameters | Returns |
|---|---|---|---|
GetEmoGraphyAndBiozPlan() | Get | — | VitalParametersRequest? |
SetEmographyAndBiozPlan(emographyValue, biozValue) | Set | two VitalParameterValue | Boolean |
GetAccelerometerPlan() | Get | — | VitalParametersRequest? |
SetAccelerometerPlan(value) | Set | value: VitalParameterValue | Boolean |
GetTemperatureRawPlan() | Get | — | VitalParametersRequest? |
SetTemperatureRawPlan(value) | Set | value: VitalParameterValue | Boolean |
enum class VitalParameterValue { DISABLE, CONTINUOUS, BEDTIME_RISETIME, UNKNOWN }
data class VitalParametersRequest(
val type: VitalParameterWatchType, // ACCELEROMETER, BIOZ, TEMPERATURE_RAW, UNKNOWN
val param1: VitalParameterValue,
val param2: VitalParameterValue?
)
The getters return null when the bracelet replies about a different parameter than the one requested.
Full walkthrough: Enable EmoGraphy, BioZ & Accelerometer.
Files
| Request | Type | Parameters | Returns |
|---|---|---|---|
GetFileSizeRequest(file) | Get | file: FileOnWatch | FileSize |
EraseFileRequest(file, size) | Set | file: FileOnWatch, size: Long | Boolean |
EraseAllRequest() | Set | — | Boolean |
FileOnWatch values: ACTIVITY, PPG, PPG2, HRV, SLEEP, ECG, BIOZ, ACC_FILE, STRESS_FILE, GREENTEG, WORKOUT, LOGS.
FileSize
class FileSize(val file: FileOnWatch, val sizeInBytes: Long) {
fun getCapacity(hardwareId: HardwareId): Double?
}
getCapacity gives how full the file is as a percentage: 0.0 when empty, 100.0 when full, and null when not applicable — for instance PPG2 on a B1, which has no such file. Note it returns a Double, so format it before displaying.
bleDevice.enqueueCommand(
request = GetFileSizeRequest(FileOnWatch.ACTIVITY),
onSuccess = {
val hardwareId = DeviceManager.getCurrentDevice()?.deviceInfo?.hardwareId ?: HardwareId.UNKNOWN
Log.d("Corsano-SDK", "File size $it and capacity ${it.getCapacity(hardwareId)}")
},
onError = { }
)
EraseAllRequest erases every data file on the bracelet — activity, sleep, hrv, ppg and the rest.
Sample app: DataActivityFragment.kt and DataMeasurementFragment.kt for file size; DataOthersFragment.kt for erase-all.
Note: In normal use you do not send file commands yourself. The automatic file transfer reads and erases files for you.
Measurements
Each of these has a dedicated page that covers the full sequence — the commands are listed here only for completeness.
| Request | Type | Returns | Documented in |
|---|---|---|---|
StartEcgMeasurement() | Action | Boolean | ECG |
StopEcgMeasurement() | Action | Boolean | ECG |
StartNibpCalibrationMeasurement() | Action | Boolean | NIBP |
StopNibpCalibrationMeasurement() | Action | Boolean | NIBP |
ResetNibpCalibrationRequest() | Action | Boolean | NIBP |
GetNibpTrainingInfoRequest() | Get | Response | NIBP |
SetBpCuffCalibrationRequest(...) | Set | SetBpCuffCalibrationRequestModel | NIBP |
StartWearingOptimizationMeasurement() | Action | Boolean | Wearing Optimization |
StopWearingOptimizationMeasurement() | Action | Boolean | Wearing Optimization |
GetWearingOptimizationStatusRequest() | Get | Response | Wearing Optimization |
StartSpotMeasurementCmd() | Action | Boolean | SpO2 spot measurement |
SetSpotMeasurementRequest(value) | Set | Boolean | SpO2 spot measurement |
Note: Prefer the managers —
EcgManager,NibpCalibrationManager,WearingOptimizationManager,SpotMeasurementManager— over sending these commands directly. They handle the ordering, retries and database writes that a raw command sequence does not.
Sleep
| Request | Type | Parameters | Returns |
|---|---|---|---|
SetStopSleepRequest() | Set | — | Int |
Stops the current sleep session early. Note this one returns an Int, not the Boolean that Set commands normally return, because it is built on the raw single-byte base class.
See Sleep Settings.
Real-time ping
| Request | Type | Parameters | Returns |
|---|---|---|---|
GetRealTimeEnabledRequest() | Get | — | Boolean |
SetRealTimePingEnableRequest(value) | Set | value: Boolean | Boolean |
GetRealTimePeriodRequest() | Get | — | Int (seconds) |
SetRealTimePingPeriodRequest(periodSecs) | Set | periodSecs: Int | Boolean |
Real-time pings push live vitals without the app asking. The pings themselves arrive as notifications — see Instant data.
Note:
SetRealTimePingPeriodRequestshares the0..255constraint described under User profile; a period outside that range throwsIllegalArgumentExceptionat construction.
Device actions
| Request | Type | Parameters | Returns |
|---|---|---|---|
ResetWatchRequest() | Set | — | Boolean |
ShutdownWatchRequest() | Set | — | Boolean |
LedTestWatchRequestV2(ledNumber, blinking, durationSecs) | Set | see below | Boolean |
Led1TestWatchRequest() | Set | — | Boolean |
Led2TestWatchRequest() | Set | — | Boolean |
Reset
Restarts the firmware. The LEDs turn off, then on.
Warning: Resend your setup commands after a reset — time, user profile and bracelet plan. The firmware may restore them itself, but it does not always, so do not rely on it.
Shutdown
Turns the bracelet off. It stops measuring and disappears from scans, since it no longer advertises over BLE. Connecting the charger brings it back, and its LEDs turn on when it restarts.
LED test
Turns on an LED on the side of the bracelet for a few seconds.
LedTestWatchRequestV2 — from SDK 1.1.5 and FW 6.87:
| Parameter | Type | Description |
|---|---|---|
ledNumber | Int | 0 = green, 1 = orange, 2 = blue |
blinking | Boolean | true to blink, false for continuous |
durationSecs | Int | Duration in seconds |
Led1TestWatchRequest and Led2TestWatchRequest are the older, parameterless equivalents.
Sample app: DataStartFragment.kt for the LED test; DataOthersFragment.kt for reset and shutdown.
File transfer requests
File transfers are enqueued with enqueueFileTransfer rather than enqueueCommand, and each takes the file size in bytes obtained from GetFileSizeRequest. See BLE SDK.
| Request | Returns |
|---|---|
ActivityFileTransferRequest(sizeInBytes) | StructFileData<ActivityDataRecord> |
PpgFileTransferRequest(sizeInBytes) | StructFileData<PpgChunk> |
HrvFileTransferRequest(sizeInBytes) | StructFileData<HrvDataRecord> |
EmographyFileTransferRequest(sizeInBytes) | StructFileData<EmographyDataRecord> |
TemperatureRawFileTransferRequest(sizeInBytes) | StructFileData<TemperatureRawDataRecord> |
LogFileTransferRequest(size) | StructFileData<String> |
Ppg2FileTransferRequest(size) | ByteArray |
AccFileTransferRequest(size) | ByteArray |
BiozFileTransferRequest(size) | ByteArray |
EcgFileTransferRequest(size) | ByteArray |
SleepFileTransferRequest(size) | ByteArray |
data class StructFileData<T>(
val items: List<T>,
val totalBytes: Int,
val processedBytes: Int
)
The transfers returning ByteArray deliver raw .wiff content, which the SDK writes to disk. To decode it into samples, see Parsing Raw Files.