Skip to main content

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: DeviceManager in 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.

Initialization required​

Warning: From SDK 2.0.0, a command sent to a connected but not-yet-initialized bracelet is rejected with NotInitializedException. Almost every command on this page requires the bracelet to be initialized first. Enable auto-initialization at startup, or initialize the device after connecting — see Bracelet Initialization. The exceptions are the commands that make up the initialization sequence itself (set time, get FW version, authentication, set plan, set sampling rates), which set requiresInitialization = false.

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 value 100, which the SDK maps to true. A false means the bracelet replied but did not acknowledge — it is not an error and onError will not fire.
  • Some Get commands return a nullable type. GetPlanRequest, GetBraceletStatusRequest, GetSessionStatusRequest and the vital-parameter getters return null when the bracelet's reply does not match the request (for example a different parameter than the one asked for). Treat null as "no usable answer", not as an error.
  • A failed command surfaces through onError / a failed Result, never as a false or null.

Device information​

RequestTypeParametersReturns
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​

RequestTypeParametersReturns
GetTimeRequest()Get—Date
SetTimeRequest()Set—Boolean
GetBedtimeRequest()Get—HourMinuteRecord
GetRisetimeRequest()Get—HourMinuteRecord
SetBedtimeRequest(bedtimeRecord)SetbedtimeRecord: HourMinuteRecordBoolean
SetRisetimeRequest(risetimeRecord)SetrisetimeRecord: HourMinuteRecordBoolean

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​

RequestTypeParametersReturns
GetUserProfileRequest()Get—UserProfileGetRecord
SetUserProfileRequest(userProfile)SetuserProfile: UserProfileSetRecordBoolean
GetBirthdayRequest()Get—Response
GetHeightRequest()Get—Int (cm)
SetHeightRequest(weight)Setweight: Int — the height in cm, see the warning belowBoolean
GetWeightRequest()Get—Int (kg)
SetWeightRequest(weight)Setweight: 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 throws IllegalArgumentException when the request is constructed, before it ever reaches the bracelet.

Warning: SetHeightRequest's constructor parameter is named weight, not height — a naming slip in the SDK. It does set the height. If you use named arguments you have to write SetHeightRequest(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​

RequestTypeParametersReturns
GetPlanRequest()Get—Response?
SetPlanRequest(plan, ppg2Frequency, sampleRateV1)Setsee belowBoolean
GetVitalParameterSamplingRateRequest(type)Gettype: VitalParameterWithSamplingRateTypeSetVitalParameterSamplingRateRequest?
SetVitalParameterSamplingRateRequest(type, samplingRate)Settype, samplingRate: SampleRateBoolean

SetPlanRequest​

class SetPlanRequest(
val plan: DevicePlan,
val ppg2Frequency: Ppg2Frequency = Ppg2Frequency.THIRTY_TWO_HZ,
val sampleRateV1: Int = 60 // only used by FW < 5.67
)
ParameterTypeDefaultDescription
planDevicePlan—The measurement plan. See Bracelet Plans
ppg2FrequencyPpg2FrequencyTHIRTY_TWO_HZPPG2 sampling frequency. B2 only
sampleRateV1Int60Legacy 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.

RequestTypeParametersReturns
GetEmoGraphyAndBiozPlan()Get—VitalParametersRequest?
SetEmographyAndBiozPlan(emographyValue, biozValue)Settwo VitalParameterValueBoolean
GetAccelerometerPlan()Get—VitalParametersRequest?
SetAccelerometerPlan(value)Setvalue: VitalParameterValueBoolean
GetTemperatureRawPlan()Get—VitalParametersRequest?
SetTemperatureRawPlan(value)Setvalue: VitalParameterValueBoolean
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​

RequestTypeParametersReturns
GetFileSizeRequest(file)Getfile: FileOnWatchFileSize
EraseFileRequest(file, size)Setfile: FileOnWatch, size: LongBoolean
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.

RequestTypeReturnsDocumented in
StartEcgMeasurement()ActionBooleanECG
StopEcgMeasurement()ActionBooleanECG
StartNibpCalibrationMeasurement()ActionBooleanNIBP
StopNibpCalibrationMeasurement()ActionBooleanNIBP
ResetNibpCalibrationRequest()ActionBooleanNIBP
GetNibpTrainingInfoRequest()GetResponseNIBP
SetBpCuffCalibrationRequest(...)SetSetBpCuffCalibrationRequestModelNIBP
StartWearingOptimizationMeasurement()ActionBooleanWearing Optimization
StopWearingOptimizationMeasurement()ActionBooleanWearing Optimization
GetWearingOptimizationStatusRequest()GetResponseWearing Optimization
StartSpotMeasurementCmd()ActionBooleanSpO2 spot measurement
SetSpotMeasurementRequest(value)SetBooleanSpO2 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​

RequestTypeParametersReturns
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​

RequestTypeParametersReturns
GetRealTimeEnabledRequest()Get—Boolean
SetRealTimePingEnableRequest(value)Setvalue: BooleanBoolean
GetRealTimePeriodRequest()Get—Int (seconds)
SetRealTimePingPeriodRequest(periodSecs)SetperiodSecs: IntBoolean

Real-time pings push live vitals without the app asking. The pings themselves arrive as notifications — see Instant data.

Note: SetRealTimePingPeriodRequest shares the 0..255 constraint described under User profile; a period outside that range throws IllegalArgumentException at construction.

Authentication​

From SDK 2.0.0 and FW 8.48. Part of the initialization sequence — you rarely send these directly. See Firmware authentication.

RequestTypeCommandParametersReturns
SetAuthenticationRequest(phoneId)Action250phoneId: String (pass Settings.Secure.ANDROID_ID)Boolean
GetAuthenticationRequest()Get251—Boolean

Device actions​

RequestTypeParametersReturns
ResetWatchRequest()Set—Boolean
ShutdownWatchRequest()Set—Boolean
LedTestWatchRequestV2(ledNumber, blinking, durationSecs)Setsee belowBoolean
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:

ParameterTypeDescription
ledNumberInt0 = green, 1 = orange, 2 = blue
blinkingBooleantrue to blink, false for continuous
durationSecsIntDuration 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.

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