From e0e32b815ae53fb44771eb4d6a630f1bd4071f4f Mon Sep 17 00:00:00 2001 From: Jukka Oikarinen Date: Mon, 30 Jan 2023 10:28:59 +0200 Subject: [PATCH 01/58] Android SDK source 5.0.0 --- .../android-communications/build.gradle | 2 +- .../android-communications/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- .../library/build.gradle | 2 +- .../exceptions/BleControlPointCommandError.kt | 3 +- .../BleControlPointResponseError.kt | 8 + .../api/ble/exceptions/SecurityError.kt | 5 + .../ble/model/gatt/client/BleDisClient.java | 2 +- .../ble/model/gatt/client/pmd/BlePMDClient.kt | 153 +- .../gatt/client/pmd/PmdActiveMeasurement.kt | 24 + .../gatt/client/pmd/PmdControlPointCommand.kt | 7 +- .../gatt/client/pmd/PmdMeasurementType.kt | 24 +- .../gatt/client/pmd/PmdOfflineTrigger.kt | 75 + .../model/gatt/client/pmd/PmdRecordingType.kt | 23 + .../ble/model/gatt/client/pmd/PmdSdkMode.kt | 16 + .../ble/model/gatt/client/pmd/PmdSecret.kt | 93 + .../ble/model/gatt/client/pmd/PmdSetting.kt | 6 +- .../model/gatt/client/pmd/model/GyrData.kt | 1 + .../model/gatt/client/pmd/model/MagData.kt | 1 + .../gatt/client/pmd/model/OfflineHrData.kt | 39 + .../client/pmd/model/PmdDataModelUtils.kt | 34 + .../model/gatt/client/pmd/model/PpgData.kt | 2 +- .../offlinerecording/OfflineRecordingData.kt | 338 ++++ .../offlinerecording/OfflineRecordingError.kt | 12 + .../OfflineRecordingUtility.kt | 19 + .../common/ble/TypeUtils.kt | 4 + .../bluedroid/host/BDDeviceListenerImpl.java | 2 + .../bluedroid/host/BDDeviceSessionImpl.java | 6 +- .../ble/bluedroid/host/GattCallback.kt | 19 +- .../host/connection/ConnectionHandler.kt | 6 +- .../sdk/java/com/polar/sdk/api/PolarBleApi.kt | 396 +--- .../com/polar/sdk/api/PolarBleApiCallback.kt | 45 +- .../sdk/api/PolarBleApiCallbackProvider.kt | 38 +- .../polar/sdk/api/PolarBleApiDefaultImpl.kt | 4 +- .../sdk/api/PolarH10OfflineExerciseApi.kt | 99 + .../polar/sdk/api/PolarOfflineRecordingApi.kt | 188 ++ .../polar/sdk/api/PolarOnlineStreamingApi.kt | 198 ++ .../java/com/polar/sdk/api/PolarSdkModeApi.kt | 52 + .../api/errors/PolarOfflineRecordingError.kt | 7 + .../com/polar/sdk/api/model/PolarHrData.kt | 42 +- .../api/model/PolarOfflineRecordingData.kt | 63 + .../api/model/PolarOfflineRecordingEntry.kt | 31 + .../model/PolarOfflineRecordingTriggerMode.kt | 29 + .../sdk/api/model/PolarRecordingSecret.kt | 21 + .../java/com/polar/sdk/impl/BDBleApiImpl.kt | 1103 ++++++++--- .../model => impl}/utils/PolarDataUtils.kt | 128 +- .../model => impl}/utils/PolarTimeUtils.kt | 2 +- .../model/gatt/client/pmd/BlePmdClientTest.kt | 8 +- .../client/pmd/PmdActiveMeasurementTest.kt | 64 + .../gatt/client/pmd/PmdOfflineTriggerTest.kt | 130 ++ .../model/gatt/client/pmd/PmdSecretTest.kt | 172 ++ .../gatt/client/pmd/model/AccDataTest.kt | 28 +- .../client/pmd/model/GnssLocationDataTest.kt | 1 + .../client/pmd/model/OfflineHrDataTest.kt | 58 + .../gatt/client/pmd/model/PpiDataTest.kt | 15 +- .../OfflineRecordingDataTest.kt | 1736 +++++++++++++++++ .../common/ble/TypeUtilsTest.kt | 46 +- .../sdk/api/model/utils/PolarTimeUtilsTest.kt | 1 + 58 files changed, 4866 insertions(+), 771 deletions(-) create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/BleControlPointResponseError.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/SecurityError.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdActiveMeasurement.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdOfflineTrigger.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdRecordingType.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSdkMode.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSecret.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/OfflineHrData.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PmdDataModelUtils.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingData.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingError.kt create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtility.kt create mode 100644 sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarH10OfflineExerciseApi.kt create mode 100644 sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOfflineRecordingApi.kt create mode 100644 sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOnlineStreamingApi.kt create mode 100644 sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarSdkModeApi.kt create mode 100644 sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/errors/PolarOfflineRecordingError.kt create mode 100644 sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingData.kt create mode 100644 sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingEntry.kt create mode 100644 sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingTriggerMode.kt create mode 100644 sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarRecordingSecret.kt rename sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/{api/model => impl}/utils/PolarDataUtils.kt (55%) rename sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/{api/model => impl}/utils/PolarTimeUtils.kt (98%) create mode 100644 sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdActiveMeasurementTest.kt create mode 100644 sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdOfflineTriggerTest.kt create mode 100644 sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSecretTest.kt create mode 100644 sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/OfflineHrDataTest.kt create mode 100644 sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingDataTest.kt diff --git a/sources/Android/android-communications/build.gradle b/sources/Android/android-communications/build.gradle index 5679ee2b..173db155 100755 --- a/sources/Android/android-communications/build.gradle +++ b/sources/Android/android-communications/build.gradle @@ -10,7 +10,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:7.3.1' + classpath 'com.android.tools.build:gradle:7.4.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.google.protobuf:protobuf-gradle-plugin:$protobuf_version" } diff --git a/sources/Android/android-communications/gradle.properties b/sources/Android/android-communications/gradle.properties index d015431a..dbb7bf70 100644 --- a/sources/Android/android-communications/gradle.properties +++ b/sources/Android/android-communications/gradle.properties @@ -1,2 +1,2 @@ +android.enableJetifier=true android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file diff --git a/sources/Android/android-communications/gradle/wrapper/gradle-wrapper.properties b/sources/Android/android-communications/gradle/wrapper/gradle-wrapper.properties index 449ed4d1..3fe3e077 100644 --- a/sources/Android/android-communications/gradle/wrapper/gradle-wrapper.properties +++ b/sources/Android/android-communications/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Oct 24 09:46:15 EEST 2022 +#Mon Jan 16 07:53:38 EET 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/sources/Android/android-communications/library/build.gradle b/sources/Android/android-communications/library/build.gradle index dc8e0ae7..6dc8f9a9 100755 --- a/sources/Android/android-communications/library/build.gradle +++ b/sources/Android/android-communications/library/build.gradle @@ -80,7 +80,7 @@ android { } tasks.dokkaJavadoc.configure { - outputDirectory.set(file("docs/html")) + outputDirectory.set(file("docs/")) dokkaSourceSets { named("main") { perPackageOption { diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/BleControlPointCommandError.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/BleControlPointCommandError.kt index 6caff0d9..aec0badd 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/BleControlPointCommandError.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/BleControlPointCommandError.kt @@ -8,5 +8,4 @@ import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdControlP class BleControlPointCommandError( message: String, val error: PmdControlPointResponse.PmdControlPointResponseCode -) : - Exception("$message failed with error: $error") \ No newline at end of file +) : Exception("$message $error") \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/BleControlPointResponseError.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/BleControlPointResponseError.kt new file mode 100644 index 00000000..091ccb9a --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/BleControlPointResponseError.kt @@ -0,0 +1,8 @@ +package com.polar.androidcommunications.api.ble.exceptions + +/** + * Error indicating the control point response has a problem + */ +class BleControlPointResponseError( + message: String, +) : Exception(message) \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/SecurityError.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/SecurityError.kt new file mode 100644 index 00000000..59007065 --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/exceptions/SecurityError.kt @@ -0,0 +1,5 @@ +package com.polar.androidcommunications.api.ble.exceptions + +sealed class SecurityError(detailMessage: String) : Exception(detailMessage) { + class SecurityStrategyUnknown(message: String) : SecurityError(message) +} diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleDisClient.java b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleDisClient.java index a8a1e5d3..942605f6 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleDisClient.java +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleDisClient.java @@ -22,7 +22,7 @@ public class BleDisClient extends BleGattBase { - public static final String TAG = BleDisClient.class.getSimpleName(); + private static final String TAG = BleDisClient.class.getSimpleName(); public static final UUID DIS_SERVICE = UUID.fromString("0000180a-0000-1000-8000-00805f9b34fb"); public static final UUID MODEL_NUMBER_STRING = UUID.fromString("00002a24-0000-1000-8000-00805f9b34fb"); diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/BlePMDClient.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/BlePMDClient.kt index 71cf5465..e8a0059c 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/BlePMDClient.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/BlePMDClient.kt @@ -6,6 +6,7 @@ import com.polar.androidcommunications.api.ble.BleLogger import com.polar.androidcommunications.api.ble.exceptions.* import com.polar.androidcommunications.api.ble.model.gatt.BleGattBase import com.polar.androidcommunications.api.ble.model.gatt.BleGattTxInterface +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdMeasurementType.Companion.fromId import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdSetting.PmdSettingType import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.model.* import com.polar.androidcommunications.common.ble.AtomicSet @@ -221,8 +222,11 @@ class BlePMDClient(txInterface: BleGattTxInterface) : BleGattBase(txInterface, P val bb = ByteBuffer.allocate(1 + params.size) bb.put(byteArrayOf(command.numVal.toByte())) if (params.isNotEmpty()) bb.put(params) + BleLogger.d(TAG, "Send control point command $command") val response = sendPmdCommand(bb.array()) - if (response.status === PmdControlPointResponse.PmdControlPointResponseCode.SUCCESS) { + BleLogger.d(TAG, "Response of control point command $command with status ${response.status} ") + + if (response.status == PmdControlPointResponse.PmdControlPointResponseCode.SUCCESS) { subscriber.onSuccess(response) return@SingleOnSubscribe } @@ -246,8 +250,10 @@ class BlePMDClient(txInterface: BleGattTxInterface) : BleGattBase(txInterface, P * - onSuccess settings query success, the queried settings emitted * - onError settings query failed */ - fun querySettings(type: PmdMeasurementType): Single { - return sendControlPointCommand(PmdControlPointCommand.GET_MEASUREMENT_SETTINGS, type.numVal.toByte()) + fun querySettings(type: PmdMeasurementType, recordingType: PmdRecordingType = PmdRecordingType.ONLINE): Single { + val measurementType = type.numVal + val requestByte = recordingType.asBitField() or measurementType + return sendControlPointCommand(PmdControlPointCommand.GET_MEASUREMENT_SETTINGS, requestByte.toByte()) .map { pmdControlPointResponse: PmdControlPointResponse -> PmdSetting(pmdControlPointResponse.parameters) } } @@ -258,8 +264,11 @@ class BlePMDClient(txInterface: BleGattTxInterface) : BleGattBase(txInterface, P * - onSuccess full settings query success, the queried settings emitted * - onError full settings query failed */ - fun queryFullSettings(type: PmdMeasurementType): Single { - return sendControlPointCommand(PmdControlPointCommand.GET_SDK_MODE_MEASUREMENT_SETTINGS, type.numVal.toByte()) + fun queryFullSettings(type: PmdMeasurementType, recordingType: PmdRecordingType = PmdRecordingType.ONLINE): Single { + val measurementType = type.numVal + val requestByte = recordingType.asBitField() or measurementType + + return sendControlPointCommand(PmdControlPointCommand.GET_SDK_MODE_MEASUREMENT_SETTINGS, requestByte.toByte()) .map { pmdControlPointResponse: PmdControlPointResponse -> PmdSetting(pmdControlPointResponse.parameters) } } @@ -294,18 +303,15 @@ class BlePMDClient(txInterface: BleGattTxInterface) : BleGattBase(txInterface, P }).subscribeOn(Schedulers.io()) } - /** - * request to start a specific measurement - * - * @param type measurement to start - * @param setting desired settings - * @return Completable stream - */ - fun startMeasurement(type: PmdMeasurementType, setting: PmdSetting): Completable { - val set = setting.serializeSelected() - val bb = ByteBuffer.allocate(1 + set.size) - bb.put(type.numVal.toByte()) - bb.put(set) + fun startMeasurement(type: PmdMeasurementType, setting: PmdSetting, recordingType: PmdRecordingType = PmdRecordingType.ONLINE, secret: PmdSecret? = null): Completable { + val measurementType = type.numVal + val firstByte = recordingType.asBitField() or measurementType + var settingsBytes = setting.serializeSelected() + settingsBytes += secret?.serializeToPmdSettings() ?: byteArrayOf() + + val bb = ByteBuffer.allocate(1 + settingsBytes.size) + bb.put(firstByte.toByte()) + bb.put(settingsBytes) currentSettings[type] = setting return sendControlPointCommand(PmdControlPointCommand.REQUEST_MEASUREMENT_START, bb.array()) @@ -314,6 +320,105 @@ class BlePMDClient(txInterface: BleGattTxInterface) : BleGattBase(txInterface, P .ignoreElements() } + fun readMeasurementStatus(): Single> { + return sendControlPointCommand(PmdControlPointCommand.GET_MEASUREMENT_STATUS) + .map { pmdControlPointResponse: PmdControlPointResponse -> + val measurementStatus: MutableMap = mutableMapOf() + for (parameter in pmdControlPointResponse.parameters) { + when (fromId(parameter)) { + PmdMeasurementType.ECG -> measurementStatus[PmdMeasurementType.ECG] = PmdActiveMeasurement.fromStatusResponse(parameter) + PmdMeasurementType.PPG -> measurementStatus[PmdMeasurementType.PPG] = PmdActiveMeasurement.fromStatusResponse(parameter) + PmdMeasurementType.ACC -> measurementStatus[PmdMeasurementType.ACC] = PmdActiveMeasurement.fromStatusResponse(parameter) + PmdMeasurementType.PPI -> measurementStatus[PmdMeasurementType.PPI] = PmdActiveMeasurement.fromStatusResponse(parameter) + PmdMeasurementType.GYRO -> measurementStatus[PmdMeasurementType.GYRO] = PmdActiveMeasurement.fromStatusResponse(parameter) + PmdMeasurementType.MAGNETOMETER -> measurementStatus[PmdMeasurementType.MAGNETOMETER] = PmdActiveMeasurement.fromStatusResponse(parameter) + PmdMeasurementType.LOCATION -> measurementStatus[PmdMeasurementType.LOCATION] = PmdActiveMeasurement.fromStatusResponse(parameter) + PmdMeasurementType.PRESSURE -> measurementStatus[PmdMeasurementType.PRESSURE] = PmdActiveMeasurement.fromStatusResponse(parameter) + PmdMeasurementType.TEMPERATURE -> measurementStatus[PmdMeasurementType.TEMPERATURE] = PmdActiveMeasurement.fromStatusResponse(parameter) + PmdMeasurementType.OFFLINE_HR -> measurementStatus[PmdMeasurementType.OFFLINE_HR] = PmdActiveMeasurement.fromStatusResponse(parameter) + else -> {} + } + } + measurementStatus + } + } + + internal fun setOfflineRecordingTrigger(offlineRecordingTrigger: PmdOfflineTrigger, secret: PmdSecret?): Completable { + val setOfflineTriggerModeCompletable = setOfflineRecordingTriggerMode(triggerMode = offlineRecordingTrigger.triggerMode) + val setOfflineTriggerSettingsCompletable = if (offlineRecordingTrigger.triggerMode != PmdOfflineRecTriggerMode.TRIGGER_DISABLE) { + getOfflineRecordingTriggerStatus() + .flatMapCompletable { pmdOfflineTriggers -> + val allSettingCommands = pmdOfflineTriggers.triggers + .map { it.key } + .map { availableMeasurementType -> + if (offlineRecordingTrigger.triggers.keys.contains(availableMeasurementType)) { + val settings = offlineRecordingTrigger.triggers[availableMeasurementType]?.second + BleLogger.d(TAG, "Enable trigger $availableMeasurementType") + setOfflineRecordingTriggerSetting(PmdOfflineRecTriggerStatus.TRIGGER_ENABLED, availableMeasurementType, settings, secret) + } else { + BleLogger.d(TAG, "Disable trigger $availableMeasurementType") + setOfflineRecordingTriggerSetting(PmdOfflineRecTriggerStatus.TRIGGER_DISABLED, availableMeasurementType) + } + } + Completable.concat(allSettingCommands) + } + } else { + Completable.complete() + } + + val commands = mutableListOf(setOfflineTriggerModeCompletable, setOfflineTriggerSettingsCompletable) + return Completable.concat(commands) + } + + private fun setOfflineRecordingTriggerMode(triggerMode: PmdOfflineRecTriggerMode): Completable { + val parameter = byteArrayOf(triggerMode.value.toByte()) + return sendControlPointCommand(PmdControlPointCommand.SET_OFFLINE_RECORDING_TRIGGER_MODE, parameter) + .toObservable() + .ignoreElements() + } + + private fun setOfflineRecordingTriggerSetting(triggerStatus: PmdOfflineRecTriggerStatus, type: PmdMeasurementType, setting: PmdSetting? = null, secret: PmdSecret? = null): Completable { + //guard + if (!type.isDataType()) { + return Completable.error(Exception("Invalid PmdMeasurementType: $type")) + } + val triggerStatusByte: UByte = triggerStatus.value + val measurementTypeByte: UByte = type.numVal + + val settingsBytes = if (triggerStatus == PmdOfflineRecTriggerStatus.TRIGGER_ENABLED) { + val settingBytes = setting?.serializeSelected() ?: byteArrayOf() + val securityBytes = secret?.serializeToPmdSettings() ?: byteArrayOf() + byteArrayOf((settingBytes + securityBytes).size.toByte()) + settingBytes + securityBytes + } else { + byteArrayOf() + } + + val parameters = byteArrayOf(triggerStatusByte.toByte(), measurementTypeByte.toByte()) + settingsBytes + return sendControlPointCommand(PmdControlPointCommand.SET_OFFLINE_RECORDING_TRIGGER_SETTINGS, parameters) + .onErrorResumeNext { + if (it is BleControlPointCommandError) { + val errorMessage = "$type $triggerStatus Trigger Setting failed" + Single.error( + BleControlPointCommandError( + message = errorMessage, + error = it.error + ) + ) + } else { + Single.error(it) + } + } + .toObservable() + .ignoreElements() + } + + internal fun getOfflineRecordingTriggerStatus(): Single { + return sendControlPointCommand(PmdControlPointCommand.GET_OFFLINE_RECORDING_TRIGGER_STATUS) + .map { pmdControlPointResponse: PmdControlPointResponse -> + PmdOfflineTrigger.parseFromResponse(pmdControlPointResponse.parameters) + } + } + /** * Request to start SDK mode * @@ -342,6 +447,20 @@ class BlePMDClient(txInterface: BleGattTxInterface) : BleGattBase(txInterface, P .ignoreElements() } + /** + * Is SDK mode enabled + * + * @return Single + * - onSuccess the value is true if SDK mode is enabled + * - onError SDK status request failed + */ + internal fun isSdkModeEnabled(): Single { + return sendControlPointCommand(PmdControlPointCommand.GET_SDK_MODE_STATUS) + .map { pmdControlPointResponse: PmdControlPointResponse -> + PmdSdkMode.fromResponse(pmdControlPointResponse.parameters.first()) + } + } + /** * Request to stop measurement * diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdActiveMeasurement.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdActiveMeasurement.kt new file mode 100644 index 00000000..3681ad4a --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdActiveMeasurement.kt @@ -0,0 +1,24 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd + +enum class PmdActiveMeasurement(private val bitMask: Int?) { + ONLINE_MEASUREMENT_ACTIVE(bitMask = 1), + OFFLINE_MEASUREMENT_ACTIVE(bitMask = 2), + ONLINE_AND_OFFLINE_ACTIVE(bitMask = 3), + NO_ACTIVE_MEASUREMENT(bitMask = 0), + UNKNOWN_MEASUREMENT_STATUS(bitMask = null); + + companion object { + private const val MEASUREMENT_BIT_MASK: UByte = 0xC0u + + fun fromStatusResponse(responseByte: Byte): PmdActiveMeasurement { + val masked = responseByte.toUByte() and MEASUREMENT_BIT_MASK + return when (masked.toInt() shr 6) { + ONLINE_MEASUREMENT_ACTIVE.bitMask -> ONLINE_MEASUREMENT_ACTIVE + OFFLINE_MEASUREMENT_ACTIVE.bitMask -> OFFLINE_MEASUREMENT_ACTIVE + ONLINE_AND_OFFLINE_ACTIVE.bitMask -> ONLINE_AND_OFFLINE_ACTIVE + NO_ACTIVE_MEASUREMENT.bitMask -> NO_ACTIVE_MEASUREMENT + else -> UNKNOWN_MEASUREMENT_STATUS + } + } + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdControlPointCommand.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdControlPointCommand.kt index 146140b7..a3d16e7e 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdControlPointCommand.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdControlPointCommand.kt @@ -5,5 +5,10 @@ enum class PmdControlPointCommand(val numVal: Int) { GET_MEASUREMENT_SETTINGS(1), REQUEST_MEASUREMENT_START(2), STOP_MEASUREMENT(3), - GET_SDK_MODE_MEASUREMENT_SETTINGS(4); + GET_SDK_MODE_MEASUREMENT_SETTINGS(4), + GET_MEASUREMENT_STATUS(5), + GET_SDK_MODE_STATUS(6), + GET_OFFLINE_RECORDING_TRIGGER_STATUS(7), + SET_OFFLINE_RECORDING_TRIGGER_MODE(8), + SET_OFFLINE_RECORDING_TRIGGER_SETTINGS(9); } \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdMeasurementType.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdMeasurementType.kt index cd0f731a..f22693b6 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdMeasurementType.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdMeasurementType.kt @@ -11,22 +11,25 @@ enum class PmdMeasurementType(val numVal: UByte) { LOCATION(10u), PRESSURE(11u), TEMPERATURE(12u), + OFFLINE_RECORDING(13u), + OFFLINE_HR(14u), UNKNOWN_TYPE(0x3fu); fun isDataType(): Boolean { return when (this) { SDK_MODE, + OFFLINE_RECORDING, UNKNOWN_TYPE -> false else -> true } } internal companion object { - private const val MEASUREMENT_BIT_MASK: UByte = 0x3Fu + private const val MEASUREMENT_TYPE_BIT_MASK: UByte = 0x3Fu fun fromId(id: Byte): PmdMeasurementType { for (type in values()) { - if (type.numVal == (id.toUByte() and MEASUREMENT_BIT_MASK)) { + if (type.numVal == (id.toUByte() and MEASUREMENT_TYPE_BIT_MASK)) { return type } } @@ -45,21 +48,8 @@ enum class PmdMeasurementType(val numVal: UByte) { if ((data[2].toUInt() and 0x08u) != 0u) measurementTypes.add(PRESSURE) if ((data[2].toUInt() and 0x10u) != 0u) measurementTypes.add(TEMPERATURE) if ((data[2].toUInt() and 0x02u) != 0u) measurementTypes.add(SDK_MODE) - return measurementTypes - } - - fun isValid(data: ByteArray): Set { - val measurementTypes: MutableSet = mutableSetOf() - if ((data[1].toUInt() and 0x01u) != 0u) measurementTypes.add(ECG) - if ((data[1].toUInt() and 0x02u) != 0u) measurementTypes.add(PPG) - if ((data[1].toUInt() and 0x04u) != 0u) measurementTypes.add(ACC) - if ((data[1].toUInt() and 0x08u) != 0u) measurementTypes.add(PPI) - if ((data[1].toUInt() and 0x20u) != 0u) measurementTypes.add(GYRO) - if ((data[1].toUInt() and 0x40u) != 0u) measurementTypes.add(MAGNETOMETER) - if ((data[2].toUInt() and 0x04u) != 0u) measurementTypes.add(LOCATION) - if ((data[2].toUInt() and 0x08u) != 0u) measurementTypes.add(PRESSURE) - if ((data[2].toUInt() and 0x10u) != 0u) measurementTypes.add(TEMPERATURE) - if ((data[2].toUInt() and 0x02u) != 0u) measurementTypes.add(SDK_MODE) + if ((data[2].toUInt() and 0x20u) != 0u) measurementTypes.add(OFFLINE_RECORDING) + if ((data[2].toUInt() and 0x40u) != 0u) measurementTypes.add(OFFLINE_HR) return measurementTypes } } diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdOfflineTrigger.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdOfflineTrigger.kt new file mode 100644 index 00000000..c5d67f1c --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdOfflineTrigger.kt @@ -0,0 +1,75 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd + +import com.polar.androidcommunications.api.ble.BleLogger +import com.polar.androidcommunications.api.ble.exceptions.BleControlPointResponseError + +internal enum class PmdOfflineRecTriggerMode(val value: UByte) { + TRIGGER_DISABLE(0u), + TRIGGER_SYSTEM_START(1u), + TRIGGER_EXERCISE_START(2u); + + companion object { + fun parseFromResponse(byte: Byte): PmdOfflineRecTriggerMode { + return values().first { it.value == byte.toUByte() } + } + } +} + +internal enum class PmdOfflineRecTriggerStatus(val value: UByte) { + TRIGGER_DISABLED(0u), + TRIGGER_ENABLED(1u); + + companion object { + fun parseFromResponse(byte: Byte): PmdOfflineRecTriggerStatus { + return try { + values().first { it.value == byte.toUByte() } + } catch (e: NoSuchElementException) { + throw BleControlPointResponseError("PmdOfflineTriggerStatus parsing failed no matching status for byte ${"0x%x".format(byte)}") + } + } + } +} + +internal data class PmdOfflineTrigger( + val triggerMode: PmdOfflineRecTriggerMode, + val triggers: Map> +) { + companion object { + private const val TAG = "PmdOfflineTrigger" + private const val TRIGGER_MODE_INDEX = 0 + private const val TRIGGER_MODE_FIELD_LENGTH = 1 + private const val TRIGGER_STATUS_FIELD_LENGTH = 1 + private const val TRIGGER_MEASUREMENT_TYPE_FIELD_LENGTH = 1 + private const val TRIGGER_MEASUREMENT_SETTINGS_SIZE_FIELD_LENGTH = 1 + + fun parseFromResponse(bytes: ByteArray): PmdOfflineTrigger { + BleLogger.d(TAG, "parse offline trigger from response ") + var offset = TRIGGER_MODE_INDEX + val triggerMode = PmdOfflineRecTriggerMode.parseFromResponse(bytes[TRIGGER_MODE_INDEX]) + offset += TRIGGER_MODE_FIELD_LENGTH + + val triggers: MutableMap> = mutableMapOf() + while (offset < bytes.size) { + val triggerStatus = PmdOfflineRecTriggerStatus.parseFromResponse(bytes[offset]) + offset += TRIGGER_STATUS_FIELD_LENGTH + val triggerMeasurementType = PmdMeasurementType.fromId(bytes[offset]) + offset += TRIGGER_MEASUREMENT_TYPE_FIELD_LENGTH + if (triggerStatus == PmdOfflineRecTriggerStatus.TRIGGER_ENABLED) { + val triggerSettingsLength = bytes[offset] + offset += TRIGGER_MEASUREMENT_SETTINGS_SIZE_FIELD_LENGTH + val settingBytes = bytes.sliceArray(offset until (offset + triggerSettingsLength)) + val pmdSetting = if (settingBytes.isNotEmpty()) { + PmdSetting(settingBytes) + } else { + null + } + offset += triggerSettingsLength + triggers[triggerMeasurementType] = Pair(triggerStatus, pmdSetting) + } else { + triggers[triggerMeasurementType] = Pair(triggerStatus, null) + } + } + return PmdOfflineTrigger(triggerMode = triggerMode, triggers = triggers) + } + } +} diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdRecordingType.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdRecordingType.kt new file mode 100644 index 00000000..ce3028f0 --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdRecordingType.kt @@ -0,0 +1,23 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd + +enum class PmdRecordingType(val numVal: UByte) { + ONLINE(0u), + OFFLINE(1u); + + fun asBitField(): UByte { + return (this.numVal.toUInt() shl 7).toUByte() + } + + companion object { + private const val RECORDING_TYPE_BIT_MASK: UByte = 0x80u + + fun fromId(id: Byte): PmdRecordingType { + for (type in values()) { + if (type.numVal == (id.toUByte() and RECORDING_TYPE_BIT_MASK)) { + return type + } + } + return ONLINE + } + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSdkMode.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSdkMode.kt new file mode 100644 index 00000000..4d12612a --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSdkMode.kt @@ -0,0 +1,16 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd + +enum class PmdSdkMode { + ENABLED, + DISABLED; + + companion object { + fun fromResponse(data: Byte): PmdSdkMode { + return if (data != 0.toByte()) { + ENABLED + } else { + DISABLED + } + } + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSecret.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSecret.kt new file mode 100644 index 00000000..52242fce --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSecret.kt @@ -0,0 +1,93 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd + +import com.polar.androidcommunications.api.ble.exceptions.SecurityError +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec +import kotlin.experimental.xor + +class PmdSecret(val strategy: SecurityStrategy, val key: ByteArray) { + constructor(strategy: SecurityStrategy, key: SecretKeySpec) : this(strategy, key.encoded) + + init { + when (strategy) { + SecurityStrategy.NONE -> require(key.isEmpty()) { "key shall be empty for ${SecurityStrategy.NONE}, key size was ${key.size}" } + SecurityStrategy.XOR -> require(key.isNotEmpty()) { "key shall not be empty for ${SecurityStrategy.XOR}, key size was ${key.size}" } + SecurityStrategy.AES128 -> require(key.size == 16) { "key must be size of 16 bytes for ${SecurityStrategy.AES128}, key size was ${key.size}" } + SecurityStrategy.AES256 -> require(key.size == 32) { "key must be size of 32 bytes for ${SecurityStrategy.AES256}, key size was ${key.size}" } + } + } + + fun serializeToPmdSettings(): ByteArray { + when (strategy) { + SecurityStrategy.NONE -> { + val securitySetting = PmdSetting.PmdSettingType.SECURITY.numVal.toByte() + val securityStrategyByte = SecurityStrategy.NONE.numVal.toByte() + val length = 1.toByte() + return byteArrayOf(securitySetting, length, securityStrategyByte) + } + SecurityStrategy.XOR -> { + val securitySetting = PmdSetting.PmdSettingType.SECURITY.numVal.toByte() + val securityStrategyByte = SecurityStrategy.XOR.numVal.toByte() + val keyBytes = key + val length = 1.toByte() + return byteArrayOf(securitySetting, length, securityStrategyByte) + keyBytes + } + SecurityStrategy.AES128 -> { + val securitySetting = PmdSetting.PmdSettingType.SECURITY.numVal.toByte() + val securityStrategyByte = SecurityStrategy.AES128.numVal.toByte() + val keyBytes = key + val length = 1.toByte() + return byteArrayOf(securitySetting, length, securityStrategyByte) + keyBytes + } + SecurityStrategy.AES256 -> { + val securitySetting = PmdSetting.PmdSettingType.SECURITY.numVal.toByte() + val securityStrategyByte = SecurityStrategy.AES256.numVal.toByte() + val keyBytes = key + val length = 1.toByte() + return byteArrayOf(securitySetting, length, securityStrategyByte) + keyBytes + } + } + } + + fun decryptArray(cipherArray: ByteArray): ByteArray { + when (this.strategy) { + SecurityStrategy.AES128 -> { + val key = SecretKeySpec(this.key, "AES") + val cipher = Cipher.getInstance("AES_128/ECB/NoPadding") + cipher.init(Cipher.DECRYPT_MODE, key) + return cipher.doFinal(cipherArray) + } + SecurityStrategy.AES256 -> { + val key = SecretKeySpec(this.key, "AES") + val cipher = Cipher.getInstance("AES_256/ECB/NoPadding") + cipher.init(Cipher.DECRYPT_MODE, key) + return cipher.doFinal(cipherArray) + } + SecurityStrategy.XOR -> { + return cipherArray.map { it xor this.key.first() }.toByteArray() + } + SecurityStrategy.NONE -> { + return cipherArray + } + } + } + + enum class SecurityStrategy(val numVal: UByte) { + NONE(0x00u), + XOR(0x01u), + AES128(0x02u), + AES256(0x03u); + + companion object { + fun fromByte(strategyByte: UByte): SecurityStrategy { + return when { + (strategyByte == NONE.numVal) -> NONE + (strategyByte == XOR.numVal) -> XOR + (strategyByte == AES128.numVal) -> AES128 + (strategyByte == AES256.numVal) -> AES256 + else -> throw SecurityError.SecurityStrategyUnknown("Cannot decide security strategy from byte ${"0x%x".format(strategyByte)}") + } + } + } + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSetting.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSetting.kt index 2fb90742..cc5c49e2 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSetting.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSetting.kt @@ -11,7 +11,8 @@ class PmdSetting { RANGE(2), RANGE_MILLIUNIT(3), CHANNELS(4), - FACTOR(5); + FACTOR(5), + SECURITY(6); } // available settings @@ -66,8 +67,10 @@ class PmdSetting { if (key == PmdSettingType.FACTOR) { continue } + outputStream.write(key.numVal) outputStream.write(1) + val fieldSize = Objects.requireNonNull(typeToFieldSize(key)) for (i in 0 until fieldSize) { outputStream.write((value shr i * 8)) @@ -107,6 +110,7 @@ class PmdSetting { PmdSettingType.RANGE_MILLIUNIT -> 4 PmdSettingType.CHANNELS -> 1 PmdSettingType.FACTOR -> 4 + PmdSettingType.SECURITY -> 16 } } diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/GyrData.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/GyrData.kt index 8c53c516..73f9dd57 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/GyrData.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/GyrData.kt @@ -4,6 +4,7 @@ import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.BlePMDClien import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.BlePMDClient.PmdDataFieldEncoding import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdDataFrame import java.lang.Float.intBitsToFloat +import java.util.* internal class GyrData constructor(@Deprecated("each sample has timestamp") val timeStamp: ULong = 0uL) { diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/MagData.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/MagData.kt index 16c57adf..c833fdab 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/MagData.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/MagData.kt @@ -39,6 +39,7 @@ internal class MagData(@Deprecated("each sample has timestamp") val timeStamp: U private const val TYPE_1_SAMPLE_SIZE_IN_BITS = TYPE_1_SAMPLE_SIZE_IN_BYTES * 8 private const val TYPE_1_CHANNELS_IN_SAMPLE = 4 + fun parseDataFromDataFrame(frame: PmdDataFrame): MagData { return if (frame.isCompressedFrame) { when (frame.frameType) { diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/OfflineHrData.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/OfflineHrData.kt new file mode 100644 index 00000000..fef1b1d1 --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/OfflineHrData.kt @@ -0,0 +1,39 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd.model + +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdDataFrame +import com.polar.androidcommunications.common.ble.TypeUtils.convertUnsignedByteToInt + +/** + * Offline hr data + */ +internal class OfflineHrData { + + data class OfflineHrSample internal constructor( + val hr: Int + ) + + val hrSamples: MutableList = mutableListOf() + + companion object { + fun parseDataFromDataFrame(frame: PmdDataFrame): OfflineHrData { + return if (frame.isCompressedFrame) { + throw java.lang.Exception("Compressed FrameType: ${frame.frameType} is not supported by Offline HR data parser") + } else { + when (frame.frameType) { + PmdDataFrame.PmdDataFrameType.TYPE_0 -> dataFromType0(frame) + else -> throw java.lang.Exception("Raw FrameType: ${frame.frameType} is not supported by Offline HR data parser") + } + } + } + + private fun dataFromType0(frame: PmdDataFrame): OfflineHrData { + val offlineHrData = OfflineHrData() + var offset = 0 + while (offset < frame.dataContent.size) { + offlineHrData.hrSamples.add(OfflineHrSample(convertUnsignedByteToInt(frame.dataContent[offset]))) + offset += 1 + } + return offlineHrData + } + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PmdDataModelUtils.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PmdDataModelUtils.kt new file mode 100644 index 00000000..98b5b54f --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PmdDataModelUtils.kt @@ -0,0 +1,34 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd.model + +internal object PmdDataModelUtils { + + fun getTimeStampDelta(previousTimeStamp: Long, newTimeStamp: Long, samplesSize: Int, samplingRate: Int): Long { + return if (previousTimeStamp <= 0) { + deltaFromSamplingRate(samplingRate) + } else { + deltaFromTimeStamps(previousTimeStamp, newTimeStamp, samplesSize) + } + } + + private fun deltaFromSamplingRate(samplingRate: Int): Long { + return ((1.0 / samplingRate.toDouble()) * 1000 * 1000 * 1000).toLong() + } + + private fun deltaFromTimeStamps(previousTimeStamp: Long, timeStamp: Long, samples: Int): Long { + val timeInBetween = timeStamp - previousTimeStamp + return if (timeInBetween > 0) { + timeInBetween / samples + } else { + 0L + } + } + + fun firstSampleTimeStamp(lastSampleTimeStamp: Long, timeStampDelta: Long, samplesSize: Int): Long { + return if (samplesSize > 0) { + val startTimeStamp = lastSampleTimeStamp - (timeStampDelta * (samplesSize - 1)) + if (startTimeStamp > 0) startTimeStamp else 0L + } else { + 0L + } + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PpgData.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PpgData.kt index 0ce84da5..5554aa08 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PpgData.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PpgData.kt @@ -64,7 +64,7 @@ internal class PpgData internal constructor(@Deprecated("each sample has timesta val ppgSamples: MutableList = ArrayList() companion object { - const val TAG = "PpgData" + private const val TAG = "PpgData" private const val TYPE_0_SAMPLE_SIZE_IN_BYTES = 3 private const val TYPE_0_SAMPLE_SIZE_IN_BITS = TYPE_0_SAMPLE_SIZE_IN_BYTES * 8 diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingData.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingData.kt new file mode 100644 index 00000000..e2782bfb --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingData.kt @@ -0,0 +1,338 @@ +package com.polar.androidcommunications.api.ble.model.offlinerecording + +import com.polar.androidcommunications.api.ble.BleLogger +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdDataFrame +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdMeasurementType +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdSecret +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdSetting +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.model.* +import com.polar.androidcommunications.common.ble.TypeUtils +import org.joda.time.DateTime +import java.util.* + +internal class OfflineRecordingData( + val offlineRecordingHeader: OfflineRecordingHeader, + val startTime: Calendar, + val recordingSettings: PmdSetting?, + val data: T +) { + + data class OfflineRecordingHeader internal constructor( + val magic: UInt, + val version: UInt, + val free: UInt, + val eswHash: UInt, + ) + + data class OfflineRecordingMetaData internal constructor( + val offlineRecordingHeader: OfflineRecordingHeader, + val startTime: Calendar, + val recordingSettings: PmdSetting?, + val securityInfo: PmdSecret, + val dataPayloadSize: Int + ) + + companion object { + private const val TAG = "OfflineRecordingData" + + private const val SECURITY_STRATEGY_INDEX = 0 + private const val SECURITY_STRATEGY_LENGTH = 1 + + private const val OFFLINE_HEADER_MAGIC = 0x3D7C4C2BU + + private const val OFFLINE_HEADER_LENGTH = 16 + private const val OFFLINE_SETTINGS_SIZE_FIELD_LENGTH = 1 + private const val DATE_TIME_LENGTH = 20 + private const val PACKET_SIZE_LENGTH = 2 + + @Throws(Exception::class) + fun parseDataFromOfflineFile(fileData: ByteArray, type: PmdMeasurementType, secret: PmdSecret? = null): OfflineRecordingData { + BleLogger.d(TAG, "Start offline file parsing. File size is ${fileData.size} and type $type") + + // guard + if (fileData.isEmpty()) { + throw OfflineRecordingError.OfflineRecordingEmptyFile + } + + val (metaData, metaDataLength) = try { + parseMetaData(fileData, secret) + } catch (e: Exception) { + throw OfflineRecordingError.OfflineRecordingErrorMetaDataParseFailed(detailMessage = e.toString()) + } + + val payloadDataBytes = fileData.drop(metaDataLength) + + // guard + if (payloadDataBytes.isEmpty()) { + throw OfflineRecordingError.OfflineRecordingNoPayloadData + } + + val parsedData = parseData( + dataBytes = payloadDataBytes, + metaData = metaData, + builder = getDataBuilder(type) + ) + + return OfflineRecordingData( + offlineRecordingHeader = metaData.offlineRecordingHeader, + recordingSettings = metaData.recordingSettings, + startTime = metaData.startTime, + data = parsedData + ) + } + + private fun getDataBuilder(type: PmdMeasurementType): Any { + val builder: Any = when (type) { + PmdMeasurementType.ECG -> EcgData() + PmdMeasurementType.PPG -> PpgData() + PmdMeasurementType.ACC -> AccData() + PmdMeasurementType.PPI -> PpiData() + PmdMeasurementType.GYRO -> GyrData() + PmdMeasurementType.MAGNETOMETER -> MagData() + PmdMeasurementType.LOCATION -> GnssLocationData() + PmdMeasurementType.PRESSURE -> PressureData() + PmdMeasurementType.TEMPERATURE -> TemperatureData() + PmdMeasurementType.OFFLINE_HR -> OfflineHrData() + else -> { + throw OfflineRecordingError.OfflineRecordingErrorNoParserForData + } + } + return builder + } + + private fun parseSecurityStrategy(strategyBytes: List): PmdSecret.SecurityStrategy { + val strategy = TypeUtils.convertArrayToUnsignedByte(strategyBytes.toByteArray()) + return PmdSecret.SecurityStrategy.fromByte(strategy) + } + + private fun parseMetaData(fileBytes: ByteArray, secret: PmdSecret?): Pair { + var securityOffset = 0 + val offlineRecordingSecurityStrategy = parseSecurityStrategy(fileBytes.slice(SECURITY_STRATEGY_INDEX until SECURITY_STRATEGY_LENGTH)) + securityOffset += SECURITY_STRATEGY_LENGTH + + val metaDataBytes = decryptMetaData(offlineRecordingSecurityStrategy, fileBytes.slice(securityOffset until fileBytes.size).toByteArray(), secret) + + val offlineRecordingHeader = parseHeader(metaDataBytes.slice(0 until OFFLINE_HEADER_LENGTH)) + var metaDataOffset = OFFLINE_HEADER_LENGTH + + // guard + if (offlineRecordingHeader.magic != OFFLINE_HEADER_MAGIC) { + throw OfflineRecordingError.OfflineRecordingHasWrongSignature + } + + val offlineRecordingStartTime = parseStartTime(metaDataBytes.slice(metaDataOffset until metaDataOffset + DATE_TIME_LENGTH)) + metaDataOffset += DATE_TIME_LENGTH + + val (pmdSetting, settingsLength) = parseSettings(metaDataBytes.slice(metaDataOffset until metaDataBytes.size)) + metaDataOffset += settingsLength + + val (payloadSecurity, securityInfoLength) = parseSecurityInfo(metaDataBytes.slice(metaDataOffset until metaDataBytes.size), secret) + metaDataOffset += securityInfoLength + + // padding bytes + val paddingBytes1Length = parsePaddingBytes(metaDataOffset, offlineRecordingSecurityStrategy) + metaDataOffset += paddingBytes1Length + + val dataPayloadSize = parsePacketSize(metaDataBytes.slice(metaDataOffset until metaDataOffset + PACKET_SIZE_LENGTH)).toInt() + metaDataOffset += PACKET_SIZE_LENGTH + + val paddingBytes2Length = parsePaddingBytes(metaDataOffset, offlineRecordingSecurityStrategy) + metaDataOffset += paddingBytes2Length + + val metaData = OfflineRecordingMetaData( + offlineRecordingHeader = offlineRecordingHeader, + startTime = offlineRecordingStartTime, + recordingSettings = pmdSetting, + securityInfo = payloadSecurity, + dataPayloadSize = dataPayloadSize + ) + return Pair(metaData, securityOffset + metaDataOffset) + } + + private fun parseSettings(metaDataBytes: List): Pair { + var offset = 0 + val settingsLength = metaDataBytes[offset] + offset += OFFLINE_SETTINGS_SIZE_FIELD_LENGTH + val settingBytes = metaDataBytes.slice(offset until (offset + settingsLength)) + val pmdSetting = if (settingBytes.isNotEmpty()) { + PmdSetting(settingBytes.toByteArray()) + } else { + null + } + return Pair(pmdSetting, offset + settingsLength) + } + + private fun decryptMetaData(offlineRecordingSecurityStrategy: PmdSecret.SecurityStrategy, metaData: ByteArray, secret: PmdSecret?): ByteArray { + return when (offlineRecordingSecurityStrategy) { + PmdSecret.SecurityStrategy.NONE -> { + metaData + } + PmdSecret.SecurityStrategy.XOR -> { + // guard + if (secret == null) { + throw OfflineRecordingError.OfflineRecordingErrorSecretMissing + } + secret.decryptArray(metaData) + + } + PmdSecret.SecurityStrategy.AES128, + PmdSecret.SecurityStrategy.AES256 -> { + // guard + if (secret == null) { + throw OfflineRecordingError.OfflineRecordingErrorSecretMissing + } + // guard + if (secret.strategy != offlineRecordingSecurityStrategy) { + throw OfflineRecordingError.OfflineRecordingSecurityStrategyMissMatch("Offline file is encrypted using $offlineRecordingSecurityStrategy. The key provided is ${secret.strategy} ") + } + + val endOffset = (metaData.size / 16 * 16) + val metaDataChunk = metaData.slice(0 until endOffset).toByteArray() + secret.decryptArray(metaDataChunk) + } + } + } + + private fun parsePaddingBytes(metaDataOffset: Int, offlineRecordingSecurityStrategy: PmdSecret.SecurityStrategy): Int { + return when (offlineRecordingSecurityStrategy) { + PmdSecret.SecurityStrategy.NONE, + PmdSecret.SecurityStrategy.XOR -> 0 + PmdSecret.SecurityStrategy.AES128, + PmdSecret.SecurityStrategy.AES256 -> 16 - metaDataOffset.mod(16) + } + } + + private fun parseSecurityInfo(securityInfoBytes: List, secret: PmdSecret?): Pair { + var offset = 0 + val infoLength = securityInfoBytes[offset].toInt() + offset++ + + if (infoLength == 0) { + return if (secret == null) { + Pair(PmdSecret(strategy = PmdSecret.SecurityStrategy.NONE, key = byteArrayOf()), offset) + } else { + Pair(secret, offset) + } + } + + val strategy = securityInfoBytes[offset].toUByte() + offset++ + + return when (PmdSecret.SecurityStrategy.fromByte((strategy))) { + PmdSecret.SecurityStrategy.NONE -> { + Pair(PmdSecret(strategy = PmdSecret.SecurityStrategy.NONE, key = byteArrayOf()), offset) + } + PmdSecret.SecurityStrategy.XOR -> { + val indexOfXor = securityInfoBytes[offset].toInt() + val xor = secret?.key?.get(indexOfXor) ?: throw OfflineRecordingError.OfflineRecordingErrorSecretMissing + offset++ + Pair(PmdSecret(strategy = PmdSecret.SecurityStrategy.XOR, key = byteArrayOf(xor)), offset) + } + PmdSecret.SecurityStrategy.AES128 -> { + val key = secret?.key ?: throw OfflineRecordingError.OfflineRecordingErrorSecretMissing + Pair(PmdSecret(strategy = PmdSecret.SecurityStrategy.AES128, key = key), offset) + } + PmdSecret.SecurityStrategy.AES256 -> { + val key = secret?.key ?: throw OfflineRecordingError.OfflineRecordingErrorSecretMissing + Pair(PmdSecret(strategy = PmdSecret.SecurityStrategy.AES256, key = key), offset) + } + } + } + + private fun parseHeader(headerBytes: List): OfflineRecordingHeader { + var offset = 0 + val step = 4 + val magic = TypeUtils.convertArrayToUnsignedInt(headerBytes.toByteArray(), offset, step) + offset += step + val version = TypeUtils.convertArrayToUnsignedInt(headerBytes.toByteArray(), offset, step) + offset += step + val free = TypeUtils.convertArrayToUnsignedInt(headerBytes.toByteArray(), offset, step) + offset += step + val eswHash = TypeUtils.convertArrayToUnsignedInt(headerBytes.toByteArray(), offset, step) + offset += step + + return OfflineRecordingHeader(magic = magic, version = version, free = free, eswHash = eswHash) + } + + private fun parseStartTime(startTimeBytes: List): Calendar { + val startTimeInIso8601 = String(startTimeBytes.dropLast(1).toByteArray()).replace(' ', 'T') + "Z" + val dt = DateTime(startTimeInIso8601) + return dt.toCalendar(null) + } + + private fun parsePacketSize(packetSize: List): UInt { + return TypeUtils.convertArrayToUnsignedInt(packetSize.toByteArray(), 0, 2) + } + + private fun parseData(dataBytes: List, metaData: OfflineRecordingMetaData, builder: T): T { + + var previousTimeStamp: ULong = 0uL + var packetSize = metaData.dataPayloadSize + val sampleRate = metaData.recordingSettings?.settings?.get(PmdSetting.PmdSettingType.SAMPLE_RATE)?.first() ?: 0 + val factor = metaData.recordingSettings?.settings?.get(PmdSetting.PmdSettingType.FACTOR)?.first()?.let { + val ieee754 = it + java.lang.Float.intBitsToFloat(ieee754) + } ?: 1.0f + + var offset = 0 + val decryptedData = metaData.securityInfo.decryptArray(dataBytes.toByteArray()) + do { + val data = decryptedData.slice(offset until packetSize + offset) + offset += packetSize + val dataFrame = PmdDataFrame(data.toByteArray(), + getPreviousTimeStamp = { previousTimeStamp }, + getFactor = { factor }, + getSampleRate = { sampleRate }) + + previousTimeStamp = dataFrame.timeStamp + + when (builder) { + is EcgData -> { + val ecgData = EcgData.parseDataFromDataFrame(dataFrame) + builder.ecgSamples.addAll(ecgData.ecgSamples) + } + is AccData -> { + val accData = AccData.parseDataFromDataFrame(dataFrame) + builder.accSamples.addAll(accData.accSamples) + } + is GyrData -> { + val gyrData = GyrData.parseDataFromDataFrame(dataFrame) + builder.gyrSamples.addAll(gyrData.gyrSamples) + } + is MagData -> { + val magData = MagData.parseDataFromDataFrame(dataFrame) + builder.magSamples.addAll(magData.magSamples) + } + is PpgData -> { + val ppgData = PpgData.parseDataFromDataFrame(dataFrame) + builder.ppgSamples.addAll(ppgData.ppgSamples) + } + is PressureData -> { + val pressureData = PressureData.parseDataFromDataFrame(dataFrame) + builder.pressureSamples.addAll(pressureData.pressureSamples) + } + is GnssLocationData -> { + val gnssLocationData = GnssLocationData.parseDataFromDataFrame(dataFrame) + builder.gnssLocationDataSamples.addAll(gnssLocationData.gnssLocationDataSamples) + } + is PpiData -> { + val ppiData = PpiData.parseDataFromDataFrame(dataFrame) + builder.ppiSamples.addAll(ppiData.ppiSamples) + } + + is OfflineHrData -> { + val offlineHrData = OfflineHrData.parseDataFromDataFrame(dataFrame) + builder.hrSamples.addAll(offlineHrData.hrSamples) + } + } + + if (offset < decryptedData.size) { + packetSize = parsePacketSize(decryptedData.slice(offset until offset + PACKET_SIZE_LENGTH)).toInt() + offset += 2 + } + } while (offset < decryptedData.size) + return builder + } + } +} + diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingError.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingError.kt new file mode 100644 index 00000000..b87c0f26 --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingError.kt @@ -0,0 +1,12 @@ +package com.polar.androidcommunications.api.ble.model.offlinerecording + +sealed class OfflineRecordingError(detailMessage: String) : Exception(detailMessage) { + object OfflineRecordingEmptyFile : OfflineRecordingError("") + object OfflineRecordingNoPayloadData : OfflineRecordingError("") + object OfflineRecordingHasWrongSignature : OfflineRecordingError("") + object OfflineRecordingErrorSecretMissing : OfflineRecordingError("") + object OfflineRecordingErrorNoParserForData : OfflineRecordingError("") + class OfflineRecordingSecurityStrategyMissMatch(detailMessage: String) : OfflineRecordingError(detailMessage) + class OfflineRecordingErrorMetaDataParseFailed(detailMessage: String) : OfflineRecordingError(detailMessage) +} + diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtility.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtility.kt new file mode 100644 index 00000000..689c2fc3 --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtility.kt @@ -0,0 +1,19 @@ +package com.polar.androidcommunications.api.ble.model.offlinerecording + +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdMeasurementType + +internal object OfflineRecordingUtility { + + fun mapOfflineRecordingFileNameToMeasurementType(fileName: String): PmdMeasurementType { + return when (fileName) { + "ACC.REC" -> PmdMeasurementType.ACC + "GYRO.REC" -> PmdMeasurementType.GYRO + "MAG.REC" -> PmdMeasurementType.MAGNETOMETER + "PPG.REC" -> PmdMeasurementType.PPG + "PPI.REC" -> PmdMeasurementType.PPI + "HR.REC" -> PmdMeasurementType.OFFLINE_HR + else -> throw Exception("Unknown offline file $fileName") + } + } +} + diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/common/ble/TypeUtils.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/common/ble/TypeUtils.kt index ed8f2dbc..654fb709 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/common/ble/TypeUtils.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/common/ble/TypeUtils.kt @@ -47,4 +47,8 @@ object TypeUtils { return result.toInt() } + + fun convertUnsignedByteToInt(byte: Byte): Int { + return byte.toUByte().toInt() + } } \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/BDDeviceListenerImpl.java b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/BDDeviceListenerImpl.java index c4117242..f109f4d6 100755 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/BDDeviceListenerImpl.java +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/BDDeviceListenerImpl.java @@ -255,8 +255,10 @@ public void connectDevice(final BDDeviceSessionImpl session) { int mask = BluetoothDevice.PHY_LE_1M_MASK; if (bluetoothAdapter.isLe2MPhySupported()) mask |= BluetoothDevice.PHY_LE_2M_MASK; + BleLogger.d(TAG, "Attempt connect to device " + session.getName() + " with the mask " + mask); gatt = session.getBluetoothDevice().connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE, mask); } else { + BleLogger.d(TAG, "Attempt connect to device " + session.getName()); gatt = session.getBluetoothDevice().connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE); } session.setGatt(gatt); diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/BDDeviceSessionImpl.java b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/BDDeviceSessionImpl.java index e5deae84..918ce2d7 100755 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/BDDeviceSessionImpl.java +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/BDDeviceSessionImpl.java @@ -1,5 +1,6 @@ package com.polar.androidcommunications.enpoints.ble.bluedroid.host; +import static com.polar.androidcommunications.common.ble.AndroidBuildUtils.getBrand; import static com.polar.androidcommunications.common.ble.AndroidBuildUtils.getBuildVersion; import android.annotation.SuppressLint; @@ -293,7 +294,9 @@ private boolean sendNextAttributeOperation(AttributeOperation operation) throws return false; } gatt.setCharacteristicNotification(characteristic, operation.isEnable()); - if (getBuildVersion() >= Build.VERSION_CODES.TIRAMISU) { + + //Note, some manufacturers, e.g. OnePlus haven't properly implemented the new API. + if (getBuildVersion() >= Build.VERSION_CODES.TIRAMISU && !getBrand().equals("OnePlus")) { int status = gatt.writeDescriptor(descriptor, value); if (status == BluetoothStatusCodes.SUCCESS) { return true; @@ -302,6 +305,7 @@ private boolean sendNextAttributeOperation(AttributeOperation operation) throws return false; } } else { + BleLogger.d(TAG, "using deprecated descriptor write"); descriptor.setValue(value); return gatt.writeDescriptor(descriptor); } diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/GattCallback.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/GattCallback.kt index 2debf753..be7cd141 100755 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/GattCallback.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/GattCallback.kt @@ -54,6 +54,7 @@ internal class GattCallback( } override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { + BleLogger.d(TAG, "GATT onServicesDiscovered. Status: $status") val deviceSession = sessions.getSession(gatt) ?: kotlin.run { BleLogger.e(TAG, "services discovered on non known gatt") return @@ -65,9 +66,10 @@ internal class GattCallback( } if (status == BluetoothGatt.GATT_SUCCESS) { deviceSession.handleServicesDiscovered() - Completable.fromAction { connectionHandler.servicesDiscovered(deviceSession) } - .subscribeOn(Schedulers.io()) - .subscribe() + // There are devices needing a delay before connection parameters negotiation is continued, e.g. Nokia G11 + Completable.timer(CONNECTION_PARAMETER_NEGOTIATION_WAIT_DELAY, TimeUnit.MILLISECONDS) + .observeOn(Schedulers.io()) + .subscribe { connectionHandler.servicesDiscovered(deviceSession) } } else { BleLogger.e(TAG, "service discovery failed: $status") Completable.fromAction { connectionHandler.disconnectDevice(deviceSession) } @@ -79,6 +81,7 @@ internal class GattCallback( @Deprecated("Deprecated in Java") @SuppressLint("MissingPermission") override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) { + BleLogger.d(TAG, "GATT onCharacteristicRead characteristic:${characteristic.uuid} status: $status") val deviceSession = sessions.getSession(gatt) if (deviceSession != null) { deviceSession.handleCharacteristicRead(characteristic.service, characteristic, characteristic.value, status) @@ -90,6 +93,7 @@ internal class GattCallback( @SuppressLint("MissingPermission") override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) { + BleLogger.d(TAG, "GATT onCharacteristicRead characteristic:${characteristic.uuid} status: $status") val deviceSession = sessions.getSession(gatt) if (deviceSession != null) { deviceSession.handleCharacteristicRead(characteristic.service, characteristic, value, status) @@ -101,6 +105,7 @@ internal class GattCallback( @SuppressLint("MissingPermission") override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) { + BleLogger.d(TAG, "GATT onCharacteristicWrite characteristic:${characteristic.uuid} status: $status") val deviceSession = sessions.getSession(gatt) if (deviceSession != null) { deviceSession.handleCharacteristicWrite(characteristic.service, characteristic, status) @@ -136,6 +141,7 @@ internal class GattCallback( @Deprecated("Deprecated in Java") @SuppressLint("MissingPermission") override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) { + BleLogger.d(TAG, "GATT onDescriptorRead descriptor:${descriptor.uuid} status: $status") val deviceSession = sessions.getSession(gatt) if (deviceSession != null) { deviceSession.handleDescriptorRead(descriptor, descriptor.value, status) @@ -147,6 +153,7 @@ internal class GattCallback( @SuppressLint("MissingPermission") override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) { + BleLogger.d(TAG, "GATT onDescriptorRead descriptor:${descriptor.uuid} status: $status") val deviceSession = sessions.getSession(gatt) if (deviceSession != null) { deviceSession.handleDescriptorRead(descriptor, value, status) @@ -158,6 +165,7 @@ internal class GattCallback( @SuppressLint("MissingPermission") override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) { + BleLogger.d(TAG, "GATT onDescriptorWrite descriptor: ${descriptor.uuid} status: $status") val deviceSession = sessions.getSession(gatt) if (deviceSession != null) { deviceSession.handleDescriptorWrite(descriptor.characteristic.service, descriptor.characteristic, descriptor.value, status) @@ -213,4 +221,9 @@ internal class GattCallback( .subscribe() } } + + override fun onServiceChanged(gatt: BluetoothGatt) { + super.onServiceChanged(gatt) + BleLogger.d(TAG, " onServiceChanged") + } } \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/connection/ConnectionHandler.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/connection/ConnectionHandler.kt index 67110e14..2626d814 100755 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/connection/ConnectionHandler.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/enpoints/ble/bluedroid/host/connection/ConnectionHandler.kt @@ -248,10 +248,11 @@ class ConnectionHandler( connectionInterface.startServiceDiscovery(session) } ConnectionHandlerAction.PHY_UPDATED -> { - connectionInterface.setMtu(session) mtuSafeGuardDisposable = Completable.timer(GUARD_TIME_MS, TimeUnit.MILLISECONDS, guardTimerScheduler) .observeOn(Schedulers.io()) .subscribe { mtuUpdated(session) } + + connectionInterface.setMtu(session) } ConnectionHandlerAction.MTU_UPDATED -> { @@ -271,10 +272,11 @@ class ConnectionHandler( } ConnectionHandlerAction.SERVICES_DISCOVERED -> { - connectionInterface.setPhy(session) phySafeGuardDisposable = Completable.timer(GUARD_TIME_MS, TimeUnit.MILLISECONDS, guardTimerScheduler) .observeOn(Schedulers.io()) .subscribe { phyUpdated(session) } + + connectionInterface.setPhy(session) } ConnectionHandlerAction.CONNECT_DEVICE -> { diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApi.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApi.kt index 7e17f20e..c0a737bb 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApi.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApi.kt @@ -2,8 +2,6 @@ package com.polar.sdk.api import androidx.annotation.IntRange -import androidx.annotation.Size -import androidx.core.util.Pair import com.polar.sdk.api.errors.PolarInvalidArgument import com.polar.sdk.api.model.* import io.reactivex.rxjava3.core.Completable @@ -15,9 +13,56 @@ import java.util.concurrent.TimeUnit /** * Polar BLE API. * - * @param features bitmask of feature(s) or [.ALL_FEATURES] + * @property features the set of the features API is used for. By giving only the needed features the SDK may reserve only the required resources */ -abstract class PolarBleApi(val features: Int) { +abstract class PolarBleApi(val features: Set) : PolarOnlineStreamingApi, PolarOfflineRecordingApi, PolarH10OfflineExerciseApi, PolarSdkModeApi { + + /** + * Features available in Polar BLE SDK library + */ + enum class PolarBleSdkFeature { + /** + * Hr feature to receive hr and rr data from Polar or any other BLE device via standard HR BLE service + */ + FEATURE_HR, + + /** + * Device information feature to receive sw information from Polar or any other BLE device + */ + FEATURE_DEVICE_INFO, + + /** + * Feature to receive battery level info from Polar or any other BLE device + */ + FEATURE_BATTERY_INFO, + + /** + * Polar sensor streaming feature to stream live online data. For example hr, ecg, acc, ppg, ppi, etc... + */ + FEATURE_POLAR_ONLINE_STREAMING, + + /** + * Polar offline recording feature to record offline data to Polar device without continuous BLE connection. + */ + FEATURE_POLAR_OFFLINE_RECORDING, + + /** + * H10 exercise recording feature to record exercise data to Polar H10 device without continuous BLE connection. + */ + FEATURE_POLAR_H10_EXERCISE_RECORDING, + + /** + * Feature to read and set device time in Polar device + */ + FEATURE_POLAR_DEVICE_TIME_SETUP, + + /** + * In SDK mode the wider range of capabilities are available for the online stream or offline recoding + * than in normal operation mode. + */ + FEATURE_POLAR_SDK_MODE, + } + /** * Logger interface for logging events from SDK. Shall be used only for tracing and debugging purposes. */ @@ -31,33 +76,14 @@ abstract class PolarBleApi(val features: Int) { } /** - * Device stream features in Polar devices. The device streaming features requires the - * [.FEATURE_POLAR_SENSOR_STREAMING] - * - * @see PolarBleApiCallback.streamingFeaturesReady - */ - enum class DeviceStreamingFeature { - ECG, ACC, PPG, PPI, GYRO, MAGNETOMETER - } - - /** - * Recoding intervals for H10 recording start + * The data types available in Polar devices for online streaming or offline recording. */ - enum class RecordingInterval(val value: Int) { - INTERVAL_1S(1), /*!< 1 second interval */ - INTERVAL_5S(5); /*!< 5 second interval */ + enum class PolarDeviceDataType { + HR, ECG, ACC, PPG, PPI, GYRO, MAGNETOMETER } /** - * Sample types for H10 recording start - */ - enum class SampleType { - HR, /*!< HeartRate in BPM */ - RR, /*!< RR interval in milliseconds */ - } - - /** - * set mtu to lower than default(232 is the default for polar devices, minimum for H10 is 70 and for OH1 is 140) + * set mtu to lower than default (232 is the default for polar devices, minimum for H10 is 70 and for OH1 is 140) * to minimize latency * * @param mtu value between 70-512 to be set @@ -79,8 +105,8 @@ abstract class PolarBleApi(val features: Int) { abstract fun cleanup() /** - * When enabled only Polar devices are found by the [.searchForDevice], if set to false - * any BLE devices with HR services are returned by the [.searchForDevice]. The default setting for + * When enabled only Polar devices are found by the [searchForDevice], if set to false + * any BLE devices with HR services are returned by the [searchForDevice]. The default setting for * Polar filter is true. * * @param enable false disables polar filter @@ -88,21 +114,13 @@ abstract class PolarBleApi(val features: Int) { abstract fun setPolarFilter(enable: Boolean) /** - * Check if the feature is ready. Only the check for the [.FEATURE_POLAR_SENSOR_STREAMING] - * and [.FEATURE_POLAR_FILE_TRANSFER] is supported by this api function + * Check if the feature is ready. * * @param deviceId polar device id or bt address * @param feature feature to be requested * @return true if feature is ready for use, */ - abstract fun isFeatureReady(deviceId: String, feature: Int): Boolean - - /** - * enables scan filter while on background - * - */ - @Deprecated("in release 3.2.8. Move to the background is not relevant information for SDK starting from release 3.2.8") - abstract fun backgroundEntered() + abstract fun isFeatureReady(deviceId: String, feature: PolarBleSdkFeature): Boolean /** * Optionally call when application enters to the foreground. By calling foregroundEntered() you make @@ -128,62 +146,25 @@ abstract class PolarBleApi(val features: Int) { abstract fun setApiLogger(logger: PolarBleApiLogger) /** - * When enabled the reconnection is attempted if device connection is lost. By default automatic reconnection is enabled. - * - * @param enable true = automatic reconnection is enabled, false = automatic reconnection is disabled - */ - abstract fun setAutomaticReconnection(enable: Boolean) - - /** - * Set time to device affects on sensor data stream(s) timestamps - * requires feature [.FEATURE_POLAR_FILE_TRANSFER] - * - * @param identifier polar device id or bt address - * @param calendar time to set - * @return Completable stream - */ - abstract fun setLocalTime(identifier: String, calendar: Calendar): Completable - - /** - * Get current time in device. To use this function feature [.FEATURE_POLAR_FILE_TRANSFER] is required + * Starts searching for BLE devices when subscribed. Search continues as long as observable is + * subscribed or error. Each found device is emitted only once. By default searches only for Polar devices, + * but can be controlled by [.setPolarFilter]. If [.setPolarFilter] is false + * then searches for any BLE heart rate capable devices * - * @param identifier polar device id or bt address - * @return [Single] + * @return Flowable stream of [PolarDeviceInfo] * Produces: - *

- onSuccess the current local time in device - *

- onError status request failed - */ - abstract fun getLocalTime(identifier: String): Single - - /** - * Request the stream settings available in current operation mode. This request shall be used before the stream is started - * to decide currently available. The available settings depend on the state of the device. For - * example, if any stream(s) or optical heart rate measurement is already enabled, then - * the device may limit the offer of possible settings for other stream feature. Requires feature - * [.FEATURE_POLAR_SENSOR_STREAMING] - * - * @param identifier polar device id or bt address - * @param feature the stream feature of interest - * @return Single stream + *

- onNext for any new Polar (or BLE) device detected + *

- onError if scan start fails + *

- onComplete non produced unless stream is further configured */ - abstract fun requestStreamSettings( - identifier: String, - feature: DeviceStreamingFeature - ): Single + abstract fun searchForDevice(): Flowable /** - * Request full steam settings capabilities. The request returns the all capabilities of the - * requested streaming feature not limited by the current operation mode. Requires feature - * [.FEATURE_POLAR_SENSOR_STREAMING]. This request is supported only by Polar Verity Sense (starting from firmware 1.1.5) + * When enabled the reconnection is attempted if device connection is lost. By default automatic reconnection is enabled. * - * @param identifier polar device id or bt address - * @param feature the stream feature of interest - * @return Single stream + * @param enable true = automatic reconnection is enabled, false = automatic reconnection is disabled */ - abstract fun requestFullStreamSettings( - identifier: String, - feature: DeviceStreamingFeature - ): Single + abstract fun setAutomaticReconnection(enable: Boolean) /** * Start connecting to a nearby Polar device. [PolarBleApiCallback.deviceConnected] callback is @@ -220,88 +201,21 @@ abstract class PolarBleApi(val features: Int) { abstract fun disconnectFromDevice(identifier: String) /** - * Request start recording. Supported only by Polar H10. Requires feature - * [.FEATURE_POLAR_FILE_TRANSFER] - * - * @param identifier polar device id or bt address - * @param exerciseId unique id for exercise entry - * @param interval recording interval to be used, parameter has no effect if the `type` parameter is SampleType.RR - * @param type sample type to be used - * @return Completable stream - */ - abstract fun startRecording( - identifier: String, - @Size(min = 1, max = 64) exerciseId: String, - interval: RecordingInterval?, - type: SampleType - ): Completable - - /** - * Request to stop recording. Supported only by Polar H10. Requires feature - * [.FEATURE_POLAR_FILE_TRANSFER] + * Set the device time. Requires feature [PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP] * * @param identifier polar device id or bt address + * @param calendar time to set * @return Completable stream */ - abstract fun stopRecording(identifier: String): Completable + abstract fun setLocalTime(identifier: String, calendar: Calendar): Completable /** - * Request current recording status. Supported only by Polar H10. Requires feature - * [.FEATURE_POLAR_FILE_TRANSFER] + * Get current time in device. Requires feature [PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP] * * @param identifier polar device id or bt address - * @return Single stream Pair first recording status, second entryId if available + * @return Single observable which emits device time in Calendar instance when observable is subscribed */ - abstract fun requestRecordingStatus(identifier: String): Single> - - /** - * List exercises stored in the device Polar H10 device. Requires feature - * [.FEATURE_POLAR_FILE_TRANSFER]. This API is working for Polar OH1 and - * Polar Verity Sense devices too, however in those devices recording of exercise requires - * that sensor is registered to Polar Flow account. - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @return Flowable stream of [PolarExerciseEntry] entries - */ - abstract fun listExercises(identifier: String): Flowable - - /** - * Api for fetching a single exercise from Polar H10 device. Requires feature - * [.FEATURE_POLAR_FILE_TRANSFER]. This API is working for Polar OH1 and - * Polar Verity Sense devices too, however in those devices recording of exercise requires - * that sensor is registered to Polar Flow account. - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @param entry [PolarExerciseEntry] object - * @return Single stream of [PolarExerciseData] - */ - abstract fun fetchExercise(identifier: String, entry: PolarExerciseEntry): Single - - /** - * Api for removing single exercise from Polar H10 device. Requires feature - * [.FEATURE_POLAR_FILE_TRANSFER]. This API is working for Polar OH1 and - * Polar Verity Sense devices too, however in those devices recording of exercise requires - * that sensor is registered to Polar Flow account. - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @param entry entry to be removed - * @return Completable stream - */ - abstract fun removeExercise(identifier: String, entry: PolarExerciseEntry): Completable - - /** - * Starts searching for BLE devices when subscribed. Search continues as long as observable is - * subscribed or error. Each found device is emitted only once. By default searches only for Polar devices, - * but can be controlled by [.setPolarFilter]. If [.setPolarFilter] is false - * then searches for any BLE heart rate capable devices - * - * @return Flowable stream of [PolarDeviceInfo] - * Produces: - *

- onNext for any new Polar (or BLE) device detected - *

- onError if scan start fails - *

- onComplete non produced unless stream is further configured - */ - abstract fun searchForDevice(): Flowable + abstract fun getLocalTime(identifier: String): Single /** * Start listening the heart rate from Polar devices when subscribed. This observable listens BLE @@ -316,164 +230,4 @@ abstract class PolarBleApi(val features: Int) { *

- onComplete non produced unless stream is further configured */ abstract fun startListenForPolarHrBroadcasts(deviceIds: Set?): Flowable - - /** - * Start the ECG (Electrocardiography) stream. ECG stream is stopped if the connection is closed, - * error occurs or stream is disposed. Requires feature [.FEATURE_POLAR_SENSOR_STREAMING]. - * Before starting the stream it is recommended to query the available settings using [.requestStreamSettings] - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @param sensorSetting settings to be used to start streaming - * @return Flowable stream of [PolarEcgData] - * Produces: - *

- onNext [PolarEcgData] - *

- onError error for possible errors invoked - *

- onComplete non produced unless stream is further configured - */ - abstract fun startEcgStreaming( - identifier: String, - sensorSetting: PolarSensorSetting - ): Flowable - - /** - * Start ACC (Accelerometer) stream. ACC stream is stopped if the connection is closed, error - * occurs or stream is disposed. Requires feature [.FEATURE_POLAR_SENSOR_STREAMING]. - * Before starting the stream it is recommended to query the available settings using [.requestStreamSettings] - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @param sensorSetting settings to be used to start streaming - * @return Flowable stream of [PolarAccelerometerData] - * Produces: - *

- onNext [PolarAccelerometerData] - *

- onError error for possible errors invoked - *

- onComplete non produced unless stream is further configured - */ - abstract fun startAccStreaming( - identifier: String, - sensorSetting: PolarSensorSetting - ): Flowable - - /** - * Start OHR (Optical heart rate) PPG (Photoplethysmography) stream. PPG stream is stopped if - * the connection is closed, error occurs or stream is disposed. Requires feature - * [.FEATURE_POLAR_SENSOR_STREAMING]. Before starting the stream it is recommended to - * query the available settings using [.requestStreamSettings] - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @param sensorSetting settings to be used to start streaming - * @return Flowable stream of OHR PPG data. - * Produces: - *

- onNext [PolarOhrData] - *

- onError error for possible errors invoked - *

- onComplete non produced unless the stream is further configured - */ - abstract fun startOhrStreaming( - identifier: String, - sensorSetting: PolarSensorSetting - ): Flowable - - /** - * Start OHR (Optical heart rate) PPI (Pulse to Pulse interval) stream. PPI stream is stopped if - * the connection is closed, error occurs or stream is disposed. Notice that there is a - * delay before PPI data stream starts. Requires feature [.FEATURE_POLAR_SENSOR_STREAMING]. - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @return Flowable stream of OHR PPI data. - * Produces: - *

- onNext [PolarOhrPPIData] - *

- onError error for possible errors invoked - *

- onComplete non produced unless the stream is further configured - */ - abstract fun startOhrPPIStreaming(identifier: String): Flowable - - /** - * Start magnetometer stream. Magnetometer stream is stopped if the connection is closed, error - * occurs or stream is disposed. Requires feature [.FEATURE_POLAR_SENSOR_STREAMING]. - * Before starting the stream it is recommended to query the available settings using [.requestStreamSettings] - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @param sensorSetting settings to be used to start streaming - * @return Flowable stream of magnetometer data. - * Produces: - *

- onNext [PolarMagnetometerData] - *

- onError error for possible errors invoked - *

- onComplete non produced unless the stream is further configured - */ - abstract fun startMagnetometerStreaming( - identifier: String, - sensorSetting: PolarSensorSetting - ): Flowable - - /** - * Start Gyro stream. Gyro stream is stopped if the connection is closed, error occurs during - * start or stream is disposed. Requires feature [.FEATURE_POLAR_SENSOR_STREAMING]. - * Before starting the stream it is recommended to query the available settings using [.requestStreamSettings] - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @param sensorSetting settings to be used to start streaming - * @return Flowable stream of gyroscope data. - * Produces: - *

- onNext [PolarGyroData] - *

- onError error for possible errors invoked - *

- onComplete non produced unless the stream is further configured - */ - abstract fun startGyroStreaming( - identifier: String, - sensorSetting: PolarSensorSetting - ): Flowable - - /** - * Enables SDK mode. In SDK mode the wider range of capabilities is available for the stream - * than in normal operation mode. SDK mode is only supported by Polar Verity Sense (starting from firmware 1.1.5). - * Requires feature [.FEATURE_POLAR_SENSOR_STREAMING]. - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @return Completable stream produces: - * success if SDK mode is enabled or device is already in SDK mode - * error if SDK mode enable failed - */ - abstract fun enableSDKMode(identifier: String): Completable - - /** - * Disables SDK mode. SDK mode is only supported by Polar Verity Sense (starting from firmware 1.1.5). - * Requires feature [.FEATURE_POLAR_SENSOR_STREAMING]. - * - * @param identifier Polar device id found printed on the sensor/device or bt address - * @return Completable stream produces: - * success if SDK mode is disabled or SDK mode was already disabled - * error if SDK mode disable failed - */ - abstract fun disableSDKMode(identifier: String): Completable - - companion object { - /** - * hr feature to receive hr and rr data. - */ - const val FEATURE_HR = 1 - - /** - * dis feature to receive sw information. - */ - const val FEATURE_DEVICE_INFO = 2 - - /** - * bas feature to receive battery level info. - */ - const val FEATURE_BATTERY_INFO = 4 - - /** - * polar sensor streaming feature for ecg, acc, ppg, ppi, etc... - */ - const val FEATURE_POLAR_SENSOR_STREAMING = 8 - - /** - * polar file transfer feature to read exercises from device - */ - const val FEATURE_POLAR_FILE_TRANSFER = 16 - - /** - * all features mask - */ - const val ALL_FEATURES = 0xff - } } \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallback.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallback.kt index cd90534d..8dff5e9d 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallback.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallback.kt @@ -1,7 +1,7 @@ // Copyright © 2019 Polar Electro Oy. All rights reserved. package com.polar.sdk.api -import com.polar.sdk.api.PolarBleApi.DeviceStreamingFeature +import com.polar.sdk.api.PolarBleApi.PolarDeviceDataType import com.polar.sdk.api.model.PolarDeviceInfo import com.polar.sdk.api.model.PolarHrData import java.util.* @@ -11,6 +11,8 @@ import java.util.* */ abstract class PolarBleApiCallback : PolarBleApiCallbackProvider { /** + * Bluetooth power state of the device where this SDK is running + * * @param powered true = Bluetooth power on, false = Bluetooth power off */ override fun blePowerStateChanged(powered: Boolean) {} @@ -30,41 +32,53 @@ abstract class PolarBleApiCallback : PolarBleApiCallbackProvider { override fun deviceConnecting(polarDeviceInfo: PolarDeviceInfo) {} /** - * Device is now disconnected, no further action is needed from the application - * if polar.com.sdk.api.PolarBleApi#disconnectFromPolarDevice is not called. Device will be automatically reconnected + * Device is now disconnected * * @param polarDeviceInfo Polar device information */ override fun deviceDisconnected(polarDeviceInfo: PolarDeviceInfo) {} + /** + * Called when the feature in connected device is available and it is ready. Called only for the features which are specified by [PolarBleApi] instantiation. + * + * @param identifier Polar device id + * @param feature feature is ready + */ + override fun bleSdkFeatureReady(identifier: String, feature: PolarBleApi.PolarBleSdkFeature) {} + /** * Polar device's streaming features ready. Application may start any stream now if desired. - * Requires feature [PolarBleApi.FEATURE_POLAR_SENSOR_STREAMING] to be enabled for the + * Requires feature [PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_ONLINE_STREAMING] to be enabled for the * Polar SDK instance. * * @param identifier Polar device id * @param features set of features available and ready */ - override fun streamingFeaturesReady(identifier: String, features: Set) {} + @Deprecated("The function is renamed. Please use the getAvailableOnlineStreamDataTypes function") + override fun streamingFeaturesReady(identifier: String, features: Set) { + } /** * Polar SDK Mode feature is available in the device. Application may now enter to SDK mode. - * Requires feature PolarBleApi#FEATURE_POLAR_SENSOR_STREAMING + * Requires feature [PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_SDK_MODE] * * @param identifier Polar device id */ - override fun sdkModeFeatureAvailable(identifier: String) {} + @Deprecated("Information whether SDK Mode is available is provided by bleSdkFeatureReady") + override fun sdkModeFeatureAvailable(identifier: String) { + } /** * Polar device HR client is now ready and HR transmission is starting in a moment. * * @param identifier Polar device id or bt address */ - override fun hrFeatureReady(identifier: String) {} + @Deprecated("Information whether HR feature is available is provided by bleSdkFeatureReady") + override fun hrFeatureReady(identifier: String) { + } /** - * DIS information received - * requires feature PolarBleApi#FEATURE_DEVICE_INFO + * DIS information received. Requires feature [PolarBleApi.PolarBleSdkFeature.FEATURE_DEVICE_INFO] * * @param identifier Polar device id or bt address * @param uuid uuid of dis value @@ -73,8 +87,7 @@ abstract class PolarBleApiCallback : PolarBleApiCallbackProvider { override fun disInformationReceived(identifier: String, uuid: UUID, value: String) {} /** - * Battery level received - * requires feature PolarBleApi#FEATURE_BATTERY_INFO + * Battery level received. Requires feature [PolarBleApi.PolarBleSdkFeature.FEATURE_BATTERY_INFO] * * @param identifier Polar device id or bt address * @param level battery level (value between 0-100%) @@ -88,7 +101,9 @@ abstract class PolarBleApiCallback : PolarBleApiCallbackProvider { * @param identifier Polar device id or bt address * @param data @see polar.com.sdk.api.model.PolarHrData.java */ - override fun hrNotificationReceived(identifier: String, data: PolarHrData) {} + @Deprecated("Please use the startHrStreaming API to get the heart rate data ") + override fun hrNotificationReceived(identifier: String, data: PolarHrData.PolarHrSample) { + } /** * File transfer ready @@ -96,5 +111,7 @@ abstract class PolarBleApiCallback : PolarBleApiCallbackProvider { * * @param identifier Polar device id */ - override fun polarFtpFeatureReady(identifier: String) {} + @Deprecated("Not supported anymore, won't be ever called. Use the bleSdkFeatureReady") + override fun polarFtpFeatureReady(identifier: String) { + } } \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallbackProvider.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallbackProvider.kt index 03d23c83..f95413cd 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallbackProvider.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallbackProvider.kt @@ -1,7 +1,7 @@ // Copyright © 2019 Polar Electro Oy. All rights reserved. package com.polar.sdk.api -import com.polar.sdk.api.PolarBleApi.DeviceStreamingFeature +import com.polar.sdk.api.PolarBleApi.PolarDeviceDataType import com.polar.sdk.api.model.PolarDeviceInfo import com.polar.sdk.api.model.PolarHrData import java.util.* @@ -11,6 +11,8 @@ import java.util.* */ interface PolarBleApiCallbackProvider { /** + * Bluetooth power state of the device where this SDK is running + * * @param powered true = Bluetooth power on, false = Bluetooth power off */ fun blePowerStateChanged(powered: Boolean) @@ -30,31 +32,38 @@ interface PolarBleApiCallbackProvider { fun deviceConnecting(polarDeviceInfo: PolarDeviceInfo) /** - * Device is now disconnected, no further action is needed from the application - * if polar.com.sdk.api.PolarBleApi#disconnectFromPolarDevice is not called. Device will be automatically reconnected + * Device is now disconnected * * @param polarDeviceInfo Polar device information */ fun deviceDisconnected(polarDeviceInfo: PolarDeviceInfo) + /** + * The feature is available in this device and it is ready. Called only for the features which are specified in [PolarBleApi] construction. + * + * @param identifier Polar device id + * @param feature feature is ready + */ + fun bleSdkFeatureReady(identifier: String, feature: PolarBleApi.PolarBleSdkFeature) + /** * Polar device's streaming features ready. Application may start any stream now if desired. - * Requires feature PolarBleApi#FEATURE_POLAR_SENSOR_STREAMING + * Requires feature [PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_ONLINE_STREAMING] to be enabled for the + * Polar SDK instance. * * @param identifier Polar device id * @param features set of features available and ready */ - fun streamingFeaturesReady( - identifier: String, - features: Set - ) + @Deprecated("The function is renamed. Please use the getAvailableOnlineStreamDataTypes function") + fun streamingFeaturesReady(identifier: String, features: Set) /** * Polar SDK Mode feature is available in the device. Application may now enter to SDK mode. - * Requires feature PolarBleApi#FEATURE_POLAR_SENSOR_STREAMING + * Requires feature [PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_SDK_MODE] * * @param identifier Polar device id */ + @Deprecated("Information whether SDK Mode is available is provided by bleSdkFeatureReady") fun sdkModeFeatureAvailable(identifier: String) /** @@ -62,11 +71,11 @@ interface PolarBleApiCallbackProvider { * * @param identifier Polar device id or bt address */ + @Deprecated("Information whether HR feature is available is provided by bleSdkFeatureReady") fun hrFeatureReady(identifier: String) /** - * DIS information received - * requires feature PolarBleApi#FEATURE_DEVICE_INFO + * DIS information received. Requires feature [PolarBleApi.PolarBleSdkFeature.FEATURE_DEVICE_INFO] * * @param identifier Polar device id or bt address * @param uuid uuid of dis value @@ -75,8 +84,7 @@ interface PolarBleApiCallbackProvider { fun disInformationReceived(identifier: String, uuid: UUID, value: String) /** - * Battery level received - * requires feature PolarBleApi#FEATURE_BATTERY_INFO + * Battery level received. Requires feature [PolarBleApi.PolarBleSdkFeature.FEATURE_BATTERY_INFO] * * @param identifier Polar device id or bt address * @param level battery level (value between 0-100%) @@ -90,7 +98,8 @@ interface PolarBleApiCallbackProvider { * @param identifier Polar device id or bt address * @param data @see polar.com.sdk.api.model.PolarHrData.java */ - fun hrNotificationReceived(identifier: String, data: PolarHrData) + @Deprecated("Please use the startHrStreaming API to get the heart rate data ") + fun hrNotificationReceived(identifier: String, data: PolarHrData.PolarHrSample) /** * File transfer ready @@ -98,5 +107,6 @@ interface PolarBleApiCallbackProvider { * * @param identifier Polar device id */ + @Deprecated("Not supported anymore, won't be ever called. Use the bleSdkFeatureReady") fun polarFtpFeatureReady(identifier: String) } \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiDefaultImpl.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiDefaultImpl.kt index 6153fa18..1f6fe09a 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiDefaultImpl.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiDefaultImpl.kt @@ -16,7 +16,7 @@ object PolarBleApiDefaultImpl { * @return default Polar API implementation */ @JvmStatic - fun defaultImplementation(context: Context, features: Int): PolarBleApi { + fun defaultImplementation(context: Context, features: Set): BDBleApiImpl { return BDBleApiImpl.getInstance(context, features) } @@ -25,6 +25,6 @@ object PolarBleApiDefaultImpl { */ @JvmStatic fun versionInfo(): String { - return "4.0.0" + return "5.0.0-beta3" } } \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarH10OfflineExerciseApi.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarH10OfflineExerciseApi.kt new file mode 100644 index 00000000..88f5f3c5 --- /dev/null +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarH10OfflineExerciseApi.kt @@ -0,0 +1,99 @@ +package com.polar.sdk.api + +import androidx.annotation.Size +import androidx.core.util.Pair +import com.polar.sdk.api.model.PolarExerciseData +import com.polar.sdk.api.model.PolarExerciseEntry +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single + +/** + * H10 Exercise recording API. + * + * H10 Exercise recording makes it possible to record Hr or Rr data to H10 device memory. + * With H10 Exercise recording the H10 and phone don't need to be connected all the time, as H10 exercise recording + * continues in Polar device even the BLE disconnects. + * + * Requires features [PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_H10_EXERCISE_RECORDING] + * + * Note, API is working only with Polar H10 device + */ + +interface PolarH10OfflineExerciseApi { + + /** + * Recoding intervals for H10 recording start + */ + enum class RecordingInterval(val value: Int) { + INTERVAL_1S(1), /*!< 1 second interval */ + INTERVAL_5S(5); /*!< 5 second interval */ + } + + /** + * Sample types for H10 recording start + */ + enum class SampleType { + HR, /*!< HeartRate in BPM */ + RR, /*!< RR interval in milliseconds */ + } + + /** + * Request start recording. + * + * @param identifier polar device id or bt address + * @param exerciseId unique id for exercise entry + * @param interval recording interval to be used, parameter has no effect if the `type` parameter is SampleType.RR + * @param type sample type to be used + * @return Completable stream + */ + fun startRecording( + identifier: String, + @Size(min = 1, max = 64) exerciseId: String, + interval: RecordingInterval?, + type: SampleType + ): Completable + + + /** + * Request to stop recording. + * + * @param identifier polar device id or bt address + * @return Completable stream + */ + fun stopRecording(identifier: String): Completable + + /** + * Request current recording status. + * + * @param identifier polar device id or bt address + * @return Single stream Pair first recording status, second entryId if available + */ + fun requestRecordingStatus(identifier: String): Single> + + /** + * List exercises stored in the device Polar H10 device. + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @return Flowable stream of [PolarExerciseEntry] entries + */ + fun listExercises(identifier: String): Flowable + + /** + * Api for fetching a single exercise from Polar H10 device. + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param entry [PolarExerciseEntry] object + * @return Single stream of [PolarExerciseData] + */ + fun fetchExercise(identifier: String, entry: PolarExerciseEntry): Single + + /** + * Api for removing single exercise from Polar H10 device. + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param entry entry to be removed + * @return Completable stream + */ + fun removeExercise(identifier: String, entry: PolarExerciseEntry): Completable +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOfflineRecordingApi.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOfflineRecordingApi.kt new file mode 100644 index 00000000..c4fffb9f --- /dev/null +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOfflineRecordingApi.kt @@ -0,0 +1,188 @@ +package com.polar.sdk.api + +import com.polar.sdk.api.PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_OFFLINE_RECORDING +import com.polar.sdk.api.model.* +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single + +/** + * Offline recording API. + * + * Offline recording makes it possible to record [PolarBleApi.PolarDeviceDataType] data to device memory. + * With Offline recording the Polar device and phone don't need to be connected all the time, as offline recording + * continues in Polar device even the BLE disconnects. + * + * Offline records saved into the device can be encrypted. The [PolarRecordingSecret] is provided for + * [startOfflineRecording] and [setOfflineRecordingTrigger] when encryption is wanted. The [PolarRecordingSecret] with same key must be provided + * in [getOfflineRecord] to correctly decrypt the data in the device. + * + * Requires features [FEATURE_POLAR_OFFLINE_RECORDING] + * + * Note, offline recording is supported in Polar Verity Sense device (starting from firmware version 2.1.0) + */ +interface PolarOfflineRecordingApi { + + /** + * Get the data types available in this device for offline recording + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @return [Single] + * Produces: + *

- onSuccess the set of available offline recording data types in this device + *

- onError status request failed + */ + fun getAvailableOfflineRecordingDataTypes(identifier: String): Single> + + /** + * Request the offline recording settings available in current operation mode. This request shall be used before the offline recording is started + * to decide currently available settings. The available settings depend on the state of the device. + * + * @param identifier polar device id or bt address + * @param feature the stream feature of interest + * @return Single stream + */ + fun requestOfflineRecordingSettings( + identifier: String, + feature: PolarBleApi.PolarDeviceDataType + ): Single + + /** + * Request all the settings available in the device. The request returns the all capabilities of the + * requested streaming feature not limited by the current operation mode. + * + * @param identifier polar device id or bt address + * @param feature the stream feature of interest + * @return Single stream + */ + fun requestFullOfflineRecordingSettings( + identifier: String, + feature: PolarBleApi.PolarDeviceDataType + ): Single + + /** + * Get current offline recording status. + * + * @param identifier polar device id or bt address + * @return [Single] + * Produces: + *

- onSuccess the list of currently recording offline recordings, if the list is empty no offline recordings currently recorded + *

- onError status request failed + */ + fun getOfflineRecordingStatus( + identifier: String + ): Single> + + /** + * List offline recordings stored in the device. + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @return [Flowable] stream + *

- onNext the found offline recording entry in [PolarOfflineRecordingEntry] + *

- onComplete the listing completed + *

- onError listing request failed + */ + fun listOfflineRecordings(identifier: String): Flowable + + /** + * Fetch recording from the Polar device. + * + * Note, the fetching of the recording may take several seconds if the recording is big. + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param entry The offline recording to be fetched + * @param secret If the secret is provided in [startOfflineRecording] or [setOfflineRecordingTrigger] + * then the same secret must be provided when fetching the offline record + * + * @return [Single] + * Produces: + *

- onSuccess the offline recording data + *

- onError fetch recording request failed + */ + fun getOfflineRecord(identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret? = null): Single + + /** + * Removes offline recording from the device + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param entry entry to be removed + * @return [Completable] + * Produces: + *

- onComplete offline record is removed + *

- onError offline record removal failed + */ + fun removeOfflineRecord(identifier: String, entry: PolarOfflineRecordingEntry): Completable + + /** + * Start offline recording. + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param feature the feature to be started + * @param settings settings for the started offline recording. In case of + * the feature [PolarBleApi.PolarDeviceDataType.PPI] or [PolarBleApi.PolarDeviceDataType.HR] the [PolarSensorSetting] is not needed + * @param secret if the secret is provided the offline recordings are encrypted in device + * @return [Completable] + * Produces: + *

- onComplete offline recording is started successfully + *

- onError offline recording start failed. + */ + fun startOfflineRecording( + identifier: String, + feature: PolarBleApi.PolarDeviceDataType, + settings: PolarSensorSetting? = null, + secret: PolarRecordingSecret? = null + ): Completable + + /** + * Request to stop offline recording. + * + * @param identifier polar device id + * @param feature which is stopped + * @return [Completable] + * Produces: + *

- onComplete offline recording is stop successfully + *

- onError offline recording stop failed. + */ + fun stopOfflineRecording( + identifier: String, + feature: PolarBleApi.PolarDeviceDataType + ): Completable + + /** + * Set the offline recording triggers. The offline recording can be started automatically by the device if the + * [PolarOfflineRecordingTriggerMode] is set enabled. If offline recordings is already recoding the + * [PolarOfflineRecordingTriggerMode] won't have affect on running recordings. Change of trigger settings will take effect + * on next device start up. + * + * Automatically started offline recording can be stopped by [stopOfflineRecording]. If user switches off the device power + * the offline recording is stopped, but starts again once power in device is switched on and the trigger event happens. Trigger + * functionality can be disabled by [PolarOfflineRecordingTriggerMode.TRIGGER_DISABLED], however the already running offline + * recording is not stopped by disable. + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param trigger the type of trigger + * @param secret if the secret is provided the offline recordings are encrypted in device + * + * Produces: + *

- onComplete offline recording trigger set successfully + *

- onError offline recording trigger set failed. + */ + fun setOfflineRecordingTrigger( + identifier: String, + trigger: PolarOfflineRecordingTrigger, + secret: PolarRecordingSecret? = null + ): Completable + + /** + * Get the setup of offline recording triggers in the device. + * + * The setup is the current offline recording trigger setup in the device. To know which offline recordings are currently recording use the [getOfflineRecordingStatus] + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * + * Produces: + *

- onSuccess the offline recording trigger setup in the device + *

- onError fetch recording request failed + */ + fun getOfflineRecordingTriggerSetup(identifier: String): Single +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOnlineStreamingApi.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOnlineStreamingApi.kt new file mode 100644 index 00000000..3d295ff3 --- /dev/null +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOnlineStreamingApi.kt @@ -0,0 +1,198 @@ +package com.polar.sdk.api + +import com.polar.sdk.api.PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_ONLINE_STREAMING +import com.polar.sdk.api.model.* +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single + +/** + * Online steaming API. + * + * Online streaming makes it possible to stream live online data from Polar device. + * + * Requires features [FEATURE_POLAR_ONLINE_STREAMING] + * + * Note, online streaming is supported by VeritySense, H10 and OH1 devices + */ +interface PolarOnlineStreamingApi { + /** + * Get the data types available in this device for online streaming + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @return [Single] + * Produces: + *

- onSuccess the set of available online streaming data types in this device + *

- onError status request failed + */ + fun getAvailableOnlineStreamDataTypes(identifier: String): Single> + + /** + * Request the online stream settings available in current operation mode. This request shall be used before the stream is started + * to decide currently available settings. The available settings depend on the state of the device. For + * example, if any stream(s) or optical heart rate measurement is already enabled, then + * the device may limit the offer of possible settings for other stream feature. + * + * @param identifier polar device id or bt address + * @param feature the stream feature of interest + * @return Single stream + */ + fun requestStreamSettings( + identifier: String, + feature: PolarBleApi.PolarDeviceDataType + ): Single + + /** + * Request the settings available in the device. The request returns the all capabilities of the + * requested streaming feature not limited by the current operation mode. + * + * Note, This request is supported only by Polar Verity Sense (starting from firmware 1.1.5) + * + * @param identifier polar device id or bt address + * @param feature the stream feature of interest + * @return Single stream + */ + fun requestFullStreamSettings( + identifier: String, + feature: PolarBleApi.PolarDeviceDataType + ): Single + + /** + * Start heart rate stream. Heart rate stream is stopped if + * the connection is closed, error occurs or stream is disposed. Requires feature + * [.FEATURE_POLAR_ONLINE_STREAMING]. Before starting the stream it is recommended to + * query the available settings using [.requestStreamSettings] + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @return Flowable stream of heart rate data. + * Produces: + *

- onNext [PolarHrData] + *

- onError error for possible errors invoked + *

- onComplete non produced unless the stream is further configured + */ + fun startHrStreaming(identifier: String): Flowable + + /** + * Start the ECG (Electrocardiography) stream. ECG stream is stopped if the connection is closed, + * error occurs or stream is disposed. Requires feature [.FEATURE_POLAR_ONLINE_STREAMING]. + * Before starting the stream it is recommended to query the available settings using [.requestStreamSettings] + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param sensorSetting settings to be used to start streaming + * @return Flowable stream of [PolarEcgData] + * Produces: + *

- onNext [PolarEcgData] + *

- onError error for possible errors invoked + *

- onComplete non produced unless stream is further configured + */ + fun startEcgStreaming( + identifier: String, + sensorSetting: PolarSensorSetting + ): Flowable + + /** + * Start ACC (Accelerometer) stream. ACC stream is stopped if the connection is closed, error + * occurs or stream is disposed. Requires feature [.FEATURE_POLAR_ONLINE_STREAMING]. + * Before starting the stream it is recommended to query the available settings using [.requestStreamSettings] + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param sensorSetting settings to be used to start streaming + * @return Flowable stream of [PolarAccelerometerData] + * Produces: + *

- onNext [PolarAccelerometerData] + *

- onError error for possible errors invoked + *

- onComplete non produced unless stream is further configured + */ + fun startAccStreaming( + identifier: String, + sensorSetting: PolarSensorSetting + ): Flowable + + /** + * Start OHR (Optical heart rate) PPG (Photoplethysmography) stream. PPG stream is stopped if + * the connection is closed, error occurs or stream is disposed. Requires feature + * [.FEATURE_POLAR_ONLINE_STREAMING]. Before starting the stream it is recommended to + * query the available settings using [.requestStreamSettings] + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param sensorSetting settings to be used to start streaming + * @return Flowable stream of OHR PPG data. + * Produces: + *

- onNext [PolarOhrData] + *

- onError error for possible errors invoked + *

- onComplete non produced unless the stream is further configured + */ + @Deprecated("API is renamed, please use startPpgStreaming()") + fun startOhrStreaming( + identifier: String, + sensorSetting: PolarSensorSetting + ): Flowable + + /** + * Start optical sensor PPG (Photoplethysmography) stream. PPG stream is stopped if + * the connection is closed, error occurs or stream is disposed. Requires feature + * [.FEATURE_POLAR_ONLINE_STREAMING]. Before starting the stream it is recommended to + * query the available settings using [.requestStreamSettings] + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param sensorSetting settings to be used to start streaming + * @return Flowable stream of OHR PPG data. + * Produces: + *

- onNext [PolarOhrData] + *

- onError error for possible errors invoked + *

- onComplete non produced unless the stream is further configured + */ + fun startPpgStreaming( + identifier: String, + sensorSetting: PolarSensorSetting + ): Flowable + + /** + * Start OHR (Optical heart rate) PPI (Pulse to Pulse interval) stream. PPI stream is stopped if + * the connection is closed, error occurs or stream is disposed. Notice that there is a + * delay before PPI data stream starts. Requires feature [.FEATURE_POLAR_ONLINE_STREAMING]. + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @return Flowable stream of OHR PPI data. + * Produces: + *

- onNext [PolarOhrPPIData] + *

- onError error for possible errors invoked + *

- onComplete non produced unless the stream is further configured + */ + fun startOhrPPIStreaming(identifier: String): Flowable + + /** + * Start magnetometer stream. Magnetometer stream is stopped if the connection is closed, error + * occurs or stream is disposed. Requires feature [.FEATURE_POLAR_ONLINE_STREAMING]. + * Before starting the stream it is recommended to query the available settings using [.requestStreamSettings] + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param sensorSetting settings to be used to start streaming + * @return Flowable stream of magnetometer data. + * Produces: + *

- onNext [PolarMagnetometerData] + *

- onError error for possible errors invoked + *

- onComplete non produced unless the stream is further configured + */ + fun startMagnetometerStreaming( + identifier: String, + sensorSetting: PolarSensorSetting + ): Flowable + + /** + * Start Gyro stream. Gyro stream is stopped if the connection is closed, error occurs during + * start or stream is disposed. Requires feature [.FEATURE_POLAR_ONLINE_STREAMING]. + * Before starting the stream it is recommended to query the available settings using [.requestStreamSettings] + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @param sensorSetting settings to be used to start streaming + * @return Flowable stream of gyroscope data. + * Produces: + *

- onNext [PolarGyroData] + *

- onError error for possible errors invoked + *

- onComplete non produced unless the stream is further configured + */ + fun startGyroStreaming( + identifier: String, + sensorSetting: PolarSensorSetting + ): Flowable +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarSdkModeApi.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarSdkModeApi.kt new file mode 100644 index 00000000..bbd10f0e --- /dev/null +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarSdkModeApi.kt @@ -0,0 +1,52 @@ +package com.polar.sdk.api + +import com.polar.sdk.api.PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_SDK_MODE +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +/** + * Polar SDK mode API + * + * In SDK mode the wider range of capabilities is available for the online streaming or + * for the offline recording than in normal operation mode. The available capabilities can be + * asked from device using [PolarOnlineStreamingApi.requestFullStreamSettings] or [PolarOfflineRecordingApi.requestFullOfflineRecordingSettings] + * + * Requires features [FEATURE_POLAR_SDK_MODE] + * + * Note, SDK mode supported by VeritySense starting from firmware 1.1.5 + */ +interface PolarSdkModeApi { + + /** + * Enables SDK mode. + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @return Completable stream produces: + * success if SDK mode is enabled or device is already in SDK mode + * error if SDK mode enable failed + */ + fun enableSDKMode(identifier: String): Completable + + /** + * Disables SDK mode. + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @return Completable stream produces: + * success if SDK mode is disabled or SDK mode was already disabled + * error if SDK mode disable failed + */ + fun disableSDKMode(identifier: String): Completable + + /** + * Check if SDK mode currently enabled. + * + * Note, SDK status check is supported by VeritySense starting from firmware 2.1.0 + * + * @param identifier Polar device id found printed on the sensor/device or bt address + * @return [Single] + * Produces: + *

- onSuccess true, if the SDK mode is currently enabled + *

- onError status request failed + */ + fun isSDKModeEnabled(identifier: String): Single +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/errors/PolarOfflineRecordingError.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/errors/PolarOfflineRecordingError.kt new file mode 100644 index 00000000..bb49cd88 --- /dev/null +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/errors/PolarOfflineRecordingError.kt @@ -0,0 +1,7 @@ +// Copyright © 2019 Polar Electro Oy. All rights reserved. +package com.polar.sdk.api.errors + +/** + * Offline recording general error + */ +class PolarOfflineRecordingError(detailMessage: String) : Exception(detailMessage) \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarHrData.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarHrData.kt index 2dfb26c3..2ba335f8 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarHrData.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarHrData.kt @@ -3,29 +3,31 @@ package com.polar.sdk.api.model import kotlin.math.roundToInt -/** - * Polar heart rate data - * @property hr value is heart rate in BPM (beats per minute). - * @property rrs list of rrs values. R is the peak of the QRS complex in the ECG wave and RR is the interval between successive Rs. In 1/1024 format. - * @property rrsMs RRs in milliseconds. - * @property contactStatus true if the sensor has contact (with a measurable surface e.g. skin) - * @property contactStatusSupported true if the sensor supports contact status - * @property rrAvailable true if RR data is available. - */ - data class PolarHrData( - val hr: Int, - val rrs: List, - val contactStatus: Boolean, - val contactStatusSupported: Boolean, - val rrAvailable: Boolean + val samples: List ) { - val rrsMs: List + /** + * Polar heart rate sample + * @property hr value is heart rate in BPM (beats per minute). + * @property rrsMs RRs in milliseconds. + * @property contactStatus true if the sensor has contact (with a measurable surface e.g. skin) + * @property contactStatusSupported true if the sensor supports contact status + * @property rrAvailable true if RR data is available. + */ + class PolarHrSample( + val hr: Int, + private val rrs: List, + val contactStatus: Boolean, + val contactStatusSupported: Boolean, + val rrAvailable: Boolean + ) { + val rrsMs: List - init { - rrsMs = mutableListOf() - for (rrRaw in rrs) { - rrsMs.add((rrRaw.toFloat() / 1024.0 * 1000.0).roundToInt()) + init { + rrsMs = mutableListOf() + for (rrRaw in rrs) { + rrsMs.add((rrRaw.toFloat() / 1024.0 * 1000.0).roundToInt()) + } } } } \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingData.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingData.kt new file mode 100644 index 00000000..5324f7b6 --- /dev/null +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingData.kt @@ -0,0 +1,63 @@ +// Copyright © 2022 Polar Electro Oy. All rights reserved. +package com.polar.sdk.api.model + +import java.util.* + +/** + * Polar Offline recording data + * + * @property startTime the time recording was started in UTC time + */ +sealed class PolarOfflineRecordingData(val startTime: Calendar, val settings: PolarSensorSetting?) { + /** + * Accelerometer offline recording data + * + * @property data acc data + * @property startTime the time recording was started in UTC time + * @property settings the settings used while recording + */ + class AccOfflineRecording(val data: PolarAccelerometerData, startTime: Calendar, settings: PolarSensorSetting) : PolarOfflineRecordingData(startTime, settings) + + /** + * Gyroscope Offline recording data + * + * @property data gyro data + * @property startTime the time recording was started in UTC time + * @property settings the settings used while recording + */ + class GyroOfflineRecording(val data: PolarGyroData, startTime: Calendar, settings: PolarSensorSetting) : PolarOfflineRecordingData(startTime, settings) + + /** + * Magnetometer offline recording data + * + * @property data magnetometer data + * @property startTime the time recording was started in UTC time + * @property settings the settings used while recording + */ + class MagOfflineRecording(val data: PolarMagnetometerData, startTime: Calendar, settings: PolarSensorSetting) : PolarOfflineRecordingData(startTime, settings) + + /** + * PPG (Photoplethysmography) offline recording data + * + * @property data ppg data + * @property startTime the time recording was started in UTC time + * @property settings the settings used while recording + */ + class PpgOfflineRecording(val data: PolarOhrData, startTime: Calendar, settings: PolarSensorSetting) : PolarOfflineRecordingData(startTime, settings) + + /** + * PPI (Peak-to-peak interval) offline recording data + * + * @property data ppi data + * @property startTime the time recording was started in UTC time + */ + class PpiOfflineRecording(val data: PolarOhrPPIData, startTime: Calendar) : PolarOfflineRecordingData(startTime, null) + + /** + * Heart rate offline recording data + * + * @property data heart rate data + * @property startTime the time recording was started in UTC time + */ + class HrOfflineRecording(val data: PolarHrData, startTime: Calendar) : PolarOfflineRecordingData(startTime, null) +} diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingEntry.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingEntry.kt new file mode 100644 index 00000000..5c5db2f3 --- /dev/null +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingEntry.kt @@ -0,0 +1,31 @@ +// Copyright © 2022 Polar Electro Oy. All rights reserved. +package com.polar.sdk.api.model + +import com.polar.sdk.api.PolarBleApi +import java.util.* + +/** + * Polar offline recording entry container. + */ +data class PolarOfflineRecordingEntry( + /** + * Recording entry path in device. + */ + @JvmField + val path: String, + /** + * Recording size in bytes. + */ + @JvmField + val size: Long, + /** + * The date and time of the recording entry i.e. the moment recording is started + */ + @JvmField + val date: Date, + /** + * data type of the recording + */ + @JvmField + val type: PolarBleApi.PolarDeviceDataType +) \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingTriggerMode.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingTriggerMode.kt new file mode 100644 index 00000000..e29325ca --- /dev/null +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingTriggerMode.kt @@ -0,0 +1,29 @@ +package com.polar.sdk.api.model + +import com.polar.sdk.api.PolarBleApi + +/** + * Polar offline recording trigger mode. Based on the trigger mode the + * device starts the offline recording automatically + */ +enum class PolarOfflineRecordingTriggerMode { + // The automatic start of offline recording is disabled + TRIGGER_DISABLED, + + // Triggers the offline recording when device is powered on + TRIGGER_SYSTEM_START, + + // Triggers the offline recording when exercise is started in device + TRIGGER_EXERCISE_START +} + +/** + * Polar offline recording trigger + * @property triggerMode the mode of the trigger + * @property triggerFeatures features enabled with the trigger, empty if [triggerMode] is [PolarOfflineRecordingTriggerMode.TRIGGER_DISABLED]. + * In case of the [PolarBleApi.PolarDeviceDataType.PPI] and [PolarBleApi.PolarDeviceDataType.HR] the [PolarSensorSetting] is null + */ +data class PolarOfflineRecordingTrigger( + val triggerMode: PolarOfflineRecordingTriggerMode, + val triggerFeatures: Map +) \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarRecordingSecret.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarRecordingSecret.kt new file mode 100644 index 00000000..dbba58f6 --- /dev/null +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarRecordingSecret.kt @@ -0,0 +1,21 @@ +// Copyright © 2022 Polar Electro Oy. All rights reserved. +package com.polar.sdk.api.model + +import androidx.annotation.Size +import javax.crypto.spec.SecretKeySpec + +/** + * Polar recording secret is used to encrypt the recording. + */ +class PolarRecordingSecret( + /** + * Secret key of size 16 bytes. Supported encryption is AES_128 + */ + @Size(16) key: ByteArray +) { + val secret: SecretKeySpec = SecretKeySpec(key, "AES") + + init { + require(key.size == 16) { "key must be size of 16 bytes (128bits), was ${key.size}" } + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/BDBleApiImpl.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/BDBleApiImpl.kt index 05dc5894..84c9c22d 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/BDBleApiImpl.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/BDBleApiImpl.kt @@ -15,32 +15,41 @@ import com.polar.androidcommunications.api.ble.model.gatt.BleGattBase import com.polar.androidcommunications.api.ble.model.gatt.client.BleBattClient import com.polar.androidcommunications.api.ble.model.gatt.client.BleDisClient import com.polar.androidcommunications.api.ble.model.gatt.client.BleHrClient -import com.polar.androidcommunications.api.ble.model.gatt.client.BleHrClient.HrNotificationData +import com.polar.androidcommunications.api.ble.model.gatt.client.BleHrClient.* import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.* import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdControlPointResponse.PmdControlPointResponseCode import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.model.* import com.polar.androidcommunications.api.ble.model.gatt.client.psftp.BlePsFtpClient import com.polar.androidcommunications.api.ble.model.gatt.client.psftp.BlePsFtpUtils import com.polar.androidcommunications.api.ble.model.gatt.client.psftp.BlePsFtpUtils.PftpResponseError +import com.polar.androidcommunications.api.ble.model.offlinerecording.OfflineRecordingData +import com.polar.androidcommunications.api.ble.model.offlinerecording.OfflineRecordingUtility.mapOfflineRecordingFileNameToMeasurementType import com.polar.androidcommunications.api.ble.model.polar.BlePolarDeviceCapabilitiesUtility.Companion.getFileSystemType import com.polar.androidcommunications.api.ble.model.polar.BlePolarDeviceCapabilitiesUtility.Companion.isRecordingSupported import com.polar.androidcommunications.api.ble.model.polar.BlePolarDeviceCapabilitiesUtility.FileSystemType import com.polar.androidcommunications.enpoints.ble.bluedroid.host.BDDeviceListenerImpl import com.polar.sdk.api.PolarBleApi import com.polar.sdk.api.PolarBleApiCallbackProvider +import com.polar.sdk.api.PolarH10OfflineExerciseApi import com.polar.sdk.api.errors.* import com.polar.sdk.api.model.* -import com.polar.sdk.api.model.utils.PolarDataUtils -import com.polar.sdk.api.model.utils.PolarDataUtils.mapPMDClientOhrDataToPolarOhr -import com.polar.sdk.api.model.utils.PolarDataUtils.mapPMDClientPpiDataToPolarOhrPpiData -import com.polar.sdk.api.model.utils.PolarDataUtils.mapPmdClientAccDataToPolarAcc -import com.polar.sdk.api.model.utils.PolarDataUtils.mapPmdClientGyroDataToPolarGyro -import com.polar.sdk.api.model.utils.PolarDataUtils.mapPmdClientMagDataToPolarMagnetometer -import com.polar.sdk.api.model.utils.PolarDataUtils.mapPmdSettingsToPolarSettings -import com.polar.sdk.api.model.utils.PolarDataUtils.mapPolarSettingsToPmdSettings -import com.polar.sdk.api.model.utils.PolarTimeUtils.javaCalendarToPbPftpSetLocalTime -import com.polar.sdk.api.model.utils.PolarTimeUtils.javaCalendarToPbPftpSetSystemTime -import com.polar.sdk.api.model.utils.PolarTimeUtils.pbLocalTimeToJavaCalendar +import com.polar.sdk.impl.utils.PolarDataUtils +import com.polar.sdk.impl.utils.PolarDataUtils.mapPMDClientOfflineHrDataToPolarHrData +import com.polar.sdk.impl.utils.PolarDataUtils.mapPMDClientOhrDataToPolarOhr +import com.polar.sdk.impl.utils.PolarDataUtils.mapPMDClientPpiDataToPolarOhrPpiData +import com.polar.sdk.impl.utils.PolarDataUtils.mapPmdClientAccDataToPolarAcc +import com.polar.sdk.impl.utils.PolarDataUtils.mapPmdClientFeatureToPolarFeature +import com.polar.sdk.impl.utils.PolarDataUtils.mapPmdClientGyroDataToPolarGyro +import com.polar.sdk.impl.utils.PolarDataUtils.mapPmdClientMagDataToPolarMagnetometer +import com.polar.sdk.impl.utils.PolarDataUtils.mapPmdSettingsToPolarSettings +import com.polar.sdk.impl.utils.PolarDataUtils.mapPmdTriggerToPolarTrigger +import com.polar.sdk.impl.utils.PolarDataUtils.mapPolarFeatureToPmdClientMeasurementType +import com.polar.sdk.impl.utils.PolarDataUtils.mapPolarOfflineTriggerToPmdOfflineTrigger +import com.polar.sdk.impl.utils.PolarDataUtils.mapPolarSecretToPmdSecret +import com.polar.sdk.impl.utils.PolarDataUtils.mapPolarSettingsToPmdSettings +import com.polar.sdk.impl.utils.PolarTimeUtils.javaCalendarToPbPftpSetLocalTime +import com.polar.sdk.impl.utils.PolarTimeUtils.javaCalendarToPbPftpSetSystemTime +import com.polar.sdk.impl.utils.PolarTimeUtils.pbLocalTimeToJavaCalendar import fi.polar.remote.representation.protobuf.ExerciseSamples.PbExerciseSamples import fi.polar.remote.representation.protobuf.Types.* import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers @@ -67,10 +76,12 @@ import java.util.concurrent.TimeUnit /** * The default implementation of the Polar API + * @Suppress */ -class BDBleApiImpl private constructor(context: Context, features: Int) : PolarBleApi(features), BlePowerStateChangedCallback { +class BDBleApiImpl private constructor(context: Context, features: Set) : PolarBleApi(features), BlePowerStateChangedCallback { private val connectSubscriptions: MutableMap = mutableMapOf() private val deviceDataMonitorDisposable: MutableMap = mutableMapOf() + private val deviceAvailableFeaturesDisposable: MutableMap = mutableMapOf() private val stopPmdStreamingDisposable: MutableMap = mutableMapOf() private val filter = BleSearchPreFilter { content: BleAdvertisementContent -> content.polarDeviceId.isNotEmpty() && content.polarDeviceType != "mobile" } private var listener: BleDeviceListener? @@ -79,22 +90,26 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB private var logger: PolarBleApiLogger? = null init { - val clients: MutableSet> = HashSet() - if (this.features and FEATURE_HR != 0) { - clients.add(BleHrClient::class.java) - } - if (this.features and FEATURE_DEVICE_INFO != 0) { - clients.add(BleDisClient::class.java) - } - if (this.features and FEATURE_BATTERY_INFO != 0) { - clients.add(BleBattClient::class.java) - } - if (this.features and FEATURE_POLAR_SENSOR_STREAMING != 0) { - clients.add(BlePMDClient::class.java) - } - if (this.features and FEATURE_POLAR_FILE_TRANSFER != 0) { - clients.add(BlePsFtpClient::class.java) + val clients: MutableSet> = mutableSetOf() + for (feature in features) { + when (feature) { + PolarBleSdkFeature.FEATURE_HR -> clients.add(BleHrClient::class.java) + PolarBleSdkFeature.FEATURE_DEVICE_INFO -> clients.add(BleDisClient::class.java) + PolarBleSdkFeature.FEATURE_BATTERY_INFO -> clients.add(BleBattClient::class.java) + PolarBleSdkFeature.FEATURE_POLAR_ONLINE_STREAMING -> { + clients.add(BleHrClient::class.java) + clients.add(BlePMDClient::class.java) + } + PolarBleSdkFeature.FEATURE_POLAR_OFFLINE_RECORDING -> { + clients.add(BlePMDClient::class.java) + clients.add(BlePsFtpClient::class.java) + } + PolarBleSdkFeature.FEATURE_POLAR_H10_EXERCISE_RECORDING -> clients.add(BlePsFtpClient::class.java) + PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP -> clients.add(BlePsFtpClient::class.java) + PolarBleSdkFeature.FEATURE_POLAR_SDK_MODE -> clients.add(BlePMDClient::class.java) + } } + val bdDeviceListenerImpl = BDDeviceListenerImpl(context, clients) bdDeviceListenerImpl.setScanPreFilter(filter) bdDeviceListenerImpl.setBlePowerStateCallback(this) @@ -133,6 +148,12 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB } } + for (disposable in deviceAvailableFeaturesDisposable.values) { + if (!disposable.isDisposed) { + disposable.dispose() + } + } + devicesStateMonitorDisposable?.dispose() devicesStateMonitorDisposable = null @@ -165,18 +186,40 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB } } - override fun isFeatureReady(deviceId: String, feature: Int): Boolean { + override fun isFeatureReady(deviceId: String, feature: PolarBleSdkFeature): Boolean { return try { return when (feature) { - FEATURE_POLAR_FILE_TRANSFER -> { + PolarBleSdkFeature.FEATURE_POLAR_ONLINE_STREAMING -> { + sessionHrClientReady(deviceId) + sessionPmdClientReady(deviceId) + true + } + PolarBleSdkFeature.FEATURE_HR -> { + sessionHrClientReady(deviceId) + true + } + PolarBleSdkFeature.FEATURE_DEVICE_INFO -> { + sessionServiceReady(deviceId, BleDisClient.DIS_SERVICE) + true + } + PolarBleSdkFeature.FEATURE_BATTERY_INFO -> { + sessionServiceReady(deviceId, BleBattClient.BATTERY_SERVICE) + true + } + PolarBleSdkFeature.FEATURE_POLAR_OFFLINE_RECORDING -> { + sessionPmdClientReady(deviceId) + sessionPsFtpClientReady(deviceId) + true + } + PolarBleSdkFeature.FEATURE_POLAR_H10_EXERCISE_RECORDING, + PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP -> { sessionPsFtpClientReady(deviceId) true } - FEATURE_POLAR_SENSOR_STREAMING -> { + PolarBleSdkFeature.FEATURE_POLAR_SDK_MODE -> { sessionPmdClientReady(deviceId) true } - else -> false } } catch (ignored: Throwable) { false @@ -199,22 +242,21 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB } override fun setLocalTime(identifier: String, calendar: Calendar): Completable { - return try { - val session = sessionPsFtpClientReady(identifier) - val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Completable.error(PolarServiceNotAvailable()) - - BleLogger.d(TAG, "set local time to ${calendar.time} device $identifier") - val pbLocalTime = javaCalendarToPbPftpSetLocalTime(calendar) - setSystemTime(client, calendar) - .onErrorComplete() - .andThen( - client.query(PftpRequest.PbPFtpQuery.SET_LOCAL_TIME_VALUE, pbLocalTime.toByteArray()) - .ignoreElement() - ) - + val session = try { + sessionPsFtpClientReady(identifier) } catch (error: Throwable) { - Completable.error(error) + return Completable.error(error) } + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Completable.error(PolarServiceNotAvailable()) + + BleLogger.d(TAG, "set local time to ${calendar.time} device $identifier") + val pbLocalTime = javaCalendarToPbPftpSetLocalTime(calendar) + return setSystemTime(client, calendar) + .onErrorComplete() + .andThen( + client.query(PftpRequest.PbPFtpQuery.SET_LOCAL_TIME_VALUE, pbLocalTime.toByteArray()) + .ignoreElement() + ) } private fun setSystemTime(client: BlePsFtpClient, calendar: Calendar): Completable { @@ -224,87 +266,102 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB } override fun getLocalTime(identifier: String): Single { - return try { - val session = sessionPsFtpClientReady(identifier) - val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? - if (client != null) { - BleLogger.d(TAG, "get local time from device $identifier") - client.query(PftpRequest.PbPFtpQuery.GET_LOCAL_TIME_VALUE, null) - .map { - val dateTime: PftpRequest.PbPFtpSetLocalTimeParams = PftpRequest.PbPFtpSetLocalTimeParams.parseFrom(it.toByteArray()) - pbLocalTimeToJavaCalendar(dateTime) - }.onErrorResumeNext { - if (it is PftpResponseError && it.error == 201) { - Single.error(BleNotSupported("${session.name} do not support getTime")) - } else { - Single.error(it) - } - } - } else { - Single.error(PolarServiceNotAvailable()) - } + val session = try { + sessionPsFtpClientReady(identifier) } catch (error: Throwable) { - Single.error(error) + return Single.error(error) + } + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Single.error(PolarServiceNotAvailable()) + + BleLogger.d(TAG, "get local time from device $identifier") + return client.query(PftpRequest.PbPFtpQuery.GET_LOCAL_TIME_VALUE, null) + .map { + val dateTime: PftpRequest.PbPFtpSetLocalTimeParams = PftpRequest.PbPFtpSetLocalTimeParams.parseFrom(it.toByteArray()) + pbLocalTimeToJavaCalendar(dateTime) + }.onErrorResumeNext { + if (it is PftpResponseError && it.error == 201) { + Single.error(BleNotSupported("${session.name} do not support getTime")) + } else { + Single.error(it) + } + } + } + + override fun requestStreamSettings(identifier: String, feature: PolarDeviceDataType): Single { + return when (feature) { + PolarDeviceDataType.ECG -> querySettings(identifier, PmdMeasurementType.ECG, PmdRecordingType.ONLINE) + PolarDeviceDataType.ACC -> querySettings(identifier, PmdMeasurementType.ACC, PmdRecordingType.ONLINE) + PolarDeviceDataType.PPG -> querySettings(identifier, PmdMeasurementType.PPG, PmdRecordingType.ONLINE) + PolarDeviceDataType.GYRO -> querySettings(identifier, PmdMeasurementType.GYRO, PmdRecordingType.ONLINE) + PolarDeviceDataType.MAGNETOMETER -> querySettings(identifier, PmdMeasurementType.MAGNETOMETER, PmdRecordingType.ONLINE) + PolarDeviceDataType.HR, + PolarDeviceDataType.PPI -> Single.error(PolarOperationNotSupported()) + else -> Single.error(PolarOperationNotSupported()) + } } - override fun requestStreamSettings(identifier: String, feature: DeviceStreamingFeature): Single { + override fun requestFullStreamSettings(identifier: String, feature: PolarDeviceDataType): Single { return when (feature) { - DeviceStreamingFeature.ECG -> querySettings(identifier, PmdMeasurementType.ECG) - DeviceStreamingFeature.ACC -> querySettings(identifier, PmdMeasurementType.ACC) - DeviceStreamingFeature.PPG -> querySettings(identifier, PmdMeasurementType.PPG) - DeviceStreamingFeature.PPI -> Single.error(PolarOperationNotSupported()) - DeviceStreamingFeature.GYRO -> querySettings(identifier, PmdMeasurementType.GYRO) - DeviceStreamingFeature.MAGNETOMETER -> querySettings(identifier, PmdMeasurementType.MAGNETOMETER) + PolarDeviceDataType.ECG -> queryFullSettings(identifier, PmdMeasurementType.ECG, PmdRecordingType.ONLINE) + PolarDeviceDataType.ACC -> queryFullSettings(identifier, PmdMeasurementType.ACC, PmdRecordingType.ONLINE) + PolarDeviceDataType.PPG -> queryFullSettings(identifier, PmdMeasurementType.PPG, PmdRecordingType.ONLINE) + PolarDeviceDataType.GYRO -> queryFullSettings(identifier, PmdMeasurementType.GYRO, PmdRecordingType.ONLINE) + PolarDeviceDataType.MAGNETOMETER -> queryFullSettings(identifier, PmdMeasurementType.MAGNETOMETER, PmdRecordingType.ONLINE) + PolarDeviceDataType.PPI, + PolarDeviceDataType.HR -> Single.error(PolarOperationNotSupported()) + else -> Single.error(PolarOperationNotSupported()) } } - override fun requestFullStreamSettings(identifier: String, feature: DeviceStreamingFeature): Single { + override fun requestOfflineRecordingSettings(identifier: String, feature: PolarDeviceDataType): Single { return when (feature) { - DeviceStreamingFeature.ECG -> queryFullSettings(identifier, PmdMeasurementType.ECG) - DeviceStreamingFeature.ACC -> queryFullSettings(identifier, PmdMeasurementType.ACC) - DeviceStreamingFeature.PPG -> queryFullSettings(identifier, PmdMeasurementType.PPG) - DeviceStreamingFeature.PPI -> Single.error(PolarOperationNotSupported()) - DeviceStreamingFeature.GYRO -> queryFullSettings(identifier, PmdMeasurementType.GYRO) - DeviceStreamingFeature.MAGNETOMETER -> queryFullSettings(identifier, PmdMeasurementType.MAGNETOMETER) + PolarDeviceDataType.ECG -> querySettings(identifier, PmdMeasurementType.ECG, PmdRecordingType.OFFLINE) + PolarDeviceDataType.ACC -> querySettings(identifier, PmdMeasurementType.ACC, PmdRecordingType.OFFLINE) + PolarDeviceDataType.PPG -> querySettings(identifier, PmdMeasurementType.PPG, PmdRecordingType.OFFLINE) + PolarDeviceDataType.GYRO -> querySettings(identifier, PmdMeasurementType.GYRO, PmdRecordingType.OFFLINE) + PolarDeviceDataType.MAGNETOMETER -> querySettings(identifier, PmdMeasurementType.MAGNETOMETER, PmdRecordingType.OFFLINE) + PolarDeviceDataType.HR, + PolarDeviceDataType.PPI -> Single.error(PolarOperationNotSupported()) + else -> Single.error(PolarOperationNotSupported()) } } - private fun querySettings(identifier: String, type: PmdMeasurementType): Single { + override fun requestFullOfflineRecordingSettings(identifier: String, feature: PolarDeviceDataType): Single { + return when (feature) { + PolarDeviceDataType.ECG -> queryFullSettings(identifier, PmdMeasurementType.ECG, PmdRecordingType.OFFLINE) + PolarDeviceDataType.ACC -> queryFullSettings(identifier, PmdMeasurementType.ACC, PmdRecordingType.OFFLINE) + PolarDeviceDataType.PPG -> queryFullSettings(identifier, PmdMeasurementType.PPG, PmdRecordingType.OFFLINE) + PolarDeviceDataType.GYRO -> queryFullSettings(identifier, PmdMeasurementType.GYRO, PmdRecordingType.OFFLINE) + PolarDeviceDataType.MAGNETOMETER -> queryFullSettings(identifier, PmdMeasurementType.MAGNETOMETER, PmdRecordingType.OFFLINE) + PolarDeviceDataType.PPI, + PolarDeviceDataType.HR -> Single.error(PolarOperationNotSupported()) + else -> Single.error(PolarOperationNotSupported()) + } + } + + private fun querySettings(identifier: String, type: PmdMeasurementType, recordingType: PmdRecordingType): Single { return try { val session = sessionPmdClientReady(identifier) - val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? - if (client != null) { - client.querySettings(type) - .map { setting: PmdSetting -> mapPmdSettingsToPolarSettings(setting, fromSelected = false) } - } else { - Single.error(PolarServiceNotAvailable()) - } + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Single.error(PolarServiceNotAvailable()) + client.querySettings(type, recordingType) + .map { setting: PmdSetting -> mapPmdSettingsToPolarSettings(setting, fromSelected = false) } } catch (e: Throwable) { Single.error(e) } } - private fun queryFullSettings(identifier: String, type: PmdMeasurementType): Single { + private fun queryFullSettings(identifier: String, type: PmdMeasurementType, recordingType: PmdRecordingType): Single { return try { val session = sessionPmdClientReady(identifier) - val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? - if (client != null) { - client.queryFullSettings(type) - .map { setting: PmdSetting -> mapPmdSettingsToPolarSettings(setting, fromSelected = false) } - } else { - Single.error(PolarServiceNotAvailable()) - } + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Single.error(PolarServiceNotAvailable()) + client.queryFullSettings(type, recordingType) + .map { setting: PmdSetting -> mapPmdSettingsToPolarSettings(setting, fromSelected = false) } } catch (e: Throwable) { Single.error(e) } } - @Deprecated("in release 3.2.8. Move to the background is not relevant information for SDK starting from release 3.2.8") - override fun backgroundEntered() { - BleLogger.w(TAG, "call of deprecated backgroundEntered() method") - } - override fun foregroundEntered() { listener?.scanRestart() } @@ -399,205 +456,328 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB } } - override fun startRecording(identifier: String, exerciseId: String, interval: RecordingInterval?, type: SampleType): Completable { - return try { - val session = sessionPsFtpClientReady(identifier) - val recordingSupported = isRecordingSupported(session.polarDeviceType) - if (recordingSupported) { - val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? - if (client != null) { - val pbSampleType = if (type === SampleType.HR) PbSampleType.SAMPLE_TYPE_HEART_RATE else PbSampleType.SAMPLE_TYPE_RR_INTERVAL - val recordingInterval = interval?.value ?: RecordingInterval.INTERVAL_1S.value - val duration = PbDuration.newBuilder().setSeconds(recordingInterval).build() - val params = PftpRequest.PbPFtpRequestStartRecordingParams.newBuilder().setSampleDataIdentifier(exerciseId) - .setSampleType(pbSampleType) - .setRecordingInterval(duration) - .build() - return client.query(PftpRequest.PbPFtpQuery.REQUEST_START_RECORDING_VALUE, params.toByteArray()) - .toObservable() - .ignoreElements() - .onErrorResumeNext { throwable: Throwable -> Completable.error(handleError(throwable)) } - } else { - Completable.error(PolarServiceNotAvailable()) - } - } else { - Completable.error(PolarOperationNotSupported()) - } + override fun startRecording(identifier: String, exerciseId: String, interval: PolarH10OfflineExerciseApi.RecordingInterval?, type: PolarH10OfflineExerciseApi.SampleType): Completable { + val session = try { + sessionPsFtpClientReady(identifier) } catch (error: Throwable) { - Completable.error(error) + return Completable.error(error) + } + if (isRecordingSupported(session.polarDeviceType)) { + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Completable.error(PolarServiceNotAvailable()) + val pbSampleType = if (type == PolarH10OfflineExerciseApi.SampleType.HR) PbSampleType.SAMPLE_TYPE_HEART_RATE else PbSampleType.SAMPLE_TYPE_RR_INTERVAL + val recordingInterval = interval?.value ?: PolarH10OfflineExerciseApi.RecordingInterval.INTERVAL_1S.value + val duration = PbDuration.newBuilder().setSeconds(recordingInterval).build() + val params = PftpRequest.PbPFtpRequestStartRecordingParams.newBuilder().setSampleDataIdentifier(exerciseId) + .setSampleType(pbSampleType) + .setRecordingInterval(duration) + .build() + return client.query(PftpRequest.PbPFtpQuery.REQUEST_START_RECORDING_VALUE, params.toByteArray()) + .toObservable() + .ignoreElements() + .onErrorResumeNext { throwable: Throwable -> Completable.error(handleError(throwable)) } + } else { + return Completable.error(PolarOperationNotSupported()) } } override fun stopRecording(identifier: String): Completable { - return try { - val session = sessionPsFtpClientReady(identifier) - if (isRecordingSupported(session.polarDeviceType)) { - val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? - if (client != null) { - client.query(PftpRequest.PbPFtpQuery.REQUEST_STOP_RECORDING_VALUE, null) - .toObservable() - .ignoreElements() - .onErrorResumeNext { throwable: Throwable -> Completable.error(handleError(throwable)) } - } else { - Completable.error(PolarServiceNotAvailable()) - } - } else Completable.error(PolarOperationNotSupported()) + val session = try { + sessionPsFtpClientReady(identifier) } catch (error: Throwable) { - Completable.error(error) + return Completable.error(error) } + if (isRecordingSupported(session.polarDeviceType)) { + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Completable.error(PolarServiceNotAvailable()) + return client.query(PftpRequest.PbPFtpQuery.REQUEST_STOP_RECORDING_VALUE, null) + .toObservable() + .ignoreElements() + .onErrorResumeNext { throwable: Throwable -> Completable.error(handleError(throwable)) } + + } else return Completable.error(PolarOperationNotSupported()) } override fun requestRecordingStatus(identifier: String): Single> { - return try { - val session = sessionPsFtpClientReady(identifier) - if (isRecordingSupported(session.polarDeviceType)) { - val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? - if (client != null) { - client.query(PftpRequest.PbPFtpQuery.REQUEST_RECORDING_STATUS_VALUE, null) - .map { byteArrayOutputStream: ByteArrayOutputStream -> - val result = PbRequestRecordingStatusResult.parseFrom(byteArrayOutputStream.toByteArray()) - androidx.core.util.Pair(result.recordingOn, if (result.hasSampleDataIdentifier()) result.sampleDataIdentifier else "") - }.onErrorResumeNext { throwable: Throwable -> Single.error(handleError(throwable)) } - } else Single.error(PolarServiceNotAvailable()) - } else Single.error(PolarOperationNotSupported()) + val session = try { + sessionPsFtpClientReady(identifier) } catch (error: Throwable) { - Single.error(error) + return Single.error(error) + } + + if (isRecordingSupported(session.polarDeviceType)) { + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Single.error(PolarServiceNotAvailable()) + return client.query(PftpRequest.PbPFtpQuery.REQUEST_RECORDING_STATUS_VALUE, null) + .map { byteArrayOutputStream: ByteArrayOutputStream -> + val result = PbRequestRecordingStatusResult.parseFrom(byteArrayOutputStream.toByteArray()) + androidx.core.util.Pair(result.recordingOn, if (result.hasSampleDataIdentifier()) result.sampleDataIdentifier else "") + }.onErrorResumeNext { throwable: Throwable -> Single.error(handleError(throwable)) } + + } else { + return Single.error(PolarOperationNotSupported()) } } - override fun listExercises(identifier: String): Flowable { - try { - val session = sessionPsFtpClientReady(identifier) - val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? - client?.let { - when (getFileSystemType(session.polarDeviceType)) { - FileSystemType.SAGRFC2_FILE_SYSTEM -> { - return fetchRecursively( - client = client, - path = "/U/0/", - condition = { entry -> - entry.matches(Regex("^([0-9]{8})(/)")) || - entry.matches(Regex("^([0-9]{6})(/)")) || - entry == "E/" || - entry == "SAMPLES.BPB" || - entry == "00/" - } - ).map { entry: Pair -> - val components = entry.first.split("/").toTypedArray() - val format = SimpleDateFormat("yyyyMMdd HHmmss", Locale.getDefault()) - val date = format.parse(components[3] + " " + components[5]) - PolarExerciseEntry(entry.first, date, components[3] + components[5]) - }.onErrorResumeNext { throwable: Throwable -> Flowable.error(handleError(throwable)) } - } - FileSystemType.H10_FILE_SYSTEM -> { - return fetchRecursively( - client = client, - path = "/", - condition = { entry -> entry.endsWith("/") || entry == "SAMPLES.BPB" } - ).map { entry: Pair -> - val components = entry.first.split("/").toTypedArray() - PolarExerciseEntry(entry.first, Date(), components[1]) - } - .onErrorResumeNext { throwable: Throwable -> Flowable.error(handleError(throwable)) } + override fun listOfflineRecordings(identifier: String): Flowable { + val session = try { + sessionPsFtpClientReady(identifier) + } catch (error: Throwable) { + return Flowable.error(error) + } + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Flowable.error(PolarServiceNotAvailable()) + + when (getFileSystemType(session.polarDeviceType)) { + FileSystemType.SAGRFC2_FILE_SYSTEM -> { + return fetchRecursively( + client = client, + path = "/U/0/", + condition = { entry -> + entry.matches(Regex("^([0-9]{8})(/)")) || + entry == "R/" || + entry.matches(Regex("^([0-9]{6})(/)")) || + entry.contains(".REC") } - else -> return Flowable.error(PolarOperationNotSupported()) - } + ).map { entry: Pair -> + val components = entry.first.split("/").toTypedArray() + val format = SimpleDateFormat("yyyyMMdd HHmmss", Locale.getDefault()) + val date = format.parse(components[3] + " " + components[5]) + ?: throw PolarInvalidArgument("Listing offline recording failed. Cannot parse create data from date ${components[3]} and time ${components[5]}") + val type = mapPmdClientFeatureToPolarFeature(mapOfflineRecordingFileNameToMeasurementType(components[6])) + PolarOfflineRecordingEntry(path = entry.first, size = entry.second, date = date, type = type) + }.onErrorResumeNext { throwable: Throwable -> Flowable.error(handleError(throwable)) } } - return Flowable.error(PolarServiceNotAvailable()) + else -> return Flowable.error(PolarOperationNotSupported()) + } + } + + override fun listExercises(identifier: String): Flowable { + val session = try { + sessionPsFtpClientReady(identifier) } catch (error: Throwable) { return Flowable.error(error) } + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Flowable.error(PolarServiceNotAvailable()) + + when (getFileSystemType(session.polarDeviceType)) { + FileSystemType.SAGRFC2_FILE_SYSTEM -> { + return fetchRecursively(client = client, + path = "/U/0/", + condition = { entry -> + entry.matches(Regex("^([0-9]{8})(/)")) || + entry.matches(Regex("^([0-9]{6})(/)")) || + entry == "E/" || + entry == "SAMPLES.BPB" || + entry == "00/" + }) + .map { entry: Pair -> + val components = entry.first.split("/").toTypedArray() + val format = SimpleDateFormat("yyyyMMdd HHmmss", Locale.getDefault()) + val date = format.parse(components[3] + " " + components[5]) + PolarExerciseEntry(entry.first, date, components[3] + components[5]) + } + .onErrorResumeNext { throwable: Throwable -> Flowable.error(handleError(throwable)) } + } + FileSystemType.H10_FILE_SYSTEM -> { + return fetchRecursively(client = client, + path = "/", + condition = { entry -> entry.endsWith("/") || entry == "SAMPLES.BPB" }) + .map { entry: Pair -> + val components = entry.first.split("/").toTypedArray() + PolarExerciseEntry(entry.first, Date(), components[1]) + } + .onErrorResumeNext { throwable: Throwable -> Flowable.error(handleError(throwable)) } + } + else -> return Flowable.error(PolarOperationNotSupported()) + } } + override fun fetchExercise(identifier: String, entry: PolarExerciseEntry): Single { - return try { - val session = sessionPsFtpClientReady(identifier) - val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Single.error(PolarServiceNotAvailable()) - val fsType = getFileSystemType(session.polarDeviceType) + val session = try { + sessionPsFtpClientReady(identifier) + } catch (error: Throwable) { + return Single.error(error) + } - val beforeFetch = if (fsType == FileSystemType.H10_FILE_SYSTEM) { - client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.INITIALIZE_SESSION_VALUE, null) - .andThen(client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.START_SYNC_VALUE, null)) - } else { - Completable.complete() - } + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Single.error(PolarServiceNotAvailable()) + val fsType = getFileSystemType(session.polarDeviceType) + val beforeFetch = if (fsType == FileSystemType.H10_FILE_SYSTEM) { + client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.INITIALIZE_SESSION_VALUE, null) + .andThen(client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.START_SYNC_VALUE, null)) + } else { + Completable.complete() + } - val afterFetch = if (fsType == FileSystemType.H10_FILE_SYSTEM) { - client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.STOP_SYNC_VALUE, null) - .andThen(client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.TERMINATE_SESSION_VALUE, null)) - } else { - Completable.complete() + val afterFetch = if (fsType == FileSystemType.H10_FILE_SYSTEM) { + client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.STOP_SYNC_VALUE, null) + .andThen(client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.TERMINATE_SESSION_VALUE, null)) + } else { + Completable.complete() + } + + val builder = PftpRequest.PbPFtpOperation.newBuilder() + builder.command = PftpRequest.PbPFtpOperation.Command.GET + builder.path = entry.path + + return beforeFetch + .andThen(client.request(builder.build().toByteArray())) + .map { byteArrayOutputStream: ByteArrayOutputStream -> + val samples = PbExerciseSamples.parseFrom(byteArrayOutputStream.toByteArray()) + if (samples.hasRrSamples()) { + return@map PolarExerciseData(samples.recordingInterval.seconds, samples.rrSamples.rrIntervalsList) + } else { + return@map PolarExerciseData(samples.recordingInterval.seconds, samples.heartRateSamplesList) + } + } + .onErrorResumeNext { throwable: Throwable -> Single.error(handleError(throwable)) } + .doFinally { + afterFetch + .onErrorComplete() + .subscribe() } + } + + override fun getOfflineRecord(identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret?): Single { + val session = try { + sessionPsFtpClientReady(identifier) + } catch (e: Exception) { + return Single.error(e) + } + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Single.error(PolarServiceNotAvailable()) + val fsType = getFileSystemType(session.polarDeviceType) + return if (fsType == FileSystemType.SAGRFC2_FILE_SYSTEM) { val builder = PftpRequest.PbPFtpOperation.newBuilder() builder.command = PftpRequest.PbPFtpOperation.Command.GET builder.path = entry.path - beforeFetch + //TODO add back the INITIALIZE_SESSION_VALUE + //client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.INITIALIZE_SESSION_VALUE, null) + Completable.complete() .andThen(client.request(builder.build().toByteArray())) .map { byteArrayOutputStream: ByteArrayOutputStream -> - val samples = PbExerciseSamples.parseFrom(byteArrayOutputStream.toByteArray()) - if (samples.hasRrSamples()) { - return@map PolarExerciseData(samples.recordingInterval.seconds, samples.rrSamples.rrIntervalsList) - } else { - return@map PolarExerciseData(samples.recordingInterval.seconds, samples.heartRateSamplesList) - } - } - .onErrorResumeNext { throwable: Throwable -> Single.error(handleError(throwable)) } - .doFinally { - afterFetch - .onErrorComplete() - .subscribe() + val pmdSecret = secret?.let { mapPolarSecretToPmdSecret(it) } + OfflineRecordingData.parseDataFromOfflineFile(byteArrayOutputStream.toByteArray(), mapPolarFeatureToPmdClientMeasurementType(entry.type), pmdSecret) } - } catch (error: Throwable) { - Single.error(error) - } + .map { offlineRecData -> + val polarSettings = offlineRecData.recordingSettings?.let { mapPmdSettingsToPolarSettings(it, fromSelected = false) } + val startTime = offlineRecData.startTime + when (val offlineData = offlineRecData.data) { + is AccData -> { + polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Acc data is missing settings") + PolarOfflineRecordingData.AccOfflineRecording(mapPmdClientAccDataToPolarAcc(offlineData), startTime, polarSettings) + } + is GyrData -> { + polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Gyro data is missing settings") + PolarOfflineRecordingData.GyroOfflineRecording(mapPmdClientGyroDataToPolarGyro(offlineData), startTime, polarSettings) + } + is MagData -> { + polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Magnetometer data is missing settings") + PolarOfflineRecordingData.MagOfflineRecording(mapPmdClientMagDataToPolarMagnetometer(offlineData), startTime, polarSettings) + } + is PpgData -> { + polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Ppg data is missing settings") + PolarOfflineRecordingData.PpgOfflineRecording(mapPMDClientOhrDataToPolarOhr(offlineData), startTime, polarSettings) + } + is PpiData -> PolarOfflineRecordingData.PpiOfflineRecording(mapPMDClientPpiDataToPolarOhrPpiData(offlineData), startTime) + is OfflineHrData -> PolarOfflineRecordingData.HrOfflineRecording(mapPMDClientOfflineHrDataToPolarHrData(offlineData), startTime) + else -> throw PolarOfflineRecordingError("Data type is not supported.") + } + }.onErrorResumeNext { throwable: Throwable -> Single.error(handleError(throwable)) } + // TODO add back the TERMINATE_SESSION_VALUE + //.doFinally { + // client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.TERMINATE_SESSION_VALUE, null) + // .onErrorComplete() + // .subscribe() + //} + + } else Single.error(PolarOperationNotSupported()) } override fun removeExercise(identifier: String, entry: PolarExerciseEntry): Completable { - return try { - val session = sessionPsFtpClientReady(identifier) - val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? - if (client != null) { - val fsType = getFileSystemType(session.polarDeviceType) - if (fsType === FileSystemType.SAGRFC2_FILE_SYSTEM) { - val builder = PftpRequest.PbPFtpOperation.newBuilder() - builder.command = PftpRequest.PbPFtpOperation.Command.GET - val components = entry.path.split("/").toTypedArray() - val exerciseParent = "/U/0/" + components[3] + "/E/" - builder.path = exerciseParent - return client.request(builder.build().toByteArray()) - .flatMap { byteArrayOutputStream: ByteArrayOutputStream -> - val directory = PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray()) - val removeBuilder = PftpRequest.PbPFtpOperation.newBuilder() - removeBuilder.command = PftpRequest.PbPFtpOperation.Command.REMOVE - if (directory.entriesCount <= 1) { - // remove entire directory - removeBuilder.path = "/U/0/" + components[3] + "/" - } else { - // remove only exercise - removeBuilder.path = "/U/0/" + components[3] + "/E/" + components[5] + "/" - } - client.request(removeBuilder.build().toByteArray()) + val session = try { + sessionPsFtpClientReady(identifier) + } catch (error: Throwable) { + return Completable.error(error) + } + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Completable.error(PolarServiceNotAvailable()) + + when (getFileSystemType(session.polarDeviceType)) { + FileSystemType.SAGRFC2_FILE_SYSTEM -> { + val builder = PftpRequest.PbPFtpOperation.newBuilder() + builder.command = PftpRequest.PbPFtpOperation.Command.GET + val components = entry.path.split("/").toTypedArray() + val exerciseParent = "/U/0/" + components[3] + "/E/" + builder.path = exerciseParent + + return client.request(builder.build().toByteArray()) + .flatMap { byteArrayOutputStream: ByteArrayOutputStream -> + val directory = PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray()) + val removeBuilder = PftpRequest.PbPFtpOperation.newBuilder() + removeBuilder.command = PftpRequest.PbPFtpOperation.Command.REMOVE + if (directory.entriesCount <= 1) { + // remove entire directory + removeBuilder.path = "/U/0/" + components[3] + "/" + } else { + // remove only exercise + removeBuilder.path = "/U/0/" + components[3] + "/E/" + components[5] + "/" } - .toObservable() - .ignoreElements() - .onErrorResumeNext { throwable: Throwable -> Completable.error(handleError(throwable)) } - } else if (fsType === FileSystemType.H10_FILE_SYSTEM) { - val builder = PftpRequest.PbPFtpOperation.newBuilder() - builder.command = PftpRequest.PbPFtpOperation.Command.REMOVE - builder.path = entry.path - return client.request(builder.build().toByteArray()) - .toObservable() - .ignoreElements() - .onErrorResumeNext { throwable: Throwable -> Completable.error(handleError(throwable)) } - } - Completable.error(PolarOperationNotSupported()) - } else { - Completable.error(PolarServiceNotAvailable()) + client.request(removeBuilder.build().toByteArray()) + } + .toObservable() + .ignoreElements() + .onErrorResumeNext { throwable: Throwable -> Completable.error(handleError(throwable)) } + } + FileSystemType.H10_FILE_SYSTEM -> { + val builder = PftpRequest.PbPFtpOperation.newBuilder() + builder.command = PftpRequest.PbPFtpOperation.Command.REMOVE + builder.path = entry.path + return client.request(builder.build().toByteArray()) + .toObservable() + .ignoreElements() + .onErrorResumeNext { throwable: Throwable -> Completable.error(handleError(throwable)) } + } + FileSystemType.UNKNOWN_FILE_SYSTEM -> { + return Completable.error(PolarOperationNotSupported()) } + } + } + + override fun removeOfflineRecord(identifier: String, entry: PolarOfflineRecordingEntry): Completable { + BleLogger.d(TAG, "Remove offline record from device $identifier path ${entry.path}") + val session = try { + sessionPsFtpClientReady(identifier) } catch (error: Throwable) { - Completable.error(error) + return Completable.error(error) + } + + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Completable.error(PolarServiceNotAvailable()) + val fsType = getFileSystemType(session.polarDeviceType) + if (fsType == FileSystemType.SAGRFC2_FILE_SYSTEM) { + val builder = PftpRequest.PbPFtpOperation.newBuilder() + builder.command = PftpRequest.PbPFtpOperation.Command.GET + val recordingFolder = entry.path.dropLastWhile { it != '/' } + builder.path = recordingFolder + return client.request(builder.build().toByteArray()) + .flatMap { byteArrayOutputStream: ByteArrayOutputStream -> + val directory = PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray()) + val removeBuilder = PftpRequest.PbPFtpOperation.newBuilder() + removeBuilder.command = PftpRequest.PbPFtpOperation.Command.REMOVE + if (directory.entriesCount <= 1) { + // remove entire directory + removeBuilder.path = recordingFolder + BleLogger.d(TAG, "Remove entire directory ${removeBuilder.path}") + } else { + // remove only recording + removeBuilder.path = entry.path + BleLogger.d(TAG, "Remove only recording ${removeBuilder.path}") + + } + client.request(removeBuilder.build().toByteArray()) + } + .toObservable() + .ignoreElements() + .onErrorResumeNext { throwable: Throwable -> Completable.error(handleError(throwable)) } + } else { + return Completable.error(PolarOperationNotSupported()) } } @@ -647,15 +827,11 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB private fun startStreaming(identifier: String, type: PmdMeasurementType, setting: PolarSensorSetting, observer: Function>): Flowable { return try { val session = sessionPmdClientReady(identifier) - val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? - if (client != null) { - client.startMeasurement(type, mapPolarSettingsToPmdSettings(setting)) - .andThen(observer.apply(client) - .onErrorResumeNext { throwable: Throwable -> Flowable.error(handleError(throwable)) } - .doFinally { stopPmdStreaming(session, client, type) }) - } else { - Flowable.error(PolarServiceNotAvailable()) - } + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Flowable.error(PolarServiceNotAvailable()) + client.startMeasurement(type, mapPolarSettingsToPmdSettings(setting)) + .andThen(observer.apply(client) + .onErrorResumeNext { throwable: Throwable -> Flowable.error(handleError(throwable)) } + .doFinally { stopPmdStreaming(session, client, type) }) } catch (t: Throwable) { Flowable.error(t) } @@ -670,6 +846,93 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB } } + override fun startOfflineRecording(identifier: String, feature: PolarDeviceDataType, settings: PolarSensorSetting?, secret: PolarRecordingSecret?): Completable { + val session = try { + sessionPmdClientReady(identifier) + } catch (t: Throwable) { + return Completable.error(t) + } + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Completable.error(PolarServiceNotAvailable()) + val pmdSecret = secret?.let { mapPolarSecretToPmdSecret(it) } + return client.startMeasurement(mapPolarFeatureToPmdClientMeasurementType(feature), mapPolarSettingsToPmdSettings(settings), PmdRecordingType.OFFLINE, pmdSecret) + } + + override fun stopOfflineRecording(identifier: String, feature: PolarDeviceDataType): Completable { + val session = try { + sessionPmdClientReady(identifier) + } catch (t: Throwable) { + return Completable.error(t) + } + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Completable.error(PolarServiceNotAvailable()) + return client.stopMeasurement(mapPolarFeatureToPmdClientMeasurementType(feature)) + } + + override fun getOfflineRecordingStatus(identifier: String): Single> { + val session = try { + sessionPmdClientReady(identifier) + } catch (t: Throwable) { + return Single.error(t) + } + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Single.error(PolarServiceNotAvailable()) + return client.readMeasurementStatus() + .map { pmdMeasurementStatus -> + val offlineRecs: MutableList = mutableListOf() + pmdMeasurementStatus.filter { + it.value == PmdActiveMeasurement.OFFLINE_MEASUREMENT_ACTIVE || + it.value == PmdActiveMeasurement.ONLINE_AND_OFFLINE_ACTIVE + } + .map { + offlineRecs.add(mapPmdClientFeatureToPolarFeature(it.key)) + } + offlineRecs.toList() + } + } + + override fun setOfflineRecordingTrigger(identifier: String, trigger: PolarOfflineRecordingTrigger, secret: PolarRecordingSecret?): Completable { + val session = try { + sessionPmdClientReady(identifier) + } catch (t: Throwable) { + return Completable.error(t) + } + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Completable.error(PolarServiceNotAvailable()) + + val pmdOfflineTrigger = mapPolarOfflineTriggerToPmdOfflineTrigger(trigger) + val pmdSecret = secret?.let { mapPolarSecretToPmdSecret(it) } + return client.setOfflineRecordingTrigger(pmdOfflineTrigger, pmdSecret) + } + + override fun getOfflineRecordingTriggerSetup(identifier: String): Single { + val session = try { + sessionPmdClientReady(identifier) + } catch (t: Throwable) { + return Single.error(t) + } + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Single.error(PolarServiceNotAvailable()) + return client.getOfflineRecordingTriggerStatus() + .map { mapPmdTriggerToPolarTrigger(it) } + } + + override fun startHrStreaming(identifier: String): Flowable { + val session = try { + sessionServiceReady(identifier, HR_SERVICE) + } catch (e: Exception) { + return Flowable.error(e) + } + val bleHrClient = session.fetchClient(HR_SERVICE) as BleHrClient? ?: return Flowable.error(PolarServiceNotAvailable()) + + return bleHrClient.observeHrNotifications(true) + .map { hrNotificationData: HrNotificationData -> + val sample = PolarHrData.PolarHrSample( + hrNotificationData.hrValue, + hrNotificationData.rrs, + hrNotificationData.sensorContact, + hrNotificationData.sensorContactSupported, + hrNotificationData.rrPresent + ) + PolarHrData(listOf(sample)) + } + } + override fun startEcgStreaming(identifier: String, sensorSetting: PolarSensorSetting): Flowable { return startStreaming(identifier, PmdMeasurementType.ECG, sensorSetting, observer = { client: BlePMDClient -> client.monitorEcgNotifications(true) @@ -684,13 +947,17 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB }) } - override fun startOhrStreaming(identifier: String, sensorSetting: PolarSensorSetting): Flowable { + override fun startPpgStreaming(identifier: String, sensorSetting: PolarSensorSetting): Flowable { return startStreaming(identifier, PmdMeasurementType.PPG, sensorSetting, observer = { client: BlePMDClient -> client.monitorPpgNotifications(true) .map { ppgData: PpgData -> mapPMDClientOhrDataToPolarOhr(ppgData) } }) } + override fun startOhrStreaming(identifier: String, sensorSetting: PolarSensorSetting): Flowable { + return startPpgStreaming(identifier, sensorSetting) + } + override fun startOhrPPIStreaming(identifier: String): Flowable { return startStreaming(identifier, PmdMeasurementType.PPI, PolarSensorSetting(emptyMap())) { client: BlePMDClient -> client.monitorPpiNotifications(true) @@ -721,8 +988,8 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB override fun enableSDKMode(identifier: String): Completable { try { val session = sessionPmdClientReady(identifier) - val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? - return if (client != null && client.isServiceDiscovered) { + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Completable.error(PolarServiceNotAvailable()) + return if (client.isServiceDiscovered) { client.startSDKMode() .onErrorResumeNext { error: Throwable -> if (error is BleControlPointCommandError && PmdControlPointResponseCode.ERROR_ALREADY_IN_STATE == error.error) { @@ -739,8 +1006,8 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB override fun disableSDKMode(identifier: String): Completable { return try { val session = sessionPmdClientReady(identifier) - val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? - if (client != null && client.isServiceDiscovered) { + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Completable.error(PolarServiceNotAvailable()) + if (client.isServiceDiscovered) { client.stopSDKMode() .onErrorResumeNext { error: Throwable -> if (error is BleControlPointCommandError && PmdControlPointResponseCode.ERROR_ALREADY_IN_STATE == error.error) { @@ -754,6 +1021,83 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB } } + override fun isSDKModeEnabled(identifier: String): Single { + val session = try { + sessionPmdClientReady(identifier) + } catch (t: Throwable) { + return Single.error(t) + } + val client = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Single.error(PolarServiceNotAvailable()) + return client.isSdkModeEnabled() + .map { + it != PmdSdkMode.DISABLED + } + } + + override fun getAvailableOfflineRecordingDataTypes(identifier: String): Single> { + val session = try { + sessionPmdClientReady(identifier) + } catch (e: Exception) { + return Single.error(e) + } + val blePMDClient = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Single.error(PolarServiceNotAvailable()) + + return blePMDClient.readFeature(true) + .observeOn(AndroidSchedulers.mainThread()) + .map { pmdFeature: Set -> + val deviceData: MutableSet = mutableSetOf() + if (pmdFeature.contains(PmdMeasurementType.ECG)) deviceData.add(PolarDeviceDataType.ECG) + if (pmdFeature.contains(PmdMeasurementType.ACC)) deviceData.add(PolarDeviceDataType.ACC) + if (pmdFeature.contains(PmdMeasurementType.PPG)) deviceData.add(PolarDeviceDataType.PPG) + if (pmdFeature.contains(PmdMeasurementType.PPI)) deviceData.add(PolarDeviceDataType.PPI) + if (pmdFeature.contains(PmdMeasurementType.GYRO)) deviceData.add(PolarDeviceDataType.GYRO) + if (pmdFeature.contains(PmdMeasurementType.MAGNETOMETER)) deviceData.add(PolarDeviceDataType.MAGNETOMETER) + if (pmdFeature.contains(PmdMeasurementType.OFFLINE_HR)) deviceData.add(PolarDeviceDataType.HR) + deviceData + } + } + + override fun getAvailableOnlineStreamDataTypes(identifier: String): Single> { + val session = try { + sessionPmdClientReady(identifier) + } catch (e: Exception) { + return Single.error(e) + } + + val blePMDClient = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Single.error(PolarServiceNotAvailable()) + val bleHrClient = session.fetchClient(HR_SERVICE) as BleHrClient? + return blePMDClient.clientReady(true) + .andThen( + blePMDClient.readFeature(true) + .observeOn(AndroidSchedulers.mainThread()) + .map { pmdFeature: Set -> + val deviceData: MutableSet = mutableSetOf() + if (bleHrClient != null) { + deviceData.add(PolarDeviceDataType.HR) + } + if (pmdFeature.contains(PmdMeasurementType.ECG)) { + deviceData.add(PolarDeviceDataType.ECG) + } + if (pmdFeature.contains(PmdMeasurementType.ACC)) { + deviceData.add(PolarDeviceDataType.ACC) + } + if (pmdFeature.contains(PmdMeasurementType.PPG)) { + deviceData.add(PolarDeviceDataType.PPG) + } + if (pmdFeature.contains(PmdMeasurementType.PPI)) { + deviceData.add(PolarDeviceDataType.PPI) + } + if (pmdFeature.contains(PmdMeasurementType.GYRO)) { + deviceData.add(PolarDeviceDataType.GYRO) + } + if (pmdFeature.contains(PmdMeasurementType.MAGNETOMETER)) { + deviceData.add(PolarDeviceDataType.MAGNETOMETER) + } + + deviceData + }) + } + @Throws(PolarInvalidArgument::class) fun fetchSession(identifier: String): BleDeviceSession? { if (identifier.matches(Regex("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"))) { @@ -808,6 +1152,17 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB throw PolarDeviceNotFound() } + @Throws(Throwable::class) + fun sessionHrClientReady(identifier: String): BleDeviceSession { + val session = sessionServiceReady(identifier, HR_SERVICE) + val client = session.fetchClient(HR_SERVICE) as BleHrClient? ?: throw PolarServiceNotAvailable() + val hrMeasurementChr = client.getNotificationAtomicInteger(HR_MEASUREMENT) + if (hrMeasurementChr != null && hrMeasurementChr.get() == BleGattBase.ATT_SUCCESS) { + return session + } + throw PolarNotificationNotEnabled() + } + @Throws(Throwable::class) fun sessionPmdClientReady(identifier: String): BleDeviceSession { val session = sessionServiceReady(identifier, BlePMDClient.PMD_SERVICE) @@ -835,12 +1190,13 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB private fun stopPmdStreaming(session: BleDeviceSession, client: BlePMDClient, type: PmdMeasurementType) { if (session.sessionState == DeviceSessionState.SESSION_OPEN) { // stop streaming - val disposable = client.stopMeasurement(type).subscribe( - {}, - { throwable: Throwable -> - logError("failed to stop pmd stream: " + throwable.localizedMessage) - } - ) + val disposable = client.stopMeasurement(type) + .subscribe( + {}, + { throwable: Throwable -> + logError("failed to stop pmd stream: " + throwable.localizedMessage) + } + ) stopPmdStreamingDisposable[session.address] = disposable } } @@ -892,25 +1248,41 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB private fun setupDevice(session: BleDeviceSession) { val deviceId = session.polarDeviceId.ifEmpty { session.address } + val disposableAvailableFeatures = session.monitorServicesDiscovered(true) + .flatMapCompletable { discoveredServices -> + val availableFeaturesList: MutableList = mutableListOf() + for (feature in PolarBleSdkFeature.values()) { + if (features.contains(feature)) { + availableFeaturesList.add(makeFeatureCallbackIfNeeded(session, discoveredServices, feature)) + } + } + Completable.concat(availableFeaturesList) + } + .subscribe( + { log("completed available features check ") }, + { throwable: Throwable -> logError("Error while available features are checked: $throwable") }, + ) + + deviceAvailableFeaturesDisposable[session.address] = disposableAvailableFeatures val disposable = session.monitorServicesDiscovered(true) .toFlowable() .flatMapIterable { uuids: List -> uuids } .flatMap { uuid: UUID -> if (session.fetchClient(uuid) != null) { when (uuid) { - BleHrClient.HR_SERVICE -> { + HR_SERVICE -> { Completable.fromAction { callback?.hrFeatureReady(deviceId) } .subscribeOn(AndroidSchedulers.mainThread()) .subscribe() - val bleHrClient = session.fetchClient(BleHrClient.HR_SERVICE) as BleHrClient? + val bleHrClient = session.fetchClient(HR_SERVICE) as BleHrClient? bleHrClient?.observeHrNotifications(true) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe( { hrNotificationData: HrNotificationData -> callback?.hrNotificationReceived( deviceId, - PolarHrData( + PolarHrData.PolarHrSample( hrNotificationData.hrValue, hrNotificationData.rrs, hrNotificationData.sensorContact, @@ -942,26 +1314,26 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB blePMDClient.readFeature(true) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess { pmdFeature: Set -> - val deviceStreamingFeatures: MutableSet = HashSet() + val deviceData: MutableSet = HashSet() if (pmdFeature.contains(PmdMeasurementType.ECG)) { - deviceStreamingFeatures.add(DeviceStreamingFeature.ECG) + deviceData.add(PolarDeviceDataType.ECG) } if (pmdFeature.contains(PmdMeasurementType.ACC)) { - deviceStreamingFeatures.add(DeviceStreamingFeature.ACC) + deviceData.add(PolarDeviceDataType.ACC) } if (pmdFeature.contains(PmdMeasurementType.PPG)) { - deviceStreamingFeatures.add(DeviceStreamingFeature.PPG) + deviceData.add(PolarDeviceDataType.PPG) } if (pmdFeature.contains(PmdMeasurementType.PPI)) { - deviceStreamingFeatures.add(DeviceStreamingFeature.PPI) + deviceData.add(PolarDeviceDataType.PPI) } if (pmdFeature.contains(PmdMeasurementType.GYRO)) { - deviceStreamingFeatures.add(DeviceStreamingFeature.GYRO) + deviceData.add(PolarDeviceDataType.GYRO) } if (pmdFeature.contains(PmdMeasurementType.MAGNETOMETER)) { - deviceStreamingFeatures.add(DeviceStreamingFeature.MAGNETOMETER) + deviceData.add(PolarDeviceDataType.MAGNETOMETER) } - callback?.streamingFeaturesReady(deviceId, deviceStreamingFeatures) + callback?.streamingFeaturesReady(deviceId, deviceData) if (pmdFeature.contains(PmdMeasurementType.SDK_MODE)) { callback?.sdkModeFeatureAvailable(deviceId) } @@ -1001,12 +1373,156 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB deviceDataMonitorDisposable[session.address] = disposable } + private fun makeFeatureCallbackIfNeeded(session: BleDeviceSession, discoveredServices: List, featurePolarOfflineRecording: PolarBleSdkFeature): Completable { + val isFeatureAvailable = when (featurePolarOfflineRecording) { + PolarBleSdkFeature.FEATURE_HR -> isHeartRateFeatureAvailable(discoveredServices, session) + PolarBleSdkFeature.FEATURE_DEVICE_INFO -> isDeviceInfoFeatureAvailable(discoveredServices, session) + PolarBleSdkFeature.FEATURE_BATTERY_INFO -> isBatteryInfoFeatureAvailable(discoveredServices, session) + PolarBleSdkFeature.FEATURE_POLAR_ONLINE_STREAMING -> isOnlineStreamingAvailable(discoveredServices, session) + PolarBleSdkFeature.FEATURE_POLAR_OFFLINE_RECORDING -> isOfflineRecordingAvailable(discoveredServices, session) + PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP -> isPolarDeviceTimeFeatureAvailable(discoveredServices, session) + PolarBleSdkFeature.FEATURE_POLAR_SDK_MODE -> isSdkModeFeatureAvailable(discoveredServices, session) + PolarBleSdkFeature.FEATURE_POLAR_H10_EXERCISE_RECORDING -> isH10ExerciseFeatureAvailable(discoveredServices, session) + } + + return isFeatureAvailable.flatMapCompletable { + if (it) { + Completable.fromAction { + callback?.bleSdkFeatureReady(session.polarDeviceId, featurePolarOfflineRecording) + } + } else { + Completable.complete() + } + } + } + + private fun isPolarDeviceTimeFeatureAvailable(discoveredServices: List, session: BleDeviceSession): Single { + return if (discoveredServices.contains(BlePsFtpUtils.RFC77_PFTP_SERVICE)) { + val blePsftpClient = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Single.just(false) + blePsftpClient.clientReady(true) + .toSingle { + return@toSingle true + } + + } else { + Single.just(false) + } + } + + private fun isBatteryInfoFeatureAvailable(discoveredServices: List, session: BleDeviceSession): Single { + return if (discoveredServices.contains(BleBattClient.BATTERY_SERVICE)) { + val bleBattClient = session.fetchClient(BleBattClient.BATTERY_SERVICE) as BleBattClient? ?: return Single.just(false) + bleBattClient.clientReady(true) + .toSingle { + return@toSingle true + } + } else { + Single.just(false) + } + } + + private fun isDeviceInfoFeatureAvailable(discoveredServices: List, session: BleDeviceSession): Single { + return if (discoveredServices.contains(BleDisClient.DIS_SERVICE)) { + val bleDisClient = session.fetchClient(BleDisClient.DIS_SERVICE) as BleDisClient? ?: return Single.just(false) + bleDisClient.clientReady(true) + .toSingle { + return@toSingle true + } + } else { + Single.just(false) + } + } + + private fun isHeartRateFeatureAvailable(discoveredServices: List, session: BleDeviceSession): Single { + return if (discoveredServices.contains(HR_SERVICE)) { + val bleHrClient = session.fetchClient(HR_SERVICE) as BleHrClient? ?: return Single.just(false) + bleHrClient.clientReady(true) + .toSingle { + return@toSingle true + } + + } else { + Single.just(false) + } + } + + private fun isH10ExerciseFeatureAvailable(discoveredServices: List, session: BleDeviceSession): Single { + return if (discoveredServices.contains(BlePsFtpUtils.RFC77_PFTP_SERVICE) && isRecordingSupported(session.polarDeviceType)) { + val blePsftpClient = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Single.just(false) + blePsftpClient.clientReady(true) + .toSingle { + return@toSingle true + } + + } else { + Single.just(false) + } + } + + private fun isSdkModeFeatureAvailable(discoveredServices: List, session: BleDeviceSession): Single { + return if (discoveredServices.contains(BlePMDClient.PMD_SERVICE)) { + val blePMDClient = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Single.just(false) + blePMDClient.clientReady(true) + .andThen( + blePMDClient.readFeature(true) + .map { pmdFeatures: Set -> + pmdFeatures.contains(PmdMeasurementType.SDK_MODE) + } + ) + } else { + Single.just(false) + } + } + + private fun isOnlineStreamingAvailable(discoveredServices: List, session: BleDeviceSession): Single { + val isClientReady = if (discoveredServices.contains(HR_SERVICE)) { + val bleHrClient = session.fetchClient(HR_SERVICE) as BleHrClient? ?: return Single.just(false) + bleHrClient.clientReady(true) + } else { + Completable.complete() + } + + return if (discoveredServices.contains(BlePMDClient.PMD_SERVICE)) { + val blePMDClient = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Single.just(false) + isClientReady.andThen( + blePMDClient.clientReady(true) + ).toSingle { + return@toSingle true + } + } else { + Single.just(false) + } + } + + private fun isOfflineRecordingAvailable(discoveredServices: List, session: BleDeviceSession): Single { + return if (discoveredServices.contains(BlePMDClient.PMD_SERVICE) && discoveredServices.contains(BlePsFtpUtils.RFC77_PFTP_SERVICE)) { + val blePMDClient = session.fetchClient(BlePMDClient.PMD_SERVICE) as BlePMDClient? ?: return Single.just(false) + val blePsftpClient = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Single.just(false) + Completable.concatArray( + blePMDClient.clientReady(true), + blePsftpClient.clientReady(true) + ).andThen( + blePMDClient.readFeature(true) + .map { pmdFeatures: Set -> + pmdFeatures.contains(PmdMeasurementType.OFFLINE_RECORDING) + } + ) + } else { + Single.just(false) + } + } + private fun tearDownDevice(session: BleDeviceSession) { val address = session.address if (deviceDataMonitorDisposable.containsKey(address)) { deviceDataMonitorDisposable[address]?.dispose() deviceDataMonitorDisposable.remove(address) } + + if (deviceAvailableFeaturesDisposable.containsKey(address)) { + deviceAvailableFeaturesDisposable[address]?.dispose() + deviceAvailableFeaturesDisposable.remove(address) + } } private fun handleError(throwable: Throwable): Exception { @@ -1068,11 +1584,10 @@ class BDBleApiImpl private constructor(context: Context, features: Int) : PolarB companion object { private const val TAG = "BDBleApiImpl" - private var instance: BDBleApiImpl? = null @Throws(PolarBleSdkInstanceException::class, BleNotAvailableInDevice::class) - fun getInstance(context: Context, features: Int): BDBleApiImpl { + fun getInstance(context: Context, features: Set): BDBleApiImpl { return instance?.let { if (it.features == features) { it diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/utils/PolarDataUtils.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/utils/PolarDataUtils.kt similarity index 55% rename from sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/utils/PolarDataUtils.kt rename to sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/utils/PolarDataUtils.kt index e456754a..efcb1a51 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/utils/PolarDataUtils.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/utils/PolarDataUtils.kt @@ -1,8 +1,7 @@ -package com.polar.sdk.api.model.utils +package com.polar.sdk.impl.utils import com.polar.androidcommunications.api.ble.BleLogger -import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdMeasurementType -import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdSetting +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.* import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.model.* import com.polar.sdk.api.PolarBleApi import com.polar.sdk.api.errors.PolarBleSdkInternalException @@ -42,6 +41,22 @@ internal object PolarDataUtils { return PolarOhrPPIData(0L, samples) } + fun mapPMDClientOfflineHrDataToPolarHrData(offlineHrData: OfflineHrData): PolarHrData { + val samples: MutableList = mutableListOf() + for (sample in offlineHrData.hrSamples) { + samples.add( + PolarHrData.PolarHrSample( + hr = sample.hr, + rrs = emptyList(), + contactStatus = false, + contactStatusSupported = false, + rrAvailable = false + ) + ) + } + return PolarHrData(samples) + } + fun mapPmdClientEcgDataToPolarEcg(ecgData: EcgData): PolarEcgData { val ecgDataSamples = mutableListOf() for (sample in ecgData.ecgSamples) { @@ -81,34 +96,47 @@ internal object PolarDataUtils { return PolarMagnetometerData(samples, magData.timeStamp.toLong()) } - fun mapPolarFeatureToPmdClientMeasurementType(polarFeature: PolarBleApi.DeviceStreamingFeature): PmdMeasurementType { + fun mapPolarFeatureToPmdClientMeasurementType(polarFeature: PolarBleApi.PolarDeviceDataType): PmdMeasurementType { return when (polarFeature) { - PolarBleApi.DeviceStreamingFeature.ECG -> PmdMeasurementType.ECG - PolarBleApi.DeviceStreamingFeature.ACC -> PmdMeasurementType.ACC - PolarBleApi.DeviceStreamingFeature.PPG -> PmdMeasurementType.PPG - PolarBleApi.DeviceStreamingFeature.PPI -> PmdMeasurementType.PPI - PolarBleApi.DeviceStreamingFeature.GYRO -> PmdMeasurementType.GYRO - PolarBleApi.DeviceStreamingFeature.MAGNETOMETER -> PmdMeasurementType.MAGNETOMETER + PolarBleApi.PolarDeviceDataType.ECG -> PmdMeasurementType.ECG + PolarBleApi.PolarDeviceDataType.ACC -> PmdMeasurementType.ACC + PolarBleApi.PolarDeviceDataType.PPG -> PmdMeasurementType.PPG + PolarBleApi.PolarDeviceDataType.PPI -> PmdMeasurementType.PPI + PolarBleApi.PolarDeviceDataType.GYRO -> PmdMeasurementType.GYRO + PolarBleApi.PolarDeviceDataType.MAGNETOMETER -> PmdMeasurementType.MAGNETOMETER + PolarBleApi.PolarDeviceDataType.HR -> PmdMeasurementType.OFFLINE_HR + else -> { + throw PolarBleSdkInternalException("Error when map $polarFeature to PMD measurement type") + } } } - fun mapPmdClientFeaturesToPolarFeatures(pmdMeasurementType: Set): List { - val polarFeatures: MutableList = mutableListOf() - for (feature in pmdMeasurementType) { - polarFeatures.add(mapPmdClientFeatureToPolarFeature(feature)) + fun mapPmdClientFeatureToPolarFeature(pmdMeasurementType: PmdMeasurementType): PolarBleApi.PolarDeviceDataType { + return when (pmdMeasurementType) { + PmdMeasurementType.ECG -> PolarBleApi.PolarDeviceDataType.ECG + PmdMeasurementType.PPG -> PolarBleApi.PolarDeviceDataType.PPG + PmdMeasurementType.ACC -> PolarBleApi.PolarDeviceDataType.ACC + PmdMeasurementType.PPI -> PolarBleApi.PolarDeviceDataType.PPI + PmdMeasurementType.GYRO -> PolarBleApi.PolarDeviceDataType.GYRO + PmdMeasurementType.MAGNETOMETER -> PolarBleApi.PolarDeviceDataType.MAGNETOMETER + PmdMeasurementType.OFFLINE_HR -> PolarBleApi.PolarDeviceDataType.HR + else -> throw PolarBleSdkInternalException("Error when map measurement type $pmdMeasurementType to Polar feature") } - return polarFeatures.toList() } - fun mapPmdClientFeatureToPolarFeature(pmdMeasurementType: PmdMeasurementType): PolarBleApi.DeviceStreamingFeature { - return when (pmdMeasurementType) { - PmdMeasurementType.ECG -> PolarBleApi.DeviceStreamingFeature.ECG - PmdMeasurementType.PPG -> PolarBleApi.DeviceStreamingFeature.PPG - PmdMeasurementType.ACC -> PolarBleApi.DeviceStreamingFeature.ACC - PmdMeasurementType.PPI -> PolarBleApi.DeviceStreamingFeature.PPI - PmdMeasurementType.GYRO -> PolarBleApi.DeviceStreamingFeature.GYRO - PmdMeasurementType.MAGNETOMETER -> PolarBleApi.DeviceStreamingFeature.MAGNETOMETER - else -> throw PolarBleSdkInternalException("Error when map measurement type $pmdMeasurementType to Polar feature") + private fun mapPolarOfflineModeTriggerToPmdOfflineTriggerMode(offlineTrigger: PolarOfflineRecordingTriggerMode): PmdOfflineRecTriggerMode { + return when (offlineTrigger) { + PolarOfflineRecordingTriggerMode.TRIGGER_SYSTEM_START -> PmdOfflineRecTriggerMode.TRIGGER_SYSTEM_START + PolarOfflineRecordingTriggerMode.TRIGGER_EXERCISE_START -> PmdOfflineRecTriggerMode.TRIGGER_EXERCISE_START + PolarOfflineRecordingTriggerMode.TRIGGER_DISABLED -> PmdOfflineRecTriggerMode.TRIGGER_DISABLE + } + } + + private fun mapPmdOfflineTriggerModeToPolarOfflineTriggerMode(pmdTriggerType: PmdOfflineRecTriggerMode): PolarOfflineRecordingTriggerMode { + return when (pmdTriggerType) { + PmdOfflineRecTriggerMode.TRIGGER_DISABLE -> PolarOfflineRecordingTriggerMode.TRIGGER_DISABLED + PmdOfflineRecTriggerMode.TRIGGER_SYSTEM_START -> PolarOfflineRecordingTriggerMode.TRIGGER_SYSTEM_START + PmdOfflineRecTriggerMode.TRIGGER_EXERCISE_START -> PolarOfflineRecordingTriggerMode.TRIGGER_EXERCISE_START } } @@ -117,10 +145,12 @@ internal object PolarDataUtils { * * @return PmdSetting */ - fun mapPolarSettingsToPmdSettings(polarSensorSetting: PolarSensorSetting): PmdSetting { + fun mapPolarSettingsToPmdSettings(polarSensorSetting: PolarSensorSetting?): PmdSetting { val selected: MutableMap = mutableMapOf() - for ((key, value) in polarSensorSetting.settings) { - selected[PmdSetting.PmdSettingType.values()[key.numVal]] = Collections.max(value) + if(polarSensorSetting != null) { + for ((key, value) in polarSensorSetting.settings) { + selected[PmdSetting.PmdSettingType.values()[key.numVal]] = Collections.max(value) + } } return PmdSetting(selected) } @@ -163,4 +193,46 @@ internal object PolarDataUtils { PolarSensorSetting(settings.toList()) } } -} \ No newline at end of file + + fun mapPmdTriggerToPolarTrigger(pmdTriggerStatus: PmdOfflineTrigger): PolarOfflineRecordingTrigger { + val triggerMode = mapPmdOfflineTriggerModeToPolarOfflineTriggerMode(pmdTriggerStatus.triggerMode) + val polarTriggerSettings: MutableMap = mutableMapOf() + + for (setting in pmdTriggerStatus.triggers) { + val polarFeature = mapPmdClientFeatureToPolarFeature(setting.key) + val triggerStatus = setting.value.first + + if (triggerStatus == PmdOfflineRecTriggerStatus.TRIGGER_ENABLED) { + // Map only the enabled + val polarSettings = setting.value.second?.let { + mapPmdSettingsToPolarSettings(it, false) + } + polarTriggerSettings[polarFeature] = polarSettings + } + } + return PolarOfflineRecordingTrigger( + triggerMode = triggerMode, + triggerFeatures = polarTriggerSettings + ) + } + + fun mapPolarOfflineTriggerToPmdOfflineTrigger(polarTrigger: PolarOfflineRecordingTrigger): PmdOfflineTrigger { + val pmdTriggerMode = mapPolarOfflineModeTriggerToPmdOfflineTriggerMode(polarTrigger.triggerMode) + val pmdTriggers: MutableMap> = mutableMapOf() + + for (trigger in polarTrigger.triggerFeatures) { + val pmdMeasurementType = mapPolarFeatureToPmdClientMeasurementType(trigger.key) + val pmdSettings = trigger.value?.let { mapPolarSettingsToPmdSettings(it) } + pmdTriggers[pmdMeasurementType] = Pair(PmdOfflineRecTriggerStatus.TRIGGER_ENABLED, pmdSettings) + } + + return PmdOfflineTrigger(triggerMode = pmdTriggerMode, triggers = pmdTriggers) + } + + fun mapPolarSecretToPmdSecret(polarSecret: PolarRecordingSecret): PmdSecret { + return PmdSecret( + strategy = PmdSecret.SecurityStrategy.AES128, + key = polarSecret.secret + ) + } +} diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/utils/PolarTimeUtils.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/utils/PolarTimeUtils.kt similarity index 98% rename from sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/utils/PolarTimeUtils.kt rename to sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/utils/PolarTimeUtils.kt index 4d59a9f9..3ac66eb8 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/utils/PolarTimeUtils.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/utils/PolarTimeUtils.kt @@ -1,4 +1,4 @@ -package com.polar.sdk.api.model.utils +package com.polar.sdk.impl.utils import fi.polar.remote.representation.protobuf.Types import org.joda.time.DateTime diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/BlePmdClientTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/BlePmdClientTest.kt index 66ea2f68..0d662dd4 100644 --- a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/BlePmdClientTest.kt +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/BlePmdClientTest.kt @@ -241,21 +241,21 @@ internal class BlePmdClientTest { @Test fun `process ppi data`() { // Arrange - // HEX: 03 00 00 00 00 00 00 00 00 03 00 + // HEX: 03 00 00 00 00 00 00 00 00 00 00 // index type data // 0: Measurement type 03 (ppi data) // 1..8: 64-bit Timestamp 00 00 00 00 00 00 00 00 (0x0000000000000000 = 0) - // 9: Frame type 03 (raw, frame type 3) + // 9: Frame type 00 (raw, frame type 0) // 10: Data 00 val expectedTimeStamp = 0uL val expectedIsCompressed = false - val expectedFrameType = PmdDataFrame.PmdDataFrameType.TYPE_3 + val expectedFrameType = PmdDataFrame.PmdDataFrameType.TYPE_0 val expectedFactor = 1.0f val ppiDataHeaderFromService = byteArrayOf( 0x03.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), - 0x03.toByte(), + 0x00.toByte(), ) val ppiDataPartFromService = byteArrayOf( 0x00.toByte() diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdActiveMeasurementTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdActiveMeasurementTest.kt new file mode 100644 index 00000000..4fb448f4 --- /dev/null +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdActiveMeasurementTest.kt @@ -0,0 +1,64 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd + +import com.polar.androidcommunications.testrules.BleLoggerTestRule +import org.junit.Assert +import org.junit.Rule +import org.junit.Test + +internal class PmdActiveMeasurementTest { + @Rule + @JvmField + val bleLoggerTestRule = BleLoggerTestRule() + + @Test + fun `test offline recording is active`() { + // Arrange + val offlineRecActiveTest1: Byte = 0x80.toByte() + val offlineRecActiveTest2: Byte = 0xBF.toByte() + // Act + val result1 = PmdActiveMeasurement.fromStatusResponse(offlineRecActiveTest1) + val result2 = PmdActiveMeasurement.fromStatusResponse(offlineRecActiveTest2) + // Assert + Assert.assertEquals(result1, PmdActiveMeasurement.OFFLINE_MEASUREMENT_ACTIVE) + Assert.assertEquals(result2, PmdActiveMeasurement.OFFLINE_MEASUREMENT_ACTIVE) + } + + @Test + fun `test online recording is active`() { + // Arrange + val onlineRecActiveTest1: Byte = 0x40.toByte() + val onlineRecActiveTest2: Byte = 0x7F.toByte() + // Act + val result1 = PmdActiveMeasurement.fromStatusResponse(onlineRecActiveTest1) + val result2 = PmdActiveMeasurement.fromStatusResponse(onlineRecActiveTest2) + // Assert + Assert.assertEquals(result1, PmdActiveMeasurement.ONLINE_MEASUREMENT_ACTIVE) + Assert.assertEquals(result2, PmdActiveMeasurement.ONLINE_MEASUREMENT_ACTIVE) + } + + @Test + fun `test online and offline recording is active`() { + // Arrange + val onlineAndOfflineRecActiveTest1: Byte = 0xC0.toByte() + val onlineAndOfflineRecActiveTest2: Byte = 0xFF.toByte() + // Act + val result1 = PmdActiveMeasurement.fromStatusResponse(onlineAndOfflineRecActiveTest1) + val result2 = PmdActiveMeasurement.fromStatusResponse(onlineAndOfflineRecActiveTest2) + // Assert + Assert.assertEquals(result1, PmdActiveMeasurement.ONLINE_AND_OFFLINE_ACTIVE) + Assert.assertEquals(result2, PmdActiveMeasurement.ONLINE_AND_OFFLINE_ACTIVE) + } + + @Test + fun `test no active recording`() { + // Arrange + val noRecordingsActiveTest1: Byte = 0x00.toByte() + val noRecordingsActiveTest2: Byte = 0x3F.toByte() + // Act + val result1 = PmdActiveMeasurement.fromStatusResponse(noRecordingsActiveTest1) + val result2 = PmdActiveMeasurement.fromStatusResponse(noRecordingsActiveTest2) + // Assert + Assert.assertEquals(result1, PmdActiveMeasurement.NO_ACTIVE_MEASUREMENT) + Assert.assertEquals(result2, PmdActiveMeasurement.NO_ACTIVE_MEASUREMENT) + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdOfflineTriggerTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdOfflineTriggerTest.kt new file mode 100644 index 00000000..1e70e7ce --- /dev/null +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdOfflineTriggerTest.kt @@ -0,0 +1,130 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd + +import com.polar.androidcommunications.api.ble.exceptions.BleControlPointResponseError +import com.polar.androidcommunications.testrules.BleLoggerTestRule +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test + +internal class PmdOfflineTriggerTest { + @Rule + @JvmField + val bleLoggerTestRule = BleLoggerTestRule() + + @Test + fun `test triggers are disabled`() { + //Arrange + val pmdGetTriggerStatusResponse = byteArrayOf( + // index 0 : trigger mode + 0x00.toByte(), + // index 1.. : trigger status, measurement type, settings length (optional), settings (optional) + 0x00.toByte(), 0x02.toByte(), + 0x00.toByte(), 0x05.toByte(), + 0x00.toByte(), 0x06.toByte(), + 0x00.toByte(), 0x01.toByte(), + 0x00.toByte(), 0x03.toByte() + ) + + //Act + val pmdOfflineTrigger = PmdOfflineTrigger.parseFromResponse(pmdGetTriggerStatusResponse) + + //Assert + assertEquals(PmdOfflineRecTriggerMode.TRIGGER_DISABLE, pmdOfflineTrigger.triggerMode) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.PPI)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.ACC)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.PPG)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.GYRO)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.MAGNETOMETER)) + assertTrue(pmdOfflineTrigger.triggers.keys.size == 5) + pmdOfflineTrigger.triggers.values.map { + assertTrue(it.first == PmdOfflineRecTriggerStatus.TRIGGER_DISABLED) + assertTrue(it.second == null) + } + } + + @Test + fun `test trigger PPI enabled at system start`() { + //Arrange + val pmdGetTriggerStatusResponse = byteArrayOf( + // index 0 : trigger mode + 0x01.toByte(), + // index 1.. : trigger status, measurement type, settings length (optional), settings (optional) + 0x00.toByte(), 0x02.toByte(), + 0x00.toByte(), 0x05.toByte(), + 0x00.toByte(), 0x06.toByte(), + 0x00.toByte(), 0x01.toByte(), + 0x01.toByte(), 0x03.toByte(), 0x00.toByte() + ) + + //Act + val pmdOfflineTrigger = PmdOfflineTrigger.parseFromResponse(pmdGetTriggerStatusResponse) + + //Assert + assertEquals(PmdOfflineRecTriggerMode.TRIGGER_SYSTEM_START, pmdOfflineTrigger.triggerMode) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.PPI)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.ACC)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.PPG)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.GYRO)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.MAGNETOMETER)) + assertTrue(pmdOfflineTrigger.triggers.keys.size == 5) + pmdOfflineTrigger.triggers.map { + if (it.key == PmdMeasurementType.PPI) { + assertTrue(it.value.first == PmdOfflineRecTriggerStatus.TRIGGER_ENABLED) + assertTrue(it.value.second == null) + } else { + assertTrue(it.value.first == PmdOfflineRecTriggerStatus.TRIGGER_DISABLED) + assertTrue(it.value.second == null) + } + } + } + + @Test + fun `test trigger PPI, ACC, GYRO, MAG, PPG enabled at exercise start`() { + //Arrange + val pmdGetTriggerStatusResponse = byteArrayOf( + // index 0 : trigger mode + 0x02.toByte(), + // index 1.. : trigger status, measurement type, settings length (optional), settings (optional) + 0x01.toByte(), 0x02.toByte(), 0x0f.toByte(), 0x00.toByte(), 0x01.toByte(), 0x34.toByte(), 0x00.toByte(), 0x01.toByte(), 0x01.toByte(), 0x10.toByte(), 0x00.toByte(), 0x02.toByte(), 0x01.toByte(), 0x08.toByte(), 0x00.toByte(), 0x04.toByte(), 0x01.toByte(), 0x03.toByte(), + 0x01.toByte(), 0x05.toByte(), 0x0f.toByte(), 0x00.toByte(), 0x01.toByte(), 0x34.toByte(), 0x00.toByte(), 0x01.toByte(), 0x01.toByte(), 0x10.toByte(), 0x00.toByte(), 0x02.toByte(), 0x01.toByte(), 0xd0.toByte(), 0x07.toByte(), 0x04.toByte(), 0x01.toByte(), 0x03.toByte(), + 0x01.toByte(), 0x06.toByte(), 0x0f.toByte(), 0x00.toByte(), 0x01.toByte(), 0x64.toByte(), 0x00.toByte(), 0x01.toByte(), 0x01.toByte(), 0x10.toByte(), 0x00.toByte(), 0x02.toByte(), 0x01.toByte(), 0x32.toByte(), 0x00.toByte(), 0x04.toByte(), 0x01.toByte(), 0x03.toByte(), + 0x01.toByte(), 0x01.toByte(), 0x0f.toByte(), 0x00.toByte(), 0x01.toByte(), 0x87.toByte(), 0x00.toByte(), 0x01.toByte(), 0x01.toByte(), 0x16.toByte(), 0x00.toByte(), 0x02.toByte(), 0x01.toByte(), 0x00.toByte(), 0x00.toByte(), 0x04.toByte(), 0x01.toByte(), 0x04.toByte(), + 0x01.toByte(), 0x03.toByte(), 0x00.toByte() + ) + //Act + val pmdOfflineTrigger = PmdOfflineTrigger.parseFromResponse(pmdGetTriggerStatusResponse) + + //Assert + assertEquals(PmdOfflineRecTriggerMode.TRIGGER_EXERCISE_START, pmdOfflineTrigger.triggerMode) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.PPI)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.ACC)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.PPG)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.GYRO)) + assertTrue(pmdOfflineTrigger.triggers.keys.contains(PmdMeasurementType.MAGNETOMETER)) + assertTrue(pmdOfflineTrigger.triggers.keys.size == 5) + pmdOfflineTrigger.triggers.map { + if (it.key == PmdMeasurementType.PPI) { + assertTrue(it.value.first == PmdOfflineRecTriggerStatus.TRIGGER_ENABLED) + assertTrue(it.value.second == null) + } else { + assertTrue(it.value.first == PmdOfflineRecTriggerStatus.TRIGGER_ENABLED) + assertTrue(it.value.second != null) + } + } + } + + @Test + fun `test trigger PMD response has wrong status`() { + //Arrange + val pmdGetTriggerStatusResponse = byteArrayOf( + // index 0 : trigger mode + 0x01.toByte(), + // index 1.. : trigger status, measurement type, settings length (optional), settings (optional) + 0x03.toByte(), 0x01.toByte() + ) + //Act & Assert + assertThrows(BleControlPointResponseError::class.java) { + PmdOfflineTrigger.parseFromResponse(pmdGetTriggerStatusResponse) + } + } +} diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSecretTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSecretTest.kt new file mode 100644 index 00000000..1759d7e2 --- /dev/null +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/PmdSecretTest.kt @@ -0,0 +1,172 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd + +import com.polar.androidcommunications.testrules.BleLoggerTestRule +import org.junit.Assert +import org.junit.Rule +import org.junit.Test + +internal class PmdSecretTest { + @Rule + @JvmField + val bleLoggerTestRule = BleLoggerTestRule() + + private val key16bytes: ByteArray = byteArrayOf(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0xFF.toByte()) + + @Test + fun `test strategy NONE serialization`() { + //Arrange + val pmdSecret = PmdSecret(strategy = PmdSecret.SecurityStrategy.NONE, key = byteArrayOf()) + + //Act + val serialized = pmdSecret.serializeToPmdSettings() + + //Assert + Assert.assertEquals(3, serialized.size) + Assert.assertEquals(PmdSetting.PmdSettingType.SECURITY.numVal.toByte(), serialized[0]) + Assert.assertEquals(1.toByte(), serialized[1]) + Assert.assertEquals(PmdSecret.SecurityStrategy.NONE.numVal.toByte(), serialized[2]) + } + + @Test + fun `test strategy XOR serialization`() { + //Arrange + val expectedKey = byteArrayOf(0xFF.toByte()) + val pmdSecret = PmdSecret(strategy = PmdSecret.SecurityStrategy.XOR, key = expectedKey) + + //Act + val serialized = pmdSecret.serializeToPmdSettings() + + //Assert + Assert.assertEquals(1 + 1 + 1 + 1, serialized.size) + Assert.assertEquals(PmdSetting.PmdSettingType.SECURITY.numVal.toByte(), serialized[0]) + Assert.assertEquals(1.toByte(), serialized[1]) + Assert.assertEquals(PmdSecret.SecurityStrategy.XOR.numVal.toByte(), serialized[2]) + Assert.assertArrayEquals(expectedKey, serialized.drop(3).toByteArray()) + } + + @Test + fun `test strategy AES128 serialization`() { + //Arrange + val expectedKey = key16bytes.reversed().toByteArray() + val pmdSecret = PmdSecret(strategy = PmdSecret.SecurityStrategy.AES128, key = expectedKey) + + //Act + val serialized = pmdSecret.serializeToPmdSettings() + + //Assert + Assert.assertEquals(1 + 1 + 1 + 16, serialized.size) + Assert.assertEquals(PmdSetting.PmdSettingType.SECURITY.numVal.toByte(), serialized[0]) + Assert.assertEquals(1.toByte(), serialized[1]) + Assert.assertEquals(PmdSecret.SecurityStrategy.AES128.numVal.toByte(), serialized[2]) + Assert.assertArrayEquals(expectedKey, serialized.drop(3).toByteArray()) + } + + @Test + fun `test strategy AES256 serialization`() { + //Arrange + val expectedKey = key16bytes + key16bytes.reversed() + val pmdSecret = PmdSecret(strategy = PmdSecret.SecurityStrategy.AES256, key = expectedKey) + + //Act + val serialized = pmdSecret.serializeToPmdSettings() + + //Assert + Assert.assertEquals(1 + 1 + 1 + 32, serialized.size) + Assert.assertEquals(PmdSetting.PmdSettingType.SECURITY.numVal.toByte(), serialized[0]) + Assert.assertEquals(1.toByte(), serialized[1]) + Assert.assertEquals(PmdSecret.SecurityStrategy.AES256.numVal.toByte(), serialized[2]) + Assert.assertArrayEquals(expectedKey, serialized.drop(3).toByteArray()) + } + + @Test + fun `test decryption strategy NONE`() { + //Arrange + val chipper = byteArrayOf( + 0x00.toByte(), 0x01.toByte(), 0x02.toByte(), 0x03.toByte(), 0x04.toByte(), 0x05.toByte(), 0x06.toByte(), 0x07.toByte(), 0x08.toByte(), 0x09.toByte(), 0x0A.toByte(), 0x0B.toByte(), 0x0C.toByte(), 0x0D.toByte(), 0x0E.toByte(), 0xFF.toByte(), + ) + + val expectedDecryptedData = byteArrayOf( + 0x00.toByte(), 0x01.toByte(), 0x02.toByte(), 0x03.toByte(), 0x04.toByte(), 0x05.toByte(), 0x06.toByte(), 0x07.toByte(), 0x08.toByte(), 0x09.toByte(), 0x0A.toByte(), 0x0B.toByte(), 0x0C.toByte(), 0x0D.toByte(), 0x0E.toByte(), 0xFF.toByte(), + ) + + val key = byteArrayOf() + val pmdSecret = PmdSecret(strategy = PmdSecret.SecurityStrategy.NONE, key = key) + + //Act + val decryptedData = pmdSecret.decryptArray(chipper) + + //Assert + Assert.assertArrayEquals(expectedDecryptedData, decryptedData) + } + + @Test + fun `test decryption strategy XOR`() { + //Arrange + val chipper = byteArrayOf( + 0x00.toByte(), 0x01.toByte(), 0x02.toByte(), 0x03.toByte(), 0x04.toByte(), 0x05.toByte(), 0x06.toByte(), 0x07.toByte(), 0x08.toByte(), 0x09.toByte(), 0x0A.toByte(), 0x0B.toByte(), 0x0C.toByte(), 0x0D.toByte(), 0x0E.toByte(), 0xFF.toByte(), + ) + + val expectedDecryptedData = byteArrayOf( + 0x55.toByte(), 0x54.toByte(), 0x57.toByte(), 0x56.toByte(), 0x51.toByte(), 0x50.toByte(), 0x53.toByte(), 0x52.toByte(), 0x5D.toByte(), 0x5C.toByte(), 0x5F.toByte(), 0x5E.toByte(), 0x59.toByte(), 0x58.toByte(), 0x5B.toByte(), 0xAA.toByte(), + ) + + val key = byteArrayOf(0x55) + val pmdSecret = PmdSecret(strategy = PmdSecret.SecurityStrategy.XOR, key = key) + + //Act + val decryptedData = pmdSecret.decryptArray(chipper) + + //Assert + Assert.assertArrayEquals(expectedDecryptedData, decryptedData) + } + + @Test + fun `test decryption strategy AE128`() { + //Arrange + val chipper = byteArrayOf( + 0x00.toByte(), 0x01.toByte(), 0x02.toByte(), 0x03.toByte(), 0x04.toByte(), 0x05.toByte(), 0x06.toByte(), 0x07.toByte(), 0x08.toByte(), 0x09.toByte(), 0x0A.toByte(), 0x0B.toByte(), 0x0C.toByte(), 0x0D.toByte(), 0x0E.toByte(), 0xFF.toByte(), + 0x00.toByte(), 0x01.toByte(), 0x02.toByte(), 0x03.toByte(), 0x04.toByte(), 0x05.toByte(), 0x06.toByte(), 0x07.toByte(), 0x08.toByte(), 0x09.toByte(), 0x0A.toByte(), 0x0B.toByte(), 0x0C.toByte(), 0x0D.toByte(), 0x0E.toByte(), 0xFF.toByte(), + 0xFF.toByte(), 0xFF.toByte(), 0x02.toByte(), 0x03.toByte(), 0x04.toByte(), 0x05.toByte(), 0x06.toByte(), 0x07.toByte(), 0x08.toByte(), 0x09.toByte(), 0x0A.toByte(), 0x0B.toByte(), 0x0C.toByte(), 0x0D.toByte(), 0x0E.toByte(), 0xFF.toByte() + ) + + val expectedDecryptedData = byteArrayOf( + 0x60.toByte(), 0x08.toByte(), 0x6b.toByte(), 0xda.toByte(), 0x00.toByte(), 0xdb.toByte(), 0x42.toByte(), 0x62.toByte(), 0x34.toByte(), 0x60.toByte(), 0x27.toByte(), 0x43.toByte(), 0x71.toByte(), 0xa7.toByte(), 0x53.toByte(), 0x68.toByte(), + 0x60.toByte(), 0x08.toByte(), 0x6b.toByte(), 0xda.toByte(), 0x00.toByte(), 0xdb.toByte(), 0x42.toByte(), 0x62.toByte(), 0x34.toByte(), 0x60.toByte(), 0x27.toByte(), 0x43.toByte(), 0x71.toByte(), 0xa7.toByte(), 0x53.toByte(), 0x68.toByte(), + 0x6f.toByte(), 0x5e.toByte(), 0x05.toByte(), 0x8b.toByte(), 0x37.toByte(), 0xdd.toByte(), 0xd1.toByte(), 0xed.toByte(), 0x0e.toByte(), 0xf2.toByte(), 0x89.toByte(), 0xef.toByte(), 0xf8.toByte(), 0xb2.toByte(), 0x85.toByte(), 0x54.toByte(), + ) + + val key = key16bytes + val pmdSecret = PmdSecret(strategy = PmdSecret.SecurityStrategy.AES128, key = key) + + //Act + val decryptedData = pmdSecret.decryptArray(chipper) + + //Assert + Assert.assertArrayEquals(expectedDecryptedData, decryptedData) + } + + @Test + fun `test decryption strategy AES256`() { + //Arrange + val chipper = byteArrayOf( + 0x00.toByte(), 0x01.toByte(), 0x02.toByte(), 0x03.toByte(), 0x04.toByte(), 0x05.toByte(), 0x06.toByte(), 0x07.toByte(), 0x08.toByte(), 0x09.toByte(), 0x0A.toByte(), 0x0B.toByte(), 0x0C.toByte(), 0x0D.toByte(), 0x0E.toByte(), 0xFF.toByte(), + 0x00.toByte(), 0x01.toByte(), 0x02.toByte(), 0x03.toByte(), 0x04.toByte(), 0x05.toByte(), 0x06.toByte(), 0x07.toByte(), 0x08.toByte(), 0x09.toByte(), 0x0A.toByte(), 0x0B.toByte(), 0x0C.toByte(), 0x0D.toByte(), 0x0E.toByte(), 0xFF.toByte(), + 0xFF.toByte(), 0xFF.toByte(), 0x02.toByte(), 0x03.toByte(), 0x04.toByte(), 0x05.toByte(), 0x06.toByte(), 0x07.toByte(), 0x08.toByte(), 0x09.toByte(), 0x0A.toByte(), 0x0B.toByte(), 0x0C.toByte(), 0x0D.toByte(), 0x0E.toByte(), 0xFF.toByte() + ) + + val expectedDecryptedData = byteArrayOf( + 0xc8.toByte(), 0x0d.toByte(), 0x56.toByte(), 0xbb.toByte(), 0x97.toByte(), 0x7a.toByte(), 0x42.toByte(), 0x5f.toByte(), 0x5a.toByte(), 0xa1.toByte(), 0xcd.toByte(), 0xfc.toByte(), 0x24.toByte(), 0xa2.toByte(), 0x78.toByte(), 0x12.toByte(), + 0xc8.toByte(), 0x0d.toByte(), 0x56.toByte(), 0xbb.toByte(), 0x97.toByte(), 0x7a.toByte(), 0x42.toByte(), 0x5f.toByte(), 0x5a.toByte(), 0xa1.toByte(), 0xcd.toByte(), 0xfc.toByte(), 0x24.toByte(), 0xa2.toByte(), 0x78.toByte(), 0x12.toByte(), + 0x30.toByte(), 0x04.toByte(), 0xb9.toByte(), 0x9f.toByte(), 0x6f.toByte(), 0xfa.toByte(), 0x3b.toByte(), 0xb7.toByte(), 0x73.toByte(), 0xb1.toByte(), 0x75.toByte(), 0xa5.toByte(), 0x23.toByte(), 0x5d.toByte(), 0xcb.toByte(), 0x93.toByte(), + ) + + val key = key16bytes + key16bytes + val pmdSecret = PmdSecret(strategy = PmdSecret.SecurityStrategy.AES256, key = key) + + //Act + val decryptedData = pmdSecret.decryptArray(chipper) + + //Assert + Assert.assertArrayEquals(expectedDecryptedData, decryptedData) + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/AccDataTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/AccDataTest.kt index e114ca5d..cf506d7e 100644 --- a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/AccDataTest.kt +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/AccDataTest.kt @@ -257,4 +257,30 @@ internal class AccDataTest { Assert.assertEquals(100uL, accData.accSamples[0].timeStamp) Assert.assertEquals(101uL, accData.accSamples[1].timeStamp) } -} \ No newline at end of file +} + +private val measurementFrameAccType0Short = byteArrayOf( + 0x5D.toByte(), 0xFF.toByte(), 0x98.toByte(), 0x00.toByte(), 0xFD.toByte(), 0x0F.toByte(), 0x06.toByte(), 0x2A.toByte(), 0xF2.toByte(), + 0xB0.toByte(), 0x13.toByte(), 0xEC.toByte(), 0xA1.toByte(), 0x1B.toByte(), 0x3E.toByte(), 0xC3.toByte(), 0xD0.toByte(), 0x40.toByte(), + 0x7F.toByte(), 0xCD.toByte(), 0x40.toByte(), 0x71.toByte(), 0xD8.toByte(), 0xF3.toByte(), 0x4D.toByte(), 0x00.toByte(), 0x46.toByte(), + 0x3F.toByte(), 0x08.toByte(), 0x7B.toByte(), 0x9F.toByte(), 0xD7.toByte(), 0x3A.toByte(), 0xAF.toByte(), 0x38.toByte(), 0x52.toByte(), + 0xA6.toByte(), 0x13.toByte(), 0xED.toByte(), 0x3E.toByte(), 0x13.toByte(), 0x09.toByte(), 0x91.toByte(), 0xDC.toByte(), 0xFA.toByte(), + 0xEE.toByte(), 0xEF.toByte(), 0x3E.toByte(), 0x54.toByte(), 0x10.toByte(), 0xE2.toByte(), 0x8F.toByte(), 0x77.toByte(), 0x85.toByte(), + 0xEE.toByte(), 0xF2.toByte(), 0x4C.toByte(), 0x7E.toByte(), 0x04.toByte(), 0x87.toByte(), 0x9C.toByte(), 0xFC.toByte(), 0xD2.toByte(), + 0x2F.toByte(), 0x10.toByte(), 0xFD.toByte(), 0x4F.toByte(), 0xEF.toByte(), 0xFE.toByte(), 0x21.toByte(), 0xFF.toByte(), 0xC2.toByte(), + 0x81.toByte(), 0x04.toByte(), 0x03.toByte(), 0x5F.toByte(), 0x13.toByte(), 0x45.toByte(), 0xD1.toByte(), 0x08.toByte(), 0x4A.toByte(), + 0xDF.toByte(), 0xBF.toByte(), 0xB5.toByte(), 0x51.toByte(), 0x44.toByte(), 0x7B.toByte(), 0x50.toByte(), 0xF7.toByte(), 0x39.toByte(), + 0xA1.toByte(), 0x10.toByte(), 0x2E.toByte(), 0x6F.toByte(), 0x18.toByte(), 0xF7.toByte(), 0x9F.toByte(), 0x1C.toByte(), 0x36.toByte(), + 0x4D.toByte(), 0x1B.toByte(), 0x0D.toByte(), 0x03.toByte(), 0x0C.toByte(), 0x14.toByte(), 0x06.toByte(), 0xD0.toByte(), 0xFE.toByte(), + 0xF7.toByte(), 0xAF.toByte(), 0xFF.toByte(), 0xFC.toByte(), 0xBF.toByte(), 0xFF.toByte(), 0xF4.toByte(), 0x3F.toByte(), 0x01.toByte(), + 0x17.toByte(), 0xF0.toByte(), 0x00.toByte(), 0x01.toByte(), 0x10.toByte(), 0x00.toByte(), 0xF8.toByte(), 0xDF.toByte(), 0xFF.toByte(), + 0x00.toByte() + + +) + +// index type data: +// 0-5: Reference sample 5D FF 98 00 FD 0F +// Sample 0 (aka. reference sample): +// channel 0: 5D FF => 0xFF5D => -163 +private const val accShortSample0Channel0 = -163 \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/GnssLocationDataTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/GnssLocationDataTest.kt index 59eae6ac..6f8b6944 100644 --- a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/GnssLocationDataTest.kt +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/GnssLocationDataTest.kt @@ -133,6 +133,7 @@ internal class GnssLocationDataTest { getFactor = { factor }, getSampleRate = { 0 }) + // Act val gnssData = GnssLocationData.parseDataFromDataFrame(dataFrame) diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/OfflineHrDataTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/OfflineHrDataTest.kt new file mode 100644 index 00000000..dffc504a --- /dev/null +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/OfflineHrDataTest.kt @@ -0,0 +1,58 @@ +package com.polar.androidcommunications.api.ble.model.gatt.client.pmd.model + +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdDataFrame +import com.polar.androidcommunications.testrules.BleLoggerTestRule +import org.junit.Assert +import org.junit.Rule +import org.junit.Test + +internal class OfflineHrDataTest { + @Rule + @JvmField + val bleLoggerTestRule = BleLoggerTestRule() + + @Test + fun `test offline hr data sample`() { + // Arrange + // HEX: 0E 00 00 00 00 00 00 00 00 00 + // index data: + // 0 type 0E (Offline hr) + // 1..9 timestamp 00 00 00 00 00 00 00 00 + // 10 frame type 00 (raw, type 0) + val offlineHrDataFrameHeader = byteArrayOf( + 0x0E.toByte(), + 0x00.toByte(), 0x94.toByte(), 0x35.toByte(), 0x77.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x00.toByte(), + ) + val previousTimeStamp = 0uL + + // index data: + // 0 sample0 00 + val expectedSample0 = 0 + // 1 sample0 FF + val expectedSample1 = 255 + // last index sampleN 7F + val expectedSampleLast = 127 + val expectedSampleSize = 9 + val offlineHrDataFrameContent = byteArrayOf( + 0x00.toByte(), 0xFF.toByte(), 0x32.toByte(), 0x32.toByte(), 0x33.toByte(), 0x33.toByte(), 0x34.toByte(), 0x35.toByte(), 0x7F.toByte(), + ) + + val dataFrame = PmdDataFrame( + data = offlineHrDataFrameHeader + offlineHrDataFrameContent, + getPreviousTimeStamp = { previousTimeStamp }, + getFactor = { 1.0f }, + getSampleRate = { 0 }) + + + // Act + val offlineHrData = OfflineHrData.parseDataFromDataFrame(dataFrame) + + // Assert + Assert.assertEquals(expectedSampleSize, offlineHrData.hrSamples.size) + Assert.assertEquals(expectedSample0, offlineHrData.hrSamples.first().hr) + Assert.assertEquals(expectedSample1, offlineHrData.hrSamples[1].hr) + Assert.assertEquals(expectedSampleLast, offlineHrData.hrSamples.last().hr) + + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PpiDataTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PpiDataTest.kt index 7e4da0ee..c44f2fff 100644 --- a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PpiDataTest.kt +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/gatt/client/pmd/model/PpiDataTest.kt @@ -1,22 +1,27 @@ package com.polar.androidcommunications.api.ble.model.gatt.client.pmd.model import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdDataFrame +import com.polar.androidcommunications.testrules.BleLoggerTestRule import org.junit.Assert.assertEquals +import org.junit.Rule import org.junit.Test -class PpiDataTest { +internal class PpiDataTest { + @Rule + @JvmField + val bleLoggerTestRule = BleLoggerTestRule() @Test fun `process ppi raw data type 0`() { // Arrange - // HEX: 03 00 94 35 77 00 00 00 00 00 + // HEX: 03 00 00 00 00 00 00 00 00 00 // index data: // 0 type 03 (PPI) // 1..9 timestamp 00 00 00 00 00 00 00 00 // 10 frame type 00 (raw, type 0) val ppiDataFrameHeader = byteArrayOf( - 0x01.toByte(), - 0x00.toByte(), 0x94.toByte(), 0x35.toByte(), 0x77.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x03.toByte(), + 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), ) val previousTimeStamp = 100uL @@ -51,7 +56,6 @@ class PpiDataTest { 0x80.toByte(), 0xFF.toByte(), 0x00.toByte(), 0x01.toByte(), 0x00.toByte(), 0x01.toByte(), 0x00.toByte(), 0x00.toByte() ) - val timeStamp: Long = Long.MAX_VALUE val dataFrame = PmdDataFrame( data = ppiDataFrameHeader + ppiDataFrameContent, @@ -79,5 +83,4 @@ class PpiDataTest { assertEquals(2, ppiData.ppiSamples.size) } - } \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingDataTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingDataTest.kt new file mode 100644 index 00000000..df122832 --- /dev/null +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingDataTest.kt @@ -0,0 +1,1736 @@ +package com.polar.androidcommunications.api.ble.model.offlinerecording + +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdMeasurementType +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdSecret +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdSetting +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.model.* +import com.polar.androidcommunications.testrules.BleLoggerTestRule +import org.joda.time.DateTime +import org.junit.Assert +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import java.util.* + +internal class OfflineRecordingDataTest { + + @Rule + @JvmField + val bleLoggerTestRule = BleLoggerTestRule() + + @Test + fun `header parsing from offline recording data`() { + // Act + val offlineAccData = OfflineRecordingData.parseDataFromOfflineFile(MockAccOfflineRecordingData.accOfflineFrame, PmdMeasurementType.ACC) + + // Assert + assertTrue(offlineAccData.data is AccData) + Assert.assertEquals(MockAccOfflineRecordingData.headerMagic, offlineAccData.offlineRecordingHeader.magic) + Assert.assertEquals(MockAccOfflineRecordingData.headerVersion, offlineAccData.offlineRecordingHeader.version) + Assert.assertEquals(MockAccOfflineRecordingData.headerFree, offlineAccData.offlineRecordingHeader.free) + Assert.assertEquals(MockAccOfflineRecordingData.headerEswHash, offlineAccData.offlineRecordingHeader.eswHash) + } + + @Test + fun `start time parsing from offline recording data`() { + // Act + val offlineAccData = OfflineRecordingData.parseDataFromOfflineFile(MockAccOfflineRecordingData.accOfflineFrame, PmdMeasurementType.ACC) + + // Assert + Assert.assertEquals(MockAccOfflineRecordingData.startTime, offlineAccData.startTime) + } + + @Test + fun `settings parsing from offline recording data`() { + // Act + val offlineAccData = OfflineRecordingData.parseDataFromOfflineFile(MockAccOfflineRecordingData.accOfflineFrame, PmdMeasurementType.ACC) + + // Assert + Assert.assertEquals(MockAccOfflineRecordingData.expectedSampleRate, offlineAccData.recordingSettings?.settings?.get(PmdSetting.PmdSettingType.SAMPLE_RATE)?.first()) + Assert.assertEquals(MockAccOfflineRecordingData.expectedRange, offlineAccData.recordingSettings?.settings?.get(PmdSetting.PmdSettingType.RANGE)?.first()) + Assert.assertEquals(MockAccOfflineRecordingData.expectedResolution, offlineAccData.recordingSettings?.settings?.get(PmdSetting.PmdSettingType.RESOLUTION)?.first()) + Assert.assertEquals(MockAccOfflineRecordingData.expectedChannels, offlineAccData.recordingSettings?.settings?.get(PmdSetting.PmdSettingType.CHANNELS)?.first()) + } + + @Test + fun `parse ACC from offline recording data`() { + // Act + val offlineAccData = OfflineRecordingData.parseDataFromOfflineFile(MockAccOfflineRecordingData.accOfflineFrame, PmdMeasurementType.ACC) + + // Assert + assertTrue(offlineAccData.data is AccData) + Assert.assertEquals(MockAccOfflineRecordingData.sample0Channel0, (offlineAccData.data as AccData).accSamples[0].x) + Assert.assertEquals(MockAccOfflineRecordingData.sample0Channel1, offlineAccData.data.accSamples[0].y) + Assert.assertEquals(MockAccOfflineRecordingData.sample0Channel2, offlineAccData.data.accSamples[0].z) + + Assert.assertEquals(MockAccOfflineRecordingData.sample1Channel0, offlineAccData.data.accSamples[1].x) + Assert.assertEquals(MockAccOfflineRecordingData.sample1Channel1, offlineAccData.data.accSamples[1].y) + Assert.assertEquals(MockAccOfflineRecordingData.sample1Channel2, offlineAccData.data.accSamples[1].z) + + Assert.assertEquals(MockAccOfflineRecordingData.expectedLastSampleTimeStamp, offlineAccData.data.accSamples.last().timeStamp) + } + + @Test + fun `parse Gyro from offline recording data`() { + // Act + val offlineGyroData = OfflineRecordingData.parseDataFromOfflineFile(GyroOfflineMockData.gyroOfflineFrame, PmdMeasurementType.GYRO) + + // Assert + assertTrue(offlineGyroData.data is GyrData) + Assert.assertEquals(GyroOfflineMockData.sample0Channel0, (offlineGyroData.data as GyrData).gyrSamples[0].x) + Assert.assertEquals(GyroOfflineMockData.sample0Channel1, offlineGyroData.data.gyrSamples[0].y) + Assert.assertEquals(GyroOfflineMockData.sample0Channel2, offlineGyroData.data.gyrSamples[0].z) + + Assert.assertEquals(GyroOfflineMockData.sample1Channel0, offlineGyroData.data.gyrSamples[1].x) + Assert.assertEquals(GyroOfflineMockData.sample1Channel1, offlineGyroData.data.gyrSamples[1].y) + Assert.assertEquals(GyroOfflineMockData.sample1Channel2, offlineGyroData.data.gyrSamples[1].z) + + Assert.assertEquals(GyroOfflineMockData.expectedLastSampleTimeStamp, offlineGyroData.data.gyrSamples.last().timeStamp) + } + + @Test + fun `parse Mag from offline recording data`() { + // Act + val offlineMagData = OfflineRecordingData.parseDataFromOfflineFile(MagOfflineMockData.magOfflineFrame, PmdMeasurementType.MAGNETOMETER) + + // Assert + assertTrue(offlineMagData.data is MagData) + Assert.assertEquals(MagOfflineMockData.sample0Channel0, (offlineMagData.data as MagData).magSamples[0].x) + Assert.assertEquals(MagOfflineMockData.sample0Channel1, offlineMagData.data.magSamples[0].y) + Assert.assertEquals(MagOfflineMockData.sample0Channel2, offlineMagData.data.magSamples[0].z) + + Assert.assertEquals(MagOfflineMockData.sample1Channel0, offlineMagData.data.magSamples[1].x) + Assert.assertEquals(MagOfflineMockData.sample1Channel1, offlineMagData.data.magSamples[1].y) + Assert.assertEquals(MagOfflineMockData.sample1Channel2, offlineMagData.data.magSamples[1].z) + + Assert.assertEquals(MagOfflineMockData.expectedLastSampleTimeStamp, offlineMagData.data.magSamples.last().timeStamp) + } + + @Test + fun `parse PPG from offline recording data`() { + // Act + val offlinePpgData = OfflineRecordingData.parseDataFromOfflineFile(PpgOfflineMockData.ppgOfflineFrame, PmdMeasurementType.PPG) + + // Assert + assertTrue(offlinePpgData.data is PpgData) + val sample0 = (offlinePpgData.data as PpgData).ppgSamples as MutableList + + Assert.assertEquals(PpgOfflineMockData.sample0Channel0, sample0[0].ppgDataSamples[0]) + Assert.assertEquals(PpgOfflineMockData.sample0Channel1, sample0[0].ppgDataSamples[1]) + Assert.assertEquals(PpgOfflineMockData.sample0Channel2, sample0[0].ppgDataSamples[2]) + Assert.assertEquals(PpgOfflineMockData.sample0Ambient, sample0[0].ambientSample) + } + + @Test + fun `parse PPI from offline recording data`() { + // Act + val offlinePpiData = OfflineRecordingData.parseDataFromOfflineFile(PpiOfflineMockData.ppiOfflineFrame, PmdMeasurementType.PPI) + + // Assert + assertTrue(offlinePpiData.data is PpiData) + Assert.assertEquals(PpiOfflineMockData.expectedHeartRate, (offlinePpiData.data as PpiData).ppiSamples[0].hr) + Assert.assertEquals(PpiOfflineMockData.expectedBlockerBit, (offlinePpiData.data).ppiSamples[0].blockerBit) + Assert.assertEquals(PpiOfflineMockData.expectedIntervalInMs, (offlinePpiData.data).ppiSamples[0].ppInMs) + Assert.assertEquals(PpiOfflineMockData.expectedErrorEstimate, (offlinePpiData.data).ppiSamples[0].ppErrorEstimate) + Assert.assertEquals(PpiOfflineMockData.expectedSkinContactStatus, (offlinePpiData.data).ppiSamples[0].skinContactStatus) + Assert.assertEquals(PpiOfflineMockData.expectedSkinContactSupported, (offlinePpiData.data).ppiSamples[0].skinContactSupported) + + Assert.assertEquals(PpiOfflineMockData.expectedHeartRate2, (offlinePpiData.data).ppiSamples[1].hr) + Assert.assertEquals(PpiOfflineMockData.expectedBlockerBit2, (offlinePpiData.data).ppiSamples[1].blockerBit) + Assert.assertEquals(PpiOfflineMockData.expectedIntervalInMs2, (offlinePpiData.data).ppiSamples[1].ppInMs) + Assert.assertEquals(PpiOfflineMockData.expectedErrorEstimate2, (offlinePpiData.data).ppiSamples[1].ppErrorEstimate) + Assert.assertEquals(PpiOfflineMockData.expectedSkinContactStatus2, (offlinePpiData.data).ppiSamples[1].skinContactStatus) + Assert.assertEquals(PpiOfflineMockData.expectedSkinContactSupported2, (offlinePpiData.data).ppiSamples[1].skinContactSupported) + + // calculated manually from ppiOfflineFrame + Assert.assertEquals(11, (offlinePpiData.data).ppiSamples.size) + } + + @Test + fun `parse Hr from offline recording data`() { + // Act + val offlineHrData = OfflineRecordingData.parseDataFromOfflineFile(HrOfflineMockData.hrOfflineFrame, PmdMeasurementType.OFFLINE_HR) + + // Assert + assertTrue(offlineHrData.data is OfflineHrData) + + Assert.assertEquals(HrOfflineMockData.expectedSize, (offlineHrData.data as OfflineHrData).hrSamples.size) + Assert.assertEquals(HrOfflineMockData.hrSample0, offlineHrData.data.hrSamples.first().hr) + Assert.assertEquals(HrOfflineMockData.hrSample1, offlineHrData.data.hrSamples[1].hr) + Assert.assertEquals(HrOfflineMockData.hrSample2, offlineHrData.data.hrSamples[2].hr) + Assert.assertEquals(HrOfflineMockData.hrSampleLast, offlineHrData.data.hrSamples.last().hr) + } + + @Test + fun `parse offline recording data containing only header`() { + // Arrange & Act & Assert + assertThrows(OfflineRecordingError.OfflineRecordingErrorMetaDataParseFailed::class.java) { + OfflineRecordingData.parseDataFromOfflineFile(offlineFrameWithoutData, PmdMeasurementType.MAGNETOMETER) + } + } + + @Test + fun `parse encrypted ACC offline recording data`() { + // Act + val offlineData = OfflineRecordingData.parseDataFromOfflineFile(MockEncryptedOfflineRecordingDataAcc.securedOfflineData, PmdMeasurementType.ACC, MockEncryptedOfflineRecordingDataAcc.security) + // Assert + assertTrue(offlineData.data is AccData) + } + + @Test + fun `parse encrypted Magnetometer offline recording data`() { + // Act + val offlineData = OfflineRecordingData.parseDataFromOfflineFile(MockEncryptedOfflineRecordingDataMAG.securedOfflineData, PmdMeasurementType.MAGNETOMETER, MockEncryptedOfflineRecordingDataMAG.security) + // Assert + assertTrue(offlineData.data is MagData) + } + + private object MockEncryptedOfflineRecordingDataAcc { + val security = PmdSecret(strategy = PmdSecret.SecurityStrategy.AES128, key = byteArrayOf(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F)) + + val securedOfflineData = byteArrayOf( + 0x02.toByte(), 0xaa.toByte(), 0xc7.toByte(), 0x79.toByte(), 0x8f.toByte(), 0x77.toByte(), 0x19.toByte(), 0xce.toByte(), 0xa5.toByte(), 0x60.toByte(), + 0x5b.toByte(), 0x85.toByte(), 0x54.toByte(), 0xc5.toByte(), 0xcb.toByte(), 0x9a.toByte(), 0x71.toByte(), 0x7b.toByte(), 0xed.toByte(), 0x26.toByte(), + 0x65.toByte(), 0xb0.toByte(), 0xa6.toByte(), 0xf3.toByte(), 0xdf.toByte(), 0x18.toByte(), 0x4f.toByte(), 0x8f.toByte(), 0x82.toByte(), 0x3f.toByte(), + 0x43.toByte(), 0x19.toByte(), 0x9e.toByte(), 0x06.toByte(), 0x1e.toByte(), 0x54.toByte(), 0x93.toByte(), 0x8b.toByte(), 0xd1.toByte(), 0xd7.toByte(), + 0x55.toByte(), 0x9d.toByte(), 0xe3.toByte(), 0xc4.toByte(), 0xae.toByte(), 0xef.toByte(), 0x3b.toByte(), 0x1c.toByte(), 0x57.toByte(), 0xba.toByte(), + 0xda.toByte(), 0xf8.toByte(), 0xd5.toByte(), 0x29.toByte(), 0x5d.toByte(), 0xd6.toByte(), 0x13.toByte(), 0xbe.toByte(), 0x65.toByte(), 0x73.toByte(), + 0xa2.toByte(), 0x62.toByte(), 0x8d.toByte(), 0xb4.toByte(), 0xfc.toByte(), 0x84.toByte(), 0x34.toByte(), 0xcb.toByte(), 0xb5.toByte(), 0xdd.toByte(), + 0x5a.toByte(), 0x87.toByte(), 0x20.toByte(), 0x97.toByte(), 0xc8.toByte(), 0x8c.toByte(), 0x57.toByte(), 0x42.toByte(), 0x59.toByte(), 0x94.toByte(), + 0x51.toByte(), 0x0e.toByte(), 0xb2.toByte(), 0x6d.toByte(), 0x22.toByte(), 0x34.toByte(), 0xa1.toByte(), 0x4b.toByte(), 0x7f.toByte(), 0x0b.toByte(), + 0x8d.toByte(), 0x34.toByte(), 0x0c.toByte(), 0x5c.toByte(), 0x0c.toByte(), 0xd0.toByte(), 0x0f.toByte(), 0x0a.toByte(), 0x1f.toByte(), 0x4d.toByte(), + 0xfc.toByte(), 0x0f.toByte(), 0x0f.toByte(), 0x1c.toByte(), 0xe0.toByte(), 0xc9.toByte(), 0x83.toByte(), 0x08.toByte(), 0x0f.toByte(), 0xf3.toByte(), + 0xff.toByte(), 0x8c.toByte(), 0x3c.toByte(), 0x0c.toByte(), 0x32.toByte(), 0xec.toByte(), 0x0f.toByte(), 0xcc.toByte(), 0xcc.toByte(), 0x0f.toByte(), + 0xf1.toByte(), 0xec.toByte(), 0x1f.toByte(), 0x08.toByte(), 0xbd.toByte(), 0xe7.toByte(), 0x32.toByte(), 0xdc.toByte(), 0x0f.toByte(), 0x8f.toByte(), + 0x1d.toByte(), 0x04.toByte(), 0xcd.toByte(), 0xb3.toByte(), 0xe7.toByte(), 0x8c.toByte(), 0x6c.toByte(), 0xe4.toByte(), 0x32.toByte(), 0x3c.toByte(), + 0x18.toByte(), 0x36.toByte(), 0x08.toByte(), 0x18.toByte(), 0xee.toByte(), 0x0f.toByte(), 0xec.toByte(), 0xcf.toByte(), 0xff.toByte(), 0x0c.toByte(), + 0x13.toByte(), 0xed.toByte(), 0x2e.toByte(), 0x03.toByte(), 0x42.toByte(), 0x1d.toByte(), 0xf5.toByte(), 0x4e.toByte(), 0xfd.toByte(), 0x08.toByte(), + 0x4c.toByte(), 0xe1.toByte(), 0x20.toByte(), 0xbe.toByte(), 0xc3.toByte(), 0x2a.toByte(), 0x0c.toByte(), 0x1c.toByte(), 0x00.toByte(), 0xc2.toByte(), + 0xf2.toByte(), 0x1a.toByte(), 0x1a.toByte(), 0xbd.toByte(), 0x0a.toByte(), 0x30.toByte(), 0xce.toByte(), 0x13.toByte(), 0xfc.toByte(), 0x31.toByte(), + 0xfc.toByte(), 0x03.toByte(), 0xf2.toByte(), 0xe3.toByte(), 0x1b.toByte(), 0x72.toByte(), 0xec.toByte(), 0x1b.toByte(), 0xf2.toByte(), 0xe3.toByte(), + 0x1f.toByte(), 0xc8.toByte(), 0xfc.toByte(), 0xf7.toByte(), 0xf1.toByte(), 0xdc.toByte(), 0x07.toByte(), 0x31.toByte(), 0x1e.toByte(), 0xf0.toByte(), + 0x35.toByte(), 0xfc.toByte(), 0x0b.toByte(), 0xf5.toByte(), 0x0c.toByte(), 0x1c.toByte(), 0x31.toByte(), 0x2c.toByte(), 0xf0.toByte(), 0x70.toByte(), + 0x63.toByte(), 0x10.toByte(), 0x73.toByte(), 0xcc.toByte(), 0x07.toByte(), 0x74.toByte(), 0x3c.toByte(), 0x0c.toByte(), 0x31.toByte(), 0x3c.toByte(), + 0x2c.toByte(), 0x36.toByte(), 0x1d.toByte(), 0x2c.toByte(), 0xf0.toByte(), 0x60.toByte(), 0x03.toByte(), 0x01.toByte(), 0xae.toByte(), 0xe3.toByte(), + 0xcd.toByte(), 0x2c.toByte(), 0xf4.toByte(), 0xc8.toByte(), 0xf2.toByte(), 0xe3.toByte(), 0x8f.toByte(), 0x1c.toByte(), 0xf4.toByte(), 0x32.toByte(), + 0x4c.toByte(), 0xf4.toByte(), 0x48.toByte(), 0x03.toByte(), 0xf4.toByte(), 0x73.toByte(), 0xcc.toByte(), 0x1b.toByte(), 0x4d.toByte(), 0x2c.toByte(), + 0xf0.toByte(), 0x4d.toByte(), 0x22.toByte(), 0x00.toByte(), 0x0a.toByte(), 0x03.toByte(), 0xd4.toByte(), 0xca.toByte(), 0x52.toByte(), 0xe8.toByte(), + 0x8f.toByte(), 0x43.toByte(), 0x08.toByte(), 0x09.toByte(), 0x1c.toByte(), 0xf4.toByte(), 0xf1.toByte(), 0xf3.toByte(), 0x1f.toByte(), 0x8e.toByte(), + 0x0c.toByte(), 0xe0.toByte(), 0xcc.toByte(), 0x23.toByte(), 0x0c.toByte(), 0xb3.toByte(), 0xe3.toByte(), 0x0b.toByte(), 0x4d.toByte(), 0xcd.toByte(), + 0x0b.toByte(), 0x30.toByte(), 0xed.toByte(), 0x03.toByte(), 0xf7.toByte(), 0xbc.toByte(), 0x2f.toByte(), 0xcc.toByte(), 0xc3.toByte(), 0xff.toByte(), + 0x4a.toByte(), 0xac.toByte(), 0x0c.toByte(), 0x0d.toByte(), 0xc2.toByte(), 0x0b.toByte(), 0x4d.toByte(), 0xd3.toByte(), 0xf7.toByte(), 0xf3.toByte(), + 0x33.toByte(), 0x14.toByte(), 0x0d.toByte(), 0x03.toByte(), 0x0c.toByte(), 0x8f.toByte(), 0xd3.toByte(), 0xfb.toByte(), 0xcd.toByte(), 0x3c.toByte(), + 0xf4.toByte(), 0x08.toByte(), 0x06.toByte(), 0x1d.toByte(), 0xef.toByte(), 0xf2.toByte(), 0x33.toByte(), 0x42.toByte(), 0x0c.toByte(), 0xc1.toByte(), + 0x32.toByte(), 0x5d.toByte(), 0x17.toByte(), 0x50.toByte(), 0xe3.toByte(), 0x30.toByte(), 0x0d.toByte(), 0x42.toByte(), 0x06.toByte(), 0x04.toByte(), + 0xf3.toByte(), 0xf3.toByte(), 0x13.toByte(), 0x8c.toByte(), 0x0d.toByte(), 0x0a.toByte(), 0x08.toByte(), 0xec.toByte(), 0xf3.toByte(), 0xf2.toByte(), + 0xfd.toByte(), 0x23.toByte(), 0xfc.toByte(), 0xb2.toByte(), 0xe4.toByte(), 0xe2.toByte(), 0x17.toByte(), 0x0c.toByte(), 0x36.toByte(), 0xf0.toByte(), + 0x1d.toByte(), 0x48.toByte(), 0xfe.toByte(), 0xcc.toByte(), 0xf3.toByte(), 0x3a.toByte(), 0xd0.toByte(), 0x73.toByte(), 0x8c.toByte(), 0xf6.toByte(), + 0x1a.toByte(), 0x0d.toByte(), 0x0e.toByte(), 0x8b.toByte(), 0xf8.toByte(), 0x76.toByte(), 0xad.toByte(), 0xa1.toByte(), 0x4b.toByte(), 0x7f.toByte(), + 0x0b.toByte(), 0x8d.toByte(), 0x44.toByte(), 0x0c.toByte(), 0x43.toByte(), 0x0c.toByte(), 0xdc.toByte(), 0x0f.toByte(), 0x0a.toByte(), 0x0d.toByte(), + 0xfc.toByte(), 0xec.toByte(), 0x0c.toByte(), 0x04.toByte(), 0x12.toByte(), 0x00.toByte(), 0xf0.toByte(), 0x05.toByte(), 0xf8.toByte(), 0x0f.toByte(), + 0xf3.toByte(), 0x09.toByte(), 0x0f.toByte(), 0xff.toByte(), 0x0d.toByte(), 0xf1.toByte(), 0xf2.toByte(), 0x0f.toByte(), 0x0c.toByte(), 0x07.toByte(), + 0x1d.toByte(), 0xfe.toByte(), 0x17.toByte(), 0xdf.toByte(), 0x13.toByte(), 0xd7.toByte(), 0x09.toByte(), 0xfb.toByte(), 0x01.toByte(), 0x28.toByte(), + 0xe7.toByte(), 0xfd.toByte(), 0xd2.toByte(), 0x15.toByte(), 0x06.toByte(), 0x17.toByte(), 0xe6.toByte(), 0x05.toByte(), 0xea.toByte(), 0x18.toByte(), + 0xe1.toByte(), 0x14.toByte(), 0xeb.toByte(), 0xda.toByte(), 0xfd.toByte(), 0x1f.toByte(), 0x2d.toByte(), 0xfa.toByte(), 0x07.toByte(), 0x1a.toByte(), + 0x03.toByte(), 0xfa.toByte(), 0x0e.toByte(), 0x0d.toByte(), 0xfb.toByte(), 0xfa.toByte(), 0x0d.toByte(), 0x08.toByte(), 0x0f.toByte(), 0xf6.toByte(), + 0xf2.toByte(), 0x0f.toByte(), 0x0a.toByte(), 0x0d.toByte(), 0xf4.toByte(), 0xf0.toByte(), 0xf0.toByte(), 0xf3.toByte(), 0x06.toByte(), 0xfb.toByte(), + 0xf0.toByte(), 0xf7.toByte(), 0x02.toByte(), 0xfc.toByte(), 0xfe.toByte(), 0x05.toByte(), 0x19.toByte(), 0x0b.toByte(), 0xfa.toByte(), 0x08.toByte(), + 0x04.toByte(), 0x0e.toByte(), 0x0b.toByte(), 0xf6.toByte(), 0xf3.toByte(), 0x09.toByte(), 0x0e.toByte(), 0x0d.toByte(), 0xfd.toByte(), 0xf7.toByte(), + 0xf0.toByte(), 0x04.toByte(), 0x06.toByte(), 0xfa.toByte(), 0xa1.toByte(), 0x06.toByte(), 0x06.toByte(), 0xfd.toByte(), 0x73.toByte(), 0x4c.toByte(), + 0x47.toByte(), 0xf5.toByte(), 0x01.toByte(), 0xd4.toByte(), 0x62.toByte(), 0xcf.toByte(), 0xfa.toByte(), 0xe1.toByte(), 0x9b.toByte(), 0x73.toByte(), + 0xcc.toByte(), 0xf2.toByte(), 0x00.toByte(), 0xf4.toByte(), 0x83.toByte(), 0x33.toByte(), 0xf3.toByte(), 0x0c.toByte(), 0x00.toByte(), 0xec.toByte(), + 0x33.toByte(), 0x0d.toByte(), 0x0c.toByte(), 0x18.toByte(), 0xec.toByte(), 0x33.toByte(), 0x0c.toByte(), 0xf5.toByte(), 0x07.toByte(), 0x5c.toByte(), + 0xf3.toByte(), 0x0a.toByte(), 0xe1.toByte(), 0x5b.toByte(), 0x0c.toByte(), 0x0a.toByte(), 0x12.toByte(), 0xbb.toByte(), 0xbe.toByte(), 0xc2.toByte(), + 0x86.toByte(), 0xae.toByte(), 0x07.toByte(), 0xc2.toByte(), 0xbd.toByte(), 0xc7.toByte(), 0x30.toByte(), 0x5d.toByte(), 0x1c.toByte(), 0xb0.toByte(), + 0xcd.toByte(), 0x03.toByte(), 0x72.toByte(), 0x9c.toByte(), 0xf7.toByte(), 0xcf.toByte(), 0xbc.toByte(), 0xf7.toByte(), 0x8d.toByte(), 0x1c.toByte(), + 0x1c.toByte(), 0x4d.toByte(), 0x03.toByte(), 0xf8.toByte(), 0x0c.toByte(), 0x03.toByte(), 0x14.toByte(), 0x31.toByte(), 0xfe.toByte(), 0x03.toByte(), + 0x74.toByte(), 0x4c.toByte(), 0x04.toByte(), 0xb2.toByte(), 0x12.toByte(), 0xe0.toByte(), 0xc8.toByte(), 0x52.toByte(), 0xf8.toByte(), 0x48.toByte(), + 0x13.toByte(), 0xf4.toByte(), 0xce.toByte(), 0xec.toByte(), 0x03.toByte(), 0xf1.toByte(), 0x0d.toByte(), 0xf0.toByte(), 0xf7.toByte(), 0x43.toByte(), + 0x10.toByte(), 0xb2.toByte(), 0xb3.toByte(), 0xf3.toByte(), 0x4d.toByte(), 0x3c.toByte(), 0xe4.toByte(), 0x31.toByte(), 0x3c.toByte(), 0x1c.toByte(), + 0x4c.toByte(), 0xec.toByte(), 0xa3.toByte(), 0x80.toByte(), 0x00.toByte(), 0x04.toByte(), 0x06.toByte(), 0x15.toByte(), 0xfb.toByte(), 0xf3.toByte(), + 0x0c.toByte(), 0x0c.toByte(), 0x07.toByte(), 0x02.toByte(), 0xe6.toByte(), 0xe4.toByte(), 0xe8.toByte(), 0x15.toByte(), 0x2d.toByte(), 0x06.toByte(), + 0xfb.toByte(), 0x0a.toByte(), 0xf7.toByte(), 0x0e.toByte(), 0xe2.toByte(), 0x0d.toByte(), 0x08.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xf0.toByte(), + 0x0c.toByte(), 0x0f.toByte(), 0xf3.toByte(), 0xf4.toByte(), 0xf1.toByte(), 0x0e.toByte(), 0xf1.toByte(), 0x0a.toByte(), 0x05.toByte(), 0x82.toByte(), + 0x81.toByte(), 0xc8.toByte(), 0x07.toByte(), 0x20.toByte(), 0xd9.toByte(), 0x41.toByte(), 0xf6.toByte(), 0xe0.toByte(), 0x42.toByte(), 0x53.toByte(), + 0xdc.toByte(), 0x0d.toByte(), 0x8c.toByte(), 0xe8.toByte(), 0x4e.toByte(), 0xb2.toByte(), 0xf4.toByte(), 0x73.toByte(), 0x01.toByte(), 0x0c.toByte(), + 0x19.toByte(), 0x0d.toByte(), 0x0e.toByte(), 0x06.toByte(), 0xab.toByte(), 0x76.toByte(), 0x21.toByte(), 0xa2.toByte(), 0x4b.toByte(), 0x7f.toByte(), + 0x0b.toByte(), 0x8d.toByte(), 0x32.toByte(), 0x0c.toByte(), 0x5d.toByte(), 0x0c.toByte(), 0xee.toByte(), 0x0f.toByte(), 0x0a.toByte(), 0x12.toByte(), + 0x71.toByte(), 0xf3.toByte(), 0xff.toByte(), 0x4a.toByte(), 0x4c.toByte(), 0xf8.toByte(), 0x31.toByte(), 0x1c.toByte(), 0x04.toByte(), 0x88.toByte(), + 0xcc.toByte(), 0xe7.toByte(), 0x36.toByte(), 0x3d.toByte(), 0x28.toByte(), 0xcd.toByte(), 0x03.toByte(), 0xf0.toByte(), 0x72.toByte(), 0x02.toByte(), + 0x1c.toByte(), 0x4c.toByte(), 0xf3.toByte(), 0x07.toByte(), 0x4c.toByte(), 0xf3.toByte(), 0x07.toByte(), 0x0e.toByte(), 0x0e.toByte(), 0xf0.toByte(), + 0x37.toByte(), 0x0c.toByte(), 0x08.toByte(), 0x48.toByte(), 0xcc.toByte(), 0xeb.toByte(), 0x0c.toByte(), 0x1d.toByte(), 0x04.toByte(), 0xf7.toByte(), + 0x0c.toByte(), 0x0c.toByte(), 0x0d.toByte(), 0x7c.toByte(), 0xe0.toByte(), 0x32.toByte(), 0xf2.toByte(), 0xf3.toByte(), 0xc9.toByte(), 0x0d.toByte(), + 0xf4.toByte(), 0x8f.toByte(), 0x1c.toByte(), 0xe4.toByte(), 0x4e.toByte(), 0xd3.toByte(), 0xff.toByte(), 0x89.toByte(), 0x0c.toByte(), 0x0c.toByte(), + 0xce.toByte(), 0xb3.toByte(), 0x0b.toByte(), 0x8f.toByte(), 0xe3.toByte(), 0xf3.toByte(), 0x88.toByte(), 0x03.toByte(), 0x08.toByte(), 0x06.toByte(), + 0x40.toByte(), 0x3d.toByte(), 0xf1.toByte(), 0x2d.toByte(), 0xf9.toByte(), 0xa0.toByte(), 0x12.toByte(), 0x4e.toByte(), 0x00.toByte(), 0x42.toByte(), + 0x12.toByte(), 0x2f.toByte(), 0xd3.toByte(), 0x23.toByte(), 0x13.toByte(), 0x0a.toByte(), 0x12.toByte(), 0xf1.toByte(), 0x0c.toByte(), 0x04.toByte(), + 0xb0.toByte(), 0x03.toByte(), 0x1c.toByte(), 0xcd.toByte(), 0xe3.toByte(), 0xff.toByte(), 0x8c.toByte(), 0x7c.toByte(), 0x00.toByte(), 0xf7.toByte(), + 0x93.toByte(), 0x2b.toByte(), 0x71.toByte(), 0xbd.toByte(), 0x0b.toByte(), 0xb3.toByte(), 0xc3.toByte(), 0x07.toByte(), 0x0d.toByte(), 0xcd.toByte(), + 0x0b.toByte(), 0x48.toByte(), 0xec.toByte(), 0xfb.toByte(), 0x4d.toByte(), 0xfd.toByte(), 0x0b.toByte(), 0x72.toByte(), 0x1c.toByte(), 0xf0.toByte(), + 0x72.toByte(), 0x0c.toByte(), 0x08.toByte(), 0x30.toByte(), 0x4c.toByte(), 0x00.toByte(), 0x08.toByte(), 0xfc.toByte(), 0xd3.toByte(), 0x32.toByte(), + 0x0c.toByte(), 0xf4.toByte(), 0xf0.toByte(), 0x2c.toByte(), 0x0c.toByte(), 0xb2.toByte(), 0x3c.toByte(), 0x00.toByte(), 0x8e.toByte(), 0x4c.toByte(), + 0xf8.toByte(), 0x8d.toByte(), 0xd3.toByte(), 0xfb.toByte(), 0xb2.toByte(), 0x0c.toByte(), 0xf4.toByte(), 0xf7.toByte(), 0x3c.toByte(), 0x2c.toByte(), + 0xb2.toByte(), 0x93.toByte(), 0xf7.toByte(), 0x8c.toByte(), 0x0c.toByte(), 0x08.toByte(), 0x06.toByte(), 0x0c.toByte(), 0x0c.toByte(), 0x3c.toByte(), + 0xed.toByte(), 0x36.toByte(), 0x5e.toByte(), 0xe3.toByte(), 0xec.toByte(), 0x3e.toByte(), 0x4e.toByte(), 0xf2.toByte(), 0xbc.toByte(), 0xd0.toByte(), + 0x69.toByte(), 0xcc.toByte(), 0x0a.toByte(), 0x12.toByte(), 0xf2.toByte(), 0x33.toByte(), 0x00.toByte(), 0x8d.toByte(), 0xd3.toByte(), 0xf3.toByte(), + 0xc9.toByte(), 0x43.toByte(), 0xd0.toByte(), 0x4e.toByte(), 0x33.toByte(), 0xf4.toByte(), 0xcc.toByte(), 0x1d.toByte(), 0xe0.toByte(), 0xb7.toByte(), + 0x03.toByte(), 0xfc.toByte(), 0x4f.toByte(), 0x1d.toByte(), 0xfc.toByte(), 0x0d.toByte(), 0x7c.toByte(), 0xf8.toByte(), 0x0f.toByte(), 0x42.toByte(), + 0x08.toByte(), 0x4f.toByte(), 0x43.toByte(), 0x08.toByte(), 0x0d.toByte(), 0xc3.toByte(), 0x03.toByte(), 0x4d.toByte(), 0xdd.toByte(), 0x0b.toByte(), + 0x30.toByte(), 0xdc.toByte(), 0x03.toByte(), 0xf0.toByte(), 0x0c.toByte(), 0x00.toByte(), 0x73.toByte(), 0xfc.toByte(), 0xf3.toByte(), 0x4c.toByte(), + 0xed.toByte(), 0xf7.toByte(), 0x72.toByte(), 0x1c.toByte(), 0xf8.toByte(), 0xf2.toByte(), 0x2c.toByte(), 0x0c.toByte(), 0x37.toByte(), 0x3c.toByte(), + 0x04.toByte(), 0xcc.toByte(), 0x23.toByte(), 0xfc.toByte(), 0x0c.toByte(), 0x43.toByte(), 0x0c.toByte(), 0xce.toByte(), 0xd3.toByte(), 0x07.toByte(), + 0xc9.toByte(), 0x03.toByte(), 0x08.toByte(), 0x06.toByte(), 0xd1.toByte(), 0x0f.toByte(), 0xfc.toByte(), 0xe1.toByte(), 0x3e.toByte(), 0x0d.toByte(), + 0xcd.toByte(), 0xed.toByte(), 0xef.toByte(), 0x18.toByte(), 0x1d.toByte(), 0x4d.toByte(), 0xf0.toByte(), 0xe6.toByte(), 0x41.toByte(), 0xd6.toByte(), + 0x0c.toByte(), 0x0e.toByte(), 0xa2.toByte(), 0xbe.toByte(), 0xb1.toByte(), 0xa0.toByte(), 0xa2.toByte(), 0x4b.toByte(), 0x7f.toByte(), 0x0b.toByte(), + 0x8d.toByte(), 0x32.toByte(), 0x0c.toByte(), 0x58.toByte(), 0x0c.toByte(), 0xee.toByte(), 0x0f.toByte(), 0x0a.toByte(), 0x05.toByte(), 0xf2.toByte(), + 0xe3.toByte(), 0xff.toByte(), 0x73.toByte(), 0xfc.toByte(), 0x0b.toByte(), 0x33.toByte(), 0x3c.toByte(), 0x08.toByte(), 0x48.toByte(), 0xfc.toByte(), + 0x0b.toByte(), 0xf5.toByte(), 0x83.toByte(), 0x07.toByte(), 0xce.toByte(), 0xdd.toByte(), 0xf7.toByte(), 0x4e.toByte(), 0x1d.toByte(), 0x0c.toByte(), + 0x08.toByte(), 0x06.toByte(), 0x07.toByte(), 0x43.toByte(), 0x17.toByte(), 0x4d.toByte(), 0x03.toByte(), 0x02.toByte(), 0x07.toByte(), 0x4d.toByte(), + 0xe9.toByte(), 0xd1.toByte(), 0x0d.toByte(), 0x01.toByte(), 0x0d.toByte(), 0xfc.toByte(), 0x0d.toByte(), 0x0a.toByte(), 0x12.toByte(), 0x33.toByte(), + 0x0c.toByte(), 0x0c.toByte(), 0xf1.toByte(), 0x0c.toByte(), 0x00.toByte(), 0x71.toByte(), 0x3c.toByte(), 0xfc.toByte(), 0x4c.toByte(), 0x43.toByte(), + 0x18.toByte(), 0x33.toByte(), 0x8c.toByte(), 0x13.toByte(), 0xb3.toByte(), 0xec.toByte(), 0x0b.toByte(), 0x0d.toByte(), 0xf3.toByte(), 0x1f.toByte(), + 0xcc.toByte(), 0xec.toByte(), 0x1f.toByte(), 0xb7.toByte(), 0xd3.toByte(), 0x03.toByte(), 0x8d.toByte(), 0xac.toByte(), 0xe7.toByte(), 0xf2.toByte(), + 0x8c.toByte(), 0x04.toByte(), 0x49.toByte(), 0xe3.toByte(), 0xe3.toByte(), 0xcd.toByte(), 0x43.toByte(), 0xf4.toByte(), 0xb3.toByte(), 0x2c.toByte(), + 0xf0.toByte(), 0x31.toByte(), 0x0c.toByte(), 0x08.toByte(), 0x71.toByte(), 0x2c.toByte(), 0x04.toByte(), 0xcd.toByte(), 0x33.toByte(), 0x0c.toByte(), + 0x4d.toByte(), 0x2c.toByte(), 0xf8.toByte(), 0x32.toByte(), 0xec.toByte(), 0x0f.toByte(), 0xf3.toByte(), 0x0c.toByte(), 0x0c.toByte(), 0x73.toByte(), + 0xf3.toByte(), 0x0f.toByte(), 0x32.toByte(), 0xfc.toByte(), 0x03.toByte(), 0x0a.toByte(), 0x0c.toByte(), 0x08.toByte(), 0x30.toByte(), 0xb1.toByte(), + 0x5d.toByte(), 0x22.toByte(), 0x12.toByte(), 0x20.toByte(), 0x2c.toByte(), 0x22.toByte(), 0x1e.toByte(), 0xe1.toByte(), 0x2c.toByte(), 0x3d.toByte(), + 0xfe.toByte(), 0xd0.toByte(), 0xd8.toByte(), 0xde.toByte(), 0xe8.toByte(), 0x13.toByte(), 0x03.toByte(), 0x0d.toByte(), 0x03.toByte(), 0xce.toByte(), + 0xe1.toByte(), 0xfb.toByte(), 0xf9.toByte(), 0xa8.toByte(), 0xdf.toByte(), 0xba.toByte(), 0xae.toByte(), 0x0e.toByte(), 0x03.toByte(), 0x13.toByte(), + 0x52.toByte(), 0x3d.toByte(), 0x3c.toByte(), 0xd0.toByte(), 0xfe.toByte(), 0x97.toByte(), 0x3d.toByte(), 0x38.toByte(), 0x32.toByte(), 0xdc.toByte(), + 0x03.toByte(), 0xea.toByte(), 0xfe.toByte(), 0x9e.toByte(), 0xdd.toByte(), 0x1f.toByte(), 0xf3.toByte(), 0x23.toByte(), 0xdd.toByte(), 0x02.toByte(), + 0x3f.toByte(), 0x2d.toByte(), 0x0c.toByte(), 0x30.toByte(), 0x3d.toByte(), 0xe0.toByte(), 0x1c.toByte(), 0x0e.toByte(), 0xec.toByte(), 0x0c.toByte(), + 0xcf.toByte(), 0x00.toByte(), 0x1f.toByte(), 0x32.toByte(), 0x08.toByte(), 0x07.toByte(), 0x39.toByte(), 0xb2.toByte(), 0xd3.toByte(), 0xc3.toByte(), + 0x48.toByte(), 0x1f.toByte(), 0x41.toByte(), 0x11.toByte(), 0x52.toByte(), 0xf3.toByte(), 0x17.toByte(), 0x3d.toByte(), 0xd1.toByte(), 0x31.toByte(), + 0x4f.toByte(), 0xe3.toByte(), 0x12.toByte(), 0xff.toByte(), 0xe2.toByte(), 0x12.toByte(), 0x33.toByte(), 0xf3.toByte(), 0x33.toByte() + ) + } + + private object MockEncryptedOfflineRecordingDataMAG { + val security = PmdSecret(strategy = PmdSecret.SecurityStrategy.AES128, key = byteArrayOf(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F)) + + val securedOfflineData = byteArrayOf( + 0x02.toByte(), 0x8b.toByte(), 0xa1.toByte(), 0x99.toByte(), 0xdb.toByte(), 0x37.toByte(), 0xec.toByte(), 0x43.toByte(), 0xf8.toByte(), 0x65.toByte(), + 0xae.toByte(), 0x8a.toByte(), 0x57.toByte(), 0xc8.toByte(), 0xb5.toByte(), 0x28.toByte(), 0x1e.toByte(), 0x9d.toByte(), 0xcc.toByte(), 0x30.toByte(), + 0x86.toByte(), 0x90.toByte(), 0xf6.toByte(), 0x55.toByte(), 0xf8.toByte(), 0xbc.toByte(), 0x0b.toByte(), 0xeb.toByte(), 0x04.toByte(), 0x8b.toByte(), + 0x35.toByte(), 0xac.toByte(), 0xf8.toByte(), 0x1a.toByte(), 0xe5.toByte(), 0xc2.toByte(), 0x04.toByte(), 0xeb.toByte(), 0xc6.toByte(), 0xce.toByte(), + 0x9a.toByte(), 0x5c.toByte(), 0x0c.toByte(), 0x4e.toByte(), 0x5d.toByte(), 0x62.toByte(), 0x1b.toByte(), 0xfc.toByte(), 0x93.toByte(), 0x6e.toByte(), + 0xf1.toByte(), 0x43.toByte(), 0x90.toByte(), 0x9d.toByte(), 0x70.toByte(), 0x23.toByte(), 0x53.toByte(), 0xb1.toByte(), 0x87.toByte(), 0x4d.toByte(), + 0x70.toByte(), 0xdc.toByte(), 0xd5.toByte(), 0x6f.toByte(), 0x10.toByte(), 0x84.toByte(), 0x34.toByte(), 0xcb.toByte(), 0xb5.toByte(), 0xdd.toByte(), + 0x5a.toByte(), 0x87.toByte(), 0x20.toByte(), 0x97.toByte(), 0xc8.toByte(), 0x8c.toByte(), 0x57.toByte(), 0x42.toByte(), 0x59.toByte(), 0x94.toByte(), + 0x51.toByte(), 0x00.toByte(), 0x8a.toByte(), 0xb7.toByte(), 0x14.toByte(), 0xdd.toByte(), 0x2f.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), + 0x86.toByte(), 0xf4.toByte(), 0x04.toByte(), 0x15.toByte(), 0x06.toByte(), 0x51.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x19.toByte(), 0xf5.toByte(), + 0x07.toByte(), 0x48.toByte(), 0x36.toByte(), 0xd7.toByte(), 0xd9.toByte(), 0xf8.toByte(), 0xf6.toByte(), 0x36.toByte(), 0x27.toByte(), 0x28.toByte(), + 0xf7.toByte(), 0xf9.toByte(), 0x19.toByte(), 0x0a.toByte(), 0x39.toByte(), 0xf6.toByte(), 0x27.toByte(), 0xc4.toByte(), 0xe2.toByte(), 0xd4.toByte(), + 0x49.toByte(), 0x49.toByte(), 0xe6.toByte(), 0xc7.toByte(), 0xf5.toByte(), 0xe6.toByte(), 0x49.toByte(), 0xf8.toByte(), 0x26.toByte(), 0xbb.toByte(), + 0x08.toByte(), 0x04.toByte(), 0x55.toByte(), 0xf7.toByte(), 0x29.toByte(), 0xb8.toByte(), 0x15.toByte(), 0x06.toByte(), 0x53.toByte(), 0x18.toByte(), + 0x09.toByte(), 0xba.toByte(), 0xf4.toByte(), 0x14.toByte(), 0x39.toByte(), 0x09.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x86.toByte(), 0x19.toByte(), + 0x0e.toByte(), 0xbb.toByte(), 0x18.toByte(), 0x02.toByte(), 0x7b.toByte(), 0x27.toByte(), 0x0e.toByte(), 0xc7.toByte(), 0x19.toByte(), 0xda.toByte(), + 0x86.toByte(), 0xe6.toByte(), 0xf1.toByte(), 0x07.toByte(), 0xf7.toByte(), 0xf1.toByte(), 0x02.toByte(), 0x1e.toByte(), 0x46.toByte(), 0x0a.toByte(), + 0x39.toByte(), 0xe7.toByte(), 0x03.toByte(), 0x04.toByte(), 0x19.toByte(), 0xd8.toByte(), 0x07.toByte(), 0x26.toByte(), 0xf6.toByte(), 0x18.toByte(), + 0x04.toByte(), 0x1b.toByte(), 0x27.toByte(), 0xc4.toByte(), 0x05.toByte(), 0x14.toByte(), 0xfb.toByte(), 0xe9.toByte(), 0xc4.toByte(), 0x37.toByte(), + 0xf8.toByte(), 0x3d.toByte(), 0x14.toByte(), 0x14.toByte(), 0x26.toByte(), 0x33.toByte(), 0xe6.toByte(), 0xd4.toByte(), 0xba.toByte(), 0x3d.toByte(), + 0xe6.toByte(), 0xfb.toByte(), 0xf4.toByte(), 0x35.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x47.toByte(), 0x16.toByte(), 0x0e.toByte(), 0xbd.toByte(), + 0x26.toByte(), 0xfe.toByte(), 0x46.toByte(), 0x16.toByte(), 0xfa.toByte(), 0xf9.toByte(), 0xe6.toByte(), 0x15.toByte(), 0xca.toByte(), 0xf9.toByte(), + 0xfd.toByte(), 0xc6.toByte(), 0x3b.toByte(), 0x05.toByte(), 0x02.toByte(), 0x16.toByte(), 0x12.toByte(), 0x04.toByte(), 0x17.toByte(), 0x09.toByte(), + 0xfb.toByte(), 0x14.toByte(), 0xf7.toByte(), 0x15.toByte(), 0xd7.toByte(), 0xd8.toByte(), 0xd6.toByte(), 0x56.toByte(), 0x56.toByte(), 0x17.toByte(), + 0xba.toByte(), 0xe4.toByte(), 0x06.toByte(), 0x05.toByte(), 0x35.toByte(), 0x16.toByte(), 0x18.toByte(), 0xe8.toByte(), 0x38.toByte(), 0xf4.toByte(), + 0x00.toByte(), 0x36.toByte(), 0x04.toByte(), 0x14.toByte(), 0xe6.toByte(), 0x87.toByte(), 0xb6.toByte(), 0xd9.toByte(), 0xf9.toByte(), 0x96.toByte(), + 0xe9.toByte(), 0x87.toByte(), 0x17.toByte(), 0x0a.toByte(), 0x42.toByte(), 0xf7.toByte(), 0xf9.toByte(), 0x04.toByte(), 0xf6.toByte(), 0x05.toByte(), + 0x8f.toByte(), 0xe4.toByte(), 0x05.toByte(), 0x82.toByte(), 0xa9.toByte(), 0xc1.toByte(), 0x84.toByte(), 0xc6.toByte(), 0x05.toByte(), 0x02.toByte(), + 0x37.toByte(), 0x12.toByte(), 0xcd.toByte(), 0xb9.toByte(), 0xfd.toByte(), 0x38.toByte(), 0x89.toByte(), 0xfd.toByte(), 0x45.toByte(), 0x06.toByte(), + 0xee.toByte(), 0x46.toByte(), 0xb6.toByte(), 0xfd.toByte(), 0xbb.toByte(), 0xf6.toByte(), 0x09.toByte(), 0x0e.toByte(), 0x06.toByte(), 0xfa.toByte(), + 0xb8.toByte(), 0x99.toByte(), 0x05.toByte(), 0x84.toByte(), 0x16.toByte(), 0x02.toByte(), 0xce.toByte(), 0x06.toByte(), 0x06.toByte(), 0x79.toByte(), + 0xa6.toByte(), 0xf1.toByte(), 0xb9.toByte(), 0x09.toByte(), 0xfe.toByte(), 0x85.toByte(), 0x36.toByte(), 0x32.toByte(), 0x8e.toByte(), 0xa6.toByte(), + 0x05.toByte(), 0xfd.toByte(), 0x7b.toByte(), 0xf9.toByte(), 0x45.toByte(), 0x19.toByte(), 0xf6.toByte(), 0x04.toByte(), 0xf6.toByte(), 0x15.toByte(), + 0x86.toByte(), 0xe6.toByte(), 0xfd.toByte(), 0x43.toByte(), 0x07.toByte(), 0x02.toByte(), 0x40.toByte(), 0xa6.toByte(), 0xe5.toByte(), 0x3a.toByte(), + 0xa6.toByte(), 0x09.toByte(), 0xb9.toByte(), 0x66.toByte(), 0x0e.toByte(), 0xc4.toByte(), 0x09.toByte(), 0x02.toByte(), 0x3b.toByte(), 0xe6.toByte(), + 0xf9.toByte(), 0xb8.toByte(), 0x09.toByte(), 0x22.toByte(), 0xc9.toByte(), 0x19.toByte(), 0x06.toByte(), 0x47.toByte(), 0xeb.toByte(), 0x08.toByte(), + 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), 0x5a.toByte(), 0x6e.toByte(), 0x2c.toByte(), 0x85.toByte(), 0x2c.toByte(), 0x99.toByte(), 0x13.toByte(), + 0x0c.toByte(), 0x86.toByte(), 0xeb.toByte(), 0x04.toByte(), 0x0b.toByte(), 0x06.toByte(), 0x5b.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x19.toByte(), + 0x54.toByte(), 0x17.toByte(), 0xe6.toByte(), 0x06.toByte(), 0xf8.toByte(), 0xfb.toByte(), 0x05.toByte(), 0xd7.toByte(), 0x47.toByte(), 0x0a.toByte(), + 0x68.toByte(), 0xd6.toByte(), 0x32.toByte(), 0xe6.toByte(), 0xe7.toByte(), 0xec.toByte(), 0x14.toByte(), 0x4b.toByte(), 0x52.toByte(), 0x07.toByte(), + 0x04.toByte(), 0x9b.toByte(), 0x2a.toByte(), 0xe4.toByte(), 0x57.toByte(), 0x25.toByte(), 0x18.toByte(), 0xc7.toByte(), 0xa8.toByte(), 0x17.toByte(), + 0x37.toByte(), 0x24.toByte(), 0x05.toByte(), 0xcd.toByte(), 0x4b.toByte(), 0x18.toByte(), 0x70.toByte(), 0xb4.toByte(), 0xe9.toByte(), 0xcd.toByte(), + 0x04.toByte(), 0xf9.toByte(), 0x54.toByte(), 0x27.toByte(), 0x25.toByte(), 0xa9.toByte(), 0x0d.toByte(), 0x00.toByte(), 0x16.toByte(), 0x78.toByte(), + 0xf9.toByte(), 0xfd.toByte(), 0xfb.toByte(), 0x16.toByte(), 0x0e.toByte(), 0x07.toByte(), 0x37.toByte(), 0xfe.toByte(), 0xb9.toByte(), 0xf9.toByte(), + 0x11.toByte(), 0x47.toByte(), 0x66.toByte(), 0x02.toByte(), 0x42.toByte(), 0x86.toByte(), 0x02.toByte(), 0x79.toByte(), 0xf9.toByte(), 0xe9.toByte(), + 0x70.toByte(), 0xc9.toByte(), 0x15.toByte(), 0x46.toByte(), 0x19.toByte(), 0x02.toByte(), 0xce.toByte(), 0xe9.toByte(), 0x11.toByte(), 0x05.toByte(), + 0xa6.toByte(), 0x0d.toByte(), 0x86.toByte(), 0x08.toByte(), 0xfe.toByte(), 0x02.toByte(), 0x16.toByte(), 0x04.toByte(), 0x69.toByte(), 0x2b.toByte(), + 0xd6.toByte(), 0xa6.toByte(), 0xe8.toByte(), 0x1d.toByte(), 0x45.toByte(), 0x16.toByte(), 0x32.toByte(), 0xf9.toByte(), 0xf6.toByte(), 0xd9.toByte(), + 0xb7.toByte(), 0xf7.toByte(), 0x36.toByte(), 0x29.toByte(), 0x0b.toByte(), 0xf6.toByte(), 0xf7.toByte(), 0x24.toByte(), 0x06.toByte(), 0x16.toByte(), + 0xf4.toByte(), 0x05.toByte(), 0x0e.toByte(), 0xef.toByte(), 0xf9.toByte(), 0x66.toByte(), 0xd9.toByte(), 0xb7.toByte(), 0xe9.toByte(), 0x6c.toByte(), + 0x87.toByte(), 0x51.toByte(), 0x00.toByte(), 0x16.toByte(), 0x39.toByte(), 0xe7.toByte(), 0xf9.toByte(), 0xc7.toByte(), 0x36.toByte(), 0xf2.toByte(), + 0x3b.toByte(), 0x16.toByte(), 0xf6.toByte(), 0xfb.toByte(), 0x48.toByte(), 0x0a.toByte(), 0x06.toByte(), 0x27.toByte(), 0x26.toByte(), 0xc6.toByte(), + 0x29.toByte(), 0xf6.toByte(), 0x3a.toByte(), 0x09.toByte(), 0xfa.toByte(), 0xbb.toByte(), 0xc6.toByte(), 0x26.toByte(), 0x44.toByte(), 0x09.toByte(), + 0x06.toByte(), 0x35.toByte(), 0xe8.toByte(), 0x0d.toByte(), 0xf9.toByte(), 0x48.toByte(), 0x02.toByte(), 0xb9.toByte(), 0x16.toByte(), 0x1a.toByte(), + 0x02.toByte(), 0x26.toByte(), 0x36.toByte(), 0xe9.toByte(), 0xf4.toByte(), 0xd8.toByte(), 0x04.toByte(), 0xd6.toByte(), 0xd6.toByte(), 0x0d.toByte(), + 0x09.toByte(), 0x27.toByte(), 0x03.toByte(), 0x29.toByte(), 0x16.toByte(), 0x19.toByte(), 0x04.toByte(), 0xf9.toByte(), 0xe4.toByte(), 0x15.toByte(), + 0xf4.toByte(), 0x48.toByte(), 0x0b.toByte(), 0x46.toByte(), 0xf6.toByte(), 0xd8.toByte(), 0xda.toByte(), 0xd6.toByte(), 0x46.toByte(), 0x35.toByte(), + 0x79.toByte(), 0xe4.toByte(), 0xb4.toByte(), 0xb5.toByte(), 0xf7.toByte(), 0x7b.toByte(), 0xe9.toByte(), 0xf7.toByte(), 0xb6.toByte(), 0x29.toByte(), + 0x13.toByte(), 0x36.toByte(), 0xf7.toByte(), 0xef.toByte(), 0xc6.toByte(), 0x54.toByte(), 0x04.toByte(), 0x49.toByte(), 0xeb.toByte(), 0x3b.toByte(), + 0x00.toByte(), 0x16.toByte(), 0x46.toByte(), 0x18.toByte(), 0xf6.toByte(), 0x45.toByte(), 0x29.toByte(), 0x26.toByte(), 0x84.toByte(), 0xe6.toByte(), + 0xfd.toByte(), 0x39.toByte(), 0x08.toByte(), 0x0e.toByte(), 0x79.toByte(), 0x46.toByte(), 0x1e.toByte(), 0x87.toByte(), 0x29.toByte(), 0x06.toByte(), + 0xbf.toByte(), 0x09.toByte(), 0x02.toByte(), 0x7d.toByte(), 0x46.toByte(), 0x2e.toByte(), 0x47.toByte(), 0x36.toByte(), 0xf6.toByte(), 0x38.toByte(), + 0xeb.toByte(), 0x01.toByte(), 0x04.toByte(), 0x77.toByte(), 0x06.toByte(), 0xc4.toByte(), 0x09.toByte(), 0xfe.toByte(), 0x02.toByte(), 0x01.toByte(), + 0x7c.toByte(), 0x08.toByte(), 0x1c.toByte(), 0xe3.toByte(), 0xf4.toByte(), 0xf4.toByte(), 0xed.toByte(), 0xf8.toByte(), 0x29.toByte(), 0xe2.toByte(), + 0x06.toByte(), 0x12.toByte(), 0x07.toByte(), 0x00.toByte(), 0x8c.toByte(), 0x34.toByte(), 0x92.toByte(), 0x2b.toByte(), 0x2d.toByte(), 0x99.toByte(), + 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xf6.toByte(), 0x04.toByte(), 0x15.toByte(), 0x06.toByte(), 0x5d.toByte(), 0xf9.toByte(), 0x02.toByte(), + 0x16.toByte(), 0x07.toByte(), 0x06.toByte(), 0xea.toByte(), 0x1a.toByte(), 0x44.toByte(), 0x17.toByte(), 0x08.toByte(), 0xea.toByte(), 0x24.toByte(), + 0x04.toByte(), 0x15.toByte(), 0xf9.toByte(), 0x2d.toByte(), 0x59.toByte(), 0x09.toByte(), 0xd4.toByte(), 0xd9.toByte(), 0xf7.toByte(), 0x04.toByte(), + 0xd7.toByte(), 0x39.toByte(), 0x46.toByte(), 0x37.toByte(), 0xe7.toByte(), 0x00.toByte(), 0x16.toByte(), 0x47.toByte(), 0xf9.toByte(), 0xf9.toByte(), + 0xb9.toByte(), 0xc6.toByte(), 0x01.toByte(), 0x44.toByte(), 0x24.toByte(), 0xf6.toByte(), 0x06.toByte(), 0xb6.toByte(), 0xc9.toByte(), 0xc4.toByte(), + 0x86.toByte(), 0x06.toByte(), 0x04.toByte(), 0xc7.toByte(), 0xfd.toByte(), 0x3e.toByte(), 0xe6.toByte(), 0x0d.toByte(), 0xc6.toByte(), 0x19.toByte(), + 0xfa.toByte(), 0x42.toByte(), 0x09.toByte(), 0xfa.toByte(), 0x47.toByte(), 0xf6.toByte(), 0x01.toByte(), 0x8c.toByte(), 0x17.toByte(), 0x06.toByte(), + 0x87.toByte(), 0x99.toByte(), 0xe9.toByte(), 0x02.toByte(), 0x1e.toByte(), 0xc7.toByte(), 0x29.toByte(), 0x27.toByte(), 0x09.toByte(), 0x19.toByte(), + 0xd9.toByte(), 0x48.toByte(), 0x33.toByte(), 0x28.toByte(), 0xf7.toByte(), 0xca.toByte(), 0x29.toByte(), 0x71.toByte(), 0xe5.toByte(), 0x14.toByte(), + 0xbd.toByte(), 0x46.toByte(), 0xc7.toByte(), 0xe6.toByte(), 0xd8.toByte(), 0x1b.toByte(), 0x18.toByte(), 0x69.toByte(), 0xe2.toByte(), 0xeb.toByte(), + 0xe7.toByte(), 0x28.toByte(), 0xd9.toByte(), 0xe6.toByte(), 0xc7.toByte(), 0x14.toByte(), 0xf6.toByte(), 0x46.toByte(), 0x14.toByte(), 0x17.toByte(), + 0xf2.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x38.toByte(), 0x26.toByte(), 0xf2.toByte(), 0xfe.toByte(), 0x49.toByte(), 0x06.toByte(), 0xf9.toByte(), + 0xa6.toByte(), 0xfa.toByte(), 0x79.toByte(), 0xe6.toByte(), 0x25.toByte(), 0x46.toByte(), 0x16.toByte(), 0xf2.toByte(), 0xc6.toByte(), 0x1b.toByte(), + 0x06.toByte(), 0x02.toByte(), 0x16.toByte(), 0x09.toByte(), 0x22.toByte(), 0x0d.toByte(), 0x26.toByte(), 0xc9.toByte(), 0x25.toByte(), 0x08.toByte(), + 0x16.toByte(), 0xea.toByte(), 0xc6.toByte(), 0x19.toByte(), 0xf2.toByte(), 0x04.toByte(), 0xeb.toByte(), 0x29.toByte(), 0xf8.toByte(), 0x35.toByte(), + 0x05.toByte(), 0x24.toByte(), 0xe7.toByte(), 0x1d.toByte(), 0x01.toByte(), 0x39.toByte(), 0x06.toByte(), 0x00.toByte(), 0x16.toByte(), 0x7c.toByte(), + 0x19.toByte(), 0xea.toByte(), 0xc3.toByte(), 0x39.toByte(), 0xfe.toByte(), 0xfa.toByte(), 0xc7.toByte(), 0xf9.toByte(), 0x46.toByte(), 0x17.toByte(), + 0xd6.toByte(), 0x86.toByte(), 0xe6.toByte(), 0xf1.toByte(), 0x87.toByte(), 0x24.toByte(), 0xf2.toByte(), 0x04.toByte(), 0x06.toByte(), 0xee.toByte(), + 0xbc.toByte(), 0x66.toByte(), 0x16.toByte(), 0x84.toByte(), 0x47.toByte(), 0x0e.toByte(), 0xbd.toByte(), 0x09.toByte(), 0x0a.toByte(), 0x8a.toByte(), + 0x38.toByte(), 0x06.toByte(), 0xf8.toByte(), 0x38.toByte(), 0x0d.toByte(), 0x02.toByte(), 0x36.toByte(), 0xef.toByte(), 0xc7.toByte(), 0x06.toByte(), + 0x44.toByte(), 0x47.toByte(), 0xd7.toByte(), 0x04.toByte(), 0xe5.toByte(), 0x29.toByte(), 0x06.toByte(), 0xe7.toByte(), 0xe6.toByte(), 0x08.toByte(), + 0xdc.toByte(), 0x34.toByte(), 0x02.toByte(), 0x02.toByte(), 0x08.toByte(), 0xfd.toByte(), 0x08.toByte(), 0x16.toByte(), 0x03.toByte(), 0xf7.toByte(), + 0xd4.toByte(), 0x16.toByte(), 0x16.toByte(), 0x26.toByte(), 0xf8.toByte(), 0xe8.toByte(), 0x2b.toByte(), 0xf7.toByte(), 0x05.toByte(), 0xc4.toByte(), + 0x09.toByte(), 0x6b.toByte(), 0x06.toByte(), 0x20.toByte(), 0xe5.toByte(), 0xfb.toByte(), 0xf8.toByte(), 0xc6.toByte(), 0x26.toByte(), 0x08.toByte(), + 0x28.toByte(), 0x36.toByte(), 0x19.toByte(), 0xb4.toByte(), 0xe5.toByte(), 0x20.toByte(), 0x5a.toByte(), 0xf9.toByte(), 0x1c.toByte(), 0xa4.toByte(), + 0x0b.toByte(), 0xf9.toByte(), 0x46.toByte(), 0xe5.toByte(), 0x27.toByte(), 0x37.toByte(), 0x18.toByte(), 0xc7.toByte(), 0xe6.toByte(), 0x37.toByte(), + 0x44.toByte(), 0x26.toByte(), 0xe6.toByte(), 0xd2.toByte(), 0xd9.toByte(), 0x26.toByte(), 0x3a.toByte(), 0x16.toByte(), 0xe7.toByte(), 0x11.toByte(), + 0x07.toByte(), 0x00.toByte(), 0x4c.toByte(), 0x51.toByte(), 0xb2.toByte(), 0xce.toByte(), 0x2d.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), + 0x86.toByte(), 0xf6.toByte(), 0x04.toByte(), 0x15.toByte(), 0x06.toByte(), 0x5b.toByte(), 0xf9.toByte(), 0x00.toByte(), 0x01.toByte(), 0x06.toByte(), + 0x06.toByte(), 0x06.toByte(), 0x46.toByte(), 0x99.toByte(), 0x22.toByte(), 0xb9.toByte(), 0x36.toByte(), 0x02.toByte(), 0xb1.toByte(), 0x3a.toByte(), + 0xfe.toByte(), 0xbb.toByte(), 0xe9.toByte(), 0x11.toByte(), 0x3b.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xf7.toByte(), 0x15.toByte(), 0x19.toByte(), + 0x26.toByte(), 0x08.toByte(), 0x08.toByte(), 0xf7.toByte(), 0x02.toByte(), 0x16.toByte(), 0x26.toByte(), 0x28.toByte(), 0x17.toByte(), 0x00.toByte(), + 0x36.toByte(), 0x46.toByte(), 0xf7.toByte(), 0xe9.toByte(), 0x84.toByte(), 0xe9.toByte(), 0xe5.toByte(), 0xb9.toByte(), 0xd6.toByte(), 0x01.toByte(), + 0x84.toByte(), 0x37.toByte(), 0x02.toByte(), 0xb8.toByte(), 0x09.toByte(), 0xf6.toByte(), 0x86.toByte(), 0x59.toByte(), 0xfa.toByte(), 0x8e.toByte(), + 0x24.toByte(), 0xfe.toByte(), 0xba.toByte(), 0x89.toByte(), 0xe5.toByte(), 0xc6.toByte(), 0x26.toByte(), 0x0e.toByte(), 0x02.toByte(), 0xd7.toByte(), + 0xfd.toByte(), 0x07.toByte(), 0xa6.toByte(), 0xe9.toByte(), 0xc6.toByte(), 0xf6.toByte(), 0xfd.toByte(), 0x4e.toByte(), 0x16.toByte(), 0xf2.toByte(), + 0xf9.toByte(), 0x96.toByte(), 0xf1.toByte(), 0x38.toByte(), 0xe6.toByte(), 0xf9.toByte(), 0x41.toByte(), 0x06.toByte(), 0x06.toByte(), 0xf9.toByte(), + 0xb6.toByte(), 0x01.toByte(), 0x07.toByte(), 0x16.toByte(), 0x06.toByte(), 0xcd.toByte(), 0xd7.toByte(), 0x01.toByte(), 0x85.toByte(), 0x59.toByte(), + 0xed.toByte(), 0x44.toByte(), 0xe6.toByte(), 0x09.toByte(), 0xc5.toByte(), 0xf9.toByte(), 0x05.toByte(), 0xc7.toByte(), 0xb9.toByte(), 0xfd.toByte(), + 0x79.toByte(), 0x19.toByte(), 0x06.toByte(), 0x83.toByte(), 0xf6.toByte(), 0x09.toByte(), 0xb9.toByte(), 0x16.toByte(), 0xf2.toByte(), 0xc7.toByte(), + 0x56.toByte(), 0x06.toByte(), 0x31.toByte(), 0x06.toByte(), 0xf6.toByte(), 0xb8.toByte(), 0x59.toByte(), 0x16.toByte(), 0x44.toByte(), 0xd6.toByte(), + 0x09.toByte(), 0x41.toByte(), 0x19.toByte(), 0xfe.toByte(), 0x05.toByte(), 0x69.toByte(), 0x0d.toByte(), 0x86.toByte(), 0xe6.toByte(), 0x01.toByte(), + 0x47.toByte(), 0xc6.toByte(), 0x05.toByte(), 0x39.toByte(), 0x06.toByte(), 0xf6.toByte(), 0x47.toByte(), 0x16.toByte(), 0x06.toByte(), 0x02.toByte(), + 0x36.toByte(), 0x18.toByte(), 0x16.toByte(), 0x48.toByte(), 0x19.toByte(), 0x38.toByte(), 0xa6.toByte(), 0xe6.toByte(), 0xe5.toByte(), 0x12.toByte(), + 0x25.toByte(), 0xe9.toByte(), 0x18.toByte(), 0x01.toByte(), 0x14.toByte(), 0x26.toByte(), 0xff.toByte(), 0x06.toByte(), 0x17.toByte(), 0x08.toByte(), + 0x1b.toByte(), 0xd4.toByte(), 0x09.toByte(), 0xe9.toByte(), 0x3a.toByte(), 0xd5.toByte(), 0x17.toByte(), 0xf4.toByte(), 0x56.toByte(), 0x26.toByte(), + 0xf8.toByte(), 0xe6.toByte(), 0xc4.toByte(), 0x35.toByte(), 0x04.toByte(), 0x17.toByte(), 0xe9.toByte(), 0x0b.toByte(), 0xf8.toByte(), 0x45.toByte(), + 0x36.toByte(), 0xf4.toByte(), 0x9b.toByte(), 0xb6.toByte(), 0x2d.toByte(), 0x67.toByte(), 0x34.toByte(), 0xe7.toByte(), 0xc8.toByte(), 0x49.toByte(), + 0x15.toByte(), 0xe7.toByte(), 0xc9.toByte(), 0x18.toByte(), 0x29.toByte(), 0x0b.toByte(), 0xe6.toByte(), 0xd5.toByte(), 0xf2.toByte(), 0x47.toByte(), + 0x2b.toByte(), 0x47.toByte(), 0xf8.toByte(), 0x45.toByte(), 0xb9.toByte(), 0xd9.toByte(), 0xff.toByte(), 0x56.toByte(), 0xe2.toByte(), 0xf5.toByte(), + 0xc9.toByte(), 0x3a.toByte(), 0xe7.toByte(), 0x00.toByte(), 0x12.toByte(), 0x04.toByte(), 0xf6.toByte(), 0x05.toByte(), 0x8e.toByte(), 0x16.toByte(), + 0x02.toByte(), 0x44.toByte(), 0x99.toByte(), 0x15.toByte(), 0xb8.toByte(), 0x19.toByte(), 0x0a.toByte(), 0x00.toByte(), 0x19.toByte(), 0x0a.toByte(), + 0x78.toByte(), 0x99.toByte(), 0x0d.toByte(), 0xc7.toByte(), 0x18.toByte(), 0x0e.toByte(), 0x01.toByte(), 0x06.toByte(), 0x0e.toByte(), 0xf9.toByte(), + 0xa9.toByte(), 0x0d.toByte(), 0xc4.toByte(), 0x26.toByte(), 0x36.toByte(), 0x4a.toByte(), 0x98.toByte(), 0x01.toByte(), 0x84.toByte(), 0x2b.toByte(), + 0x05.toByte(), 0x06.toByte(), 0x39.toByte(), 0x0a.toByte(), 0x85.toByte(), 0x56.toByte(), 0x0a.toByte(), 0xba.toByte(), 0xd9.toByte(), 0xfd.toByte(), + 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), 0x18.toByte(), 0xb0.toByte(), 0x15.toByte(), 0x75.toByte(), 0x2a.toByte(), 0x99.toByte(), 0x13.toByte(), + 0x0c.toByte(), 0x86.toByte(), 0xf5.toByte(), 0x04.toByte(), 0x16.toByte(), 0x06.toByte(), 0x51.toByte(), 0xf9.toByte(), 0x00.toByte(), 0x05.toByte(), + 0x8c.toByte(), 0x16.toByte(), 0xfe.toByte(), 0x84.toByte(), 0x96.toByte(), 0x0d.toByte(), 0x07.toByte(), 0x02.toByte(), 0x2e.toByte(), 0xf5.toByte(), + 0xf2.toByte(), 0x19.toByte(), 0xba.toByte(), 0xd9.toByte(), 0xf9.toByte(), 0x29.toByte(), 0x4a.toByte(), 0xdb.toByte(), 0x5b.toByte(), 0xd2.toByte(), + 0x66.toByte(), 0xd2.toByte(), 0x68.toByte(), 0xb7.toByte(), 0xf6.toByte(), 0xb9.toByte(), 0x55.toByte(), 0x0b.toByte(), 0x59.toByte(), 0xa8.toByte(), + 0x46.toByte(), 0xa6.toByte(), 0x16.toByte(), 0xd6.toByte(), 0x27.toByte(), 0x39.toByte(), 0x13.toByte(), 0xbb.toByte(), 0xf7.toByte(), 0xf8.toByte(), + 0x34.toByte(), 0xf8.toByte(), 0x36.toByte(), 0x07.toByte(), 0x35.toByte(), 0xe6.toByte(), 0x19.toByte(), 0xe7.toByte(), 0x04.toByte(), 0xe8.toByte(), + 0x26.toByte(), 0xfb.toByte(), 0x22.toByte(), 0xf9.toByte(), 0x26.toByte(), 0xb8.toByte(), 0xf8.toByte(), 0xd7.toByte(), 0x24.toByte(), 0x35.toByte(), + 0xf9.toByte(), 0x08.toByte(), 0xc9.toByte(), 0x15.toByte(), 0x08.toByte(), 0x26.toByte(), 0x48.toByte(), 0x25.toByte(), 0x26.toByte(), 0x00.toByte(), + 0x0e.toByte(), 0x7b.toByte(), 0xe9.toByte(), 0xfd.toByte(), 0x44.toByte(), 0xc6.toByte(), 0x26.toByte(), 0x05.toByte(), 0x16.toByte(), 0xfa.toByte(), + 0x70.toByte(), 0xe8.toByte(), 0x01.toByte(), 0x7b.toByte(), 0x09.toByte(), 0x02.toByte(), 0x46.toByte(), 0x36.toByte(), 0x0a.toByte(), 0x02.toByte(), + 0x0e.toByte(), 0xd5.toByte(), 0xd6.toByte(), 0xf7.toByte(), 0xf7.toByte(), 0x32.toByte(), 0x29.toByte(), 0xda.toByte(), 0xfd.toByte(), 0xc8.toByte(), + 0x22.toByte(), 0xe8.toByte(), 0xf9.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x7b.toByte(), 0x06.toByte(), 0x02.toByte(), 0x05.toByte(), 0xc7.toByte(), + 0x2e.toByte(), 0x84.toByte(), 0xf9.toByte(), 0x05.toByte(), 0xf2.toByte(), 0xfa.toByte(), 0x09.toByte(), 0x83.toByte(), 0x26.toByte(), 0x1e.toByte(), + 0x7b.toByte(), 0xb7.toByte(), 0xf5.toByte(), 0x02.toByte(), 0x1e.toByte(), 0xbc.toByte(), 0xe4.toByte(), 0x07.toByte(), 0xf2.toByte(), 0xf4.toByte(), + 0x19.toByte(), 0x18.toByte(), 0xf8.toByte(), 0x15.toByte(), 0xf5.toByte(), 0x03.toByte(), 0xf7.toByte(), 0xf8.toByte(), 0xf8.toByte(), 0xf9.toByte(), + 0x58.toByte(), 0x48.toByte(), 0x29.toByte(), 0xc7.toByte(), 0x05.toByte(), 0xf8.toByte(), 0x5b.toByte(), 0x78.toByte(), 0x00.toByte(), 0xf7.toByte(), + 0x96.toByte(), 0xea.toByte(), 0x06.toByte(), 0x09.toByte(), 0x3a.toByte(), 0xe6.toByte(), 0x04.toByte(), 0xc0.toByte(), 0x66.toByte(), 0x19.toByte(), + 0x2b.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xb9.toByte(), 0xf8.toByte(), 0x05.toByte(), 0x39.toByte(), 0xe6.toByte(), 0x21.toByte(), 0x02.toByte(), + 0x16.toByte(), 0xfa.toByte(), 0xc5.toByte(), 0xdb.toByte(), 0x0d.toByte(), 0xc6.toByte(), 0xf9.toByte(), 0x11.toByte(), 0x84.toByte(), 0x19.toByte(), + 0xfa.toByte(), 0x02.toByte(), 0x16.toByte(), 0xb6.toByte(), 0x09.toByte(), 0x69.toByte(), 0x54.toByte(), 0xf4.toByte(), 0xc8.toByte(), 0xb9.toByte(), + 0x16.toByte(), 0xe4.toByte(), 0x57.toByte(), 0x08.toByte(), 0x1a.toByte(), 0xc8.toByte(), 0xf6.toByte(), 0x09.toByte(), 0x65.toByte(), 0x16.toByte(), + 0x05.toByte(), 0xaa.toByte(), 0x04.toByte(), 0xf5.toByte(), 0x33.toByte(), 0xf4.toByte(), 0x16.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x3d.toByte(), + 0x09.toByte(), 0xfe.toByte(), 0x3a.toByte(), 0xb6.toByte(), 0x02.toByte(), 0xba.toByte(), 0x37.toByte(), 0x06.toByte(), 0xb1.toByte(), 0x54.toByte(), + 0xea.toByte(), 0xf8.toByte(), 0x69.toByte(), 0xe6.toByte(), 0xbb.toByte(), 0x06.toByte(), 0x0a.toByte(), 0x02.toByte(), 0x13.toByte(), 0xfb.toByte(), + 0x04.toByte(), 0xf4.toByte(), 0xf5.toByte(), 0xfb.toByte(), 0x06.toByte(), 0xef.toByte(), 0x39.toByte(), 0x38.toByte(), 0x47.toByte(), 0x05.toByte(), + 0xc4.toByte(), 0xd9.toByte(), 0xeb.toByte(), 0x3a.toByte(), 0x07.toByte(), 0x14.toByte(), 0xe4.toByte(), 0x39.toByte(), 0x0b.toByte(), 0xe2.toByte(), + 0xd9.toByte(), 0xe4.toByte(), 0x18.toByte(), 0xf8.toByte(), 0x26.toByte(), 0x26.toByte(), 0x13.toByte(), 0x17.toByte(), 0xd6.toByte(), 0xea.toByte(), + 0x07.toByte(), 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), 0x26.toByte(), 0xb9.toByte(), 0xdf.toByte(), 0x0d.toByte(), 0x2b.toByte(), 0x99.toByte(), + 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xe9.toByte(), 0x04.toByte(), 0x16.toByte(), 0x06.toByte(), 0x5c.toByte(), 0xf9.toByte(), 0x05.toByte(), + 0x04.toByte(), 0xcf.toByte(), 0xa7.toByte(), 0x05.toByte(), 0x00.toByte(), 0x1e.toByte(), 0xc2.toByte(), 0x16.toByte(), 0x02.toByte(), 0x79.toByte(), + 0xb6.toByte(), 0xf1.toByte(), 0x44.toByte(), 0x19.toByte(), 0x0e.toByte(), 0xca.toByte(), 0xd6.toByte(), 0x01.toByte(), 0x47.toByte(), 0x56.toByte(), + 0xf1.toByte(), 0x45.toByte(), 0xb9.toByte(), 0xfd.toByte(), 0x46.toByte(), 0xd6.toByte(), 0x01.toByte(), 0x07.toByte(), 0x86.toByte(), 0x06.toByte(), + 0x44.toByte(), 0x06.toByte(), 0x02.toByte(), 0x7c.toByte(), 0xe6.toByte(), 0xfd.toByte(), 0x85.toByte(), 0xe9.toByte(), 0xfd.toByte(), 0x87.toByte(), + 0x14.toByte(), 0x06.toByte(), 0x06.toByte(), 0x06.toByte(), 0xd6.toByte(), 0xb8.toByte(), 0x06.toByte(), 0xfe.toByte(), 0x86.toByte(), 0x06.toByte(), + 0x06.toByte(), 0x85.toByte(), 0xd6.toByte(), 0x05.toByte(), 0x7a.toByte(), 0xe6.toByte(), 0xf1.toByte(), 0x07.toByte(), 0x57.toByte(), 0xfa.toByte(), + 0x02.toByte(), 0x16.toByte(), 0x22.toByte(), 0xb6.toByte(), 0xf9.toByte(), 0x39.toByte(), 0x17.toByte(), 0x59.toByte(), 0xeb.toByte(), 0x1b.toByte(), + 0xe5.toByte(), 0xe8.toByte(), 0xa7.toByte(), 0xeb.toByte(), 0x33.toByte(), 0x65.toByte(), 0xe5.toByte(), 0xf9.toByte(), 0x09.toByte(), 0x48.toByte(), + 0xd9.toByte(), 0xf6.toByte(), 0xd9.toByte(), 0x49.toByte(), 0x09.toByte(), 0x02.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x86.toByte(), 0xf9.toByte(), + 0x2d.toByte(), 0xca.toByte(), 0x16.toByte(), 0x06.toByte(), 0x04.toByte(), 0x58.toByte(), 0xf9.toByte(), 0x38.toByte(), 0x26.toByte(), 0x06.toByte(), + 0xf8.toByte(), 0x59.toByte(), 0x06.toByte(), 0x79.toByte(), 0x46.toByte(), 0xfa.toByte(), 0x02.toByte(), 0x16.toByte(), 0x07.toByte(), 0x18.toByte(), + 0x17.toByte(), 0xfa.toByte(), 0xc4.toByte(), 0x17.toByte(), 0xf7.toByte(), 0xe6.toByte(), 0xfb.toByte(), 0xe9.toByte(), 0x38.toByte(), 0xd4.toByte(), + 0xf4.toByte(), 0xf7.toByte(), 0x1a.toByte(), 0x56.toByte(), 0x17.toByte(), 0xf0.toByte(), 0xc9.toByte(), 0x17.toByte(), 0x38.toByte(), 0x47.toByte(), + 0xf6.toByte(), 0xd7.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x38.toByte(), 0x09.toByte(), 0x0e.toByte(), 0xfa.toByte(), 0x59.toByte(), 0x0e.toByte(), + 0xb9.toByte(), 0x46.toByte(), 0x02.toByte(), 0xcc.toByte(), 0x09.toByte(), 0xea.toByte(), 0x3b.toByte(), 0x46.toByte(), 0xf9.toByte(), 0x39.toByte(), + 0x37.toByte(), 0x0e.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x0b.toByte(), 0xb6.toByte(), 0xf8.toByte(), 0xc4.toByte(), 0x74.toByte(), 0xe6.toByte(), + 0x19.toByte(), 0xa6.toByte(), 0x45.toByte(), 0x38.toByte(), 0x58.toByte(), 0xdb.toByte(), 0x00.toByte(), 0x16.toByte(), 0x7b.toByte(), 0x26.toByte(), + 0xf2.toByte(), 0x06.toByte(), 0x16.toByte(), 0xf2.toByte(), 0x84.toByte(), 0x27.toByte(), 0x02.toByte(), 0x04.toByte(), 0x06.toByte(), 0xe6.toByte(), + 0x78.toByte(), 0x16.toByte(), 0xfa.toByte(), 0xb9.toByte(), 0x17.toByte(), 0x0e.toByte(), 0x07.toByte(), 0x17.toByte(), 0x12.toByte(), 0x79.toByte(), + 0xb9.toByte(), 0x01.toByte(), 0x38.toByte(), 0x28.toByte(), 0xf6.toByte(), 0x82.toByte(), 0xf8.toByte(), 0x0d.toByte(), 0xba.toByte(), 0xb7.toByte(), + 0x01.toByte(), 0x07.toByte(), 0x26.toByte(), 0xfe.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x34.toByte(), 0x06.toByte(), 0x39.toByte(), 0xe7.toByte(), + 0xf9.toByte(), 0xc9.toByte(), 0x36.toByte(), 0x00.toByte(), 0x35.toByte(), 0xe6.toByte(), 0xdd.toByte(), 0xd9.toByte(), 0x00.toByte(), 0x0e.toByte(), + 0x82.toByte(), 0x09.toByte(), 0x22.toByte(), 0x8d.toByte(), 0xe6.toByte(), 0x15.toByte(), 0x47.toByte(), 0xa8.toByte(), 0x0d.toByte(), 0x04.toByte(), + 0x29.toByte(), 0xfa.toByte(), 0xba.toByte(), 0xd9.toByte(), 0x0d.toByte(), 0xf9.toByte(), 0xf9.toByte(), 0x05.toByte(), 0x05.toByte(), 0x0e.toByte(), + 0x2d.toByte(), 0x88.toByte(), 0x7c.toByte(), 0x03.toByte(), 0xa6.toByte(), 0xed.toByte(), 0x7c.toByte(), 0x72.toByte(), 0x66.toByte(), 0x02.toByte(), + 0x01.toByte(), 0xd9.toByte(), 0x36.toByte(), 0x04.toByte(), 0x11.toByte(), 0xcb.toByte(), 0xfa.toByte(), 0x2d.toByte(), 0x09.toByte(), 0x47.toByte(), + 0x60.toByte(), 0x03.toByte(), 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), 0x08.toByte(), 0x2c.toByte(), 0xc9.toByte(), 0xa3.toByte(), 0x2b.toByte(), + 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xf3.toByte(), 0x04.toByte(), 0x1a.toByte(), 0x06.toByte(), 0x5a.toByte(), 0xf9.toByte(), + 0x00.toByte(), 0x0e.toByte(), 0xba.toByte(), 0xd8.toByte(), 0x01.toByte(), 0xc4.toByte(), 0xf9.toByte(), 0xe9.toByte(), 0x44.toByte(), 0x09.toByte(), + 0x06.toByte(), 0x45.toByte(), 0xf4.toByte(), 0x01.toByte(), 0x3b.toByte(), 0xd7.toByte(), 0xe9.toByte(), 0xc5.toByte(), 0x19.toByte(), 0x06.toByte(), + 0x02.toByte(), 0x16.toByte(), 0xd7.toByte(), 0x38.toByte(), 0xb7.toByte(), 0xf9.toByte(), 0xd8.toByte(), 0x37.toByte(), 0x61.toByte(), 0x17.toByte(), + 0x36.toByte(), 0xcf.toByte(), 0x06.toByte(), 0x29.toByte(), 0x24.toByte(), 0x16.toByte(), 0xd9.toByte(), 0xe8.toByte(), 0x08.toByte(), 0x06.toByte(), + 0x56.toByte(), 0xe6.toByte(), 0x0b.toByte(), 0xb6.toByte(), 0x24.toByte(), 0xf7.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x47.toByte(), 0xf6.toByte(), + 0x05.toByte(), 0x39.toByte(), 0x56.toByte(), 0xfe.toByte(), 0xf8.toByte(), 0x08.toByte(), 0x02.toByte(), 0xb8.toByte(), 0x17.toByte(), 0x22.toByte(), + 0x41.toByte(), 0xe6.toByte(), 0xe9.toByte(), 0xbb.toByte(), 0xeb.toByte(), 0x0d.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x22.toByte(), 0x57.toByte(), + 0xf8.toByte(), 0xac.toByte(), 0xe6.toByte(), 0xd9.toByte(), 0x70.toByte(), 0xf5.toByte(), 0x07.toByte(), 0xca.toByte(), 0xd7.toByte(), 0xf4.toByte(), + 0x00.toByte(), 0x16.toByte(), 0x84.toByte(), 0xd9.toByte(), 0x0d.toByte(), 0x79.toByte(), 0x06.toByte(), 0x02.toByte(), 0x47.toByte(), 0xe9.toByte(), + 0x01.toByte(), 0x8f.toByte(), 0x26.toByte(), 0x0e.toByte(), 0xf9.toByte(), 0x88.toByte(), 0xf1.toByte(), 0x07.toByte(), 0x46.toByte(), 0x0a.toByte(), + 0x01.toByte(), 0xe7.toByte(), 0xfd.toByte(), 0x3b.toByte(), 0xc7.toByte(), 0xf9.toByte(), 0x47.toByte(), 0x64.toByte(), 0xfa.toByte(), 0x05.toByte(), + 0xf6.toByte(), 0xed.toByte(), 0xbe.toByte(), 0x39.toByte(), 0x06.toByte(), 0x46.toByte(), 0x39.toByte(), 0x06.toByte(), 0x02.toByte(), 0x0e.toByte(), + 0xfd.toByte(), 0x06.toByte(), 0x19.toByte(), 0x3a.toByte(), 0x06.toByte(), 0xd7.toByte(), 0x12.toByte(), 0x15.toByte(), 0x1b.toByte(), 0xe8.toByte(), + 0xd8.toByte(), 0xf9.toByte(), 0x00.toByte(), 0x1e.toByte(), 0x87.toByte(), 0xc9.toByte(), 0x09.toByte(), 0x84.toByte(), 0xe7.toByte(), 0x09.toByte(), + 0x44.toByte(), 0xb5.toByte(), 0xfe.toByte(), 0x45.toByte(), 0x09.toByte(), 0xc2.toByte(), 0x7d.toByte(), 0xc9.toByte(), 0x01.toByte(), 0xb8.toByte(), + 0x96.toByte(), 0x09.toByte(), 0x38.toByte(), 0x16.toByte(), 0x02.toByte(), 0x03.toByte(), 0x26.toByte(), 0x02.toByte(), 0x06.toByte(), 0xf6.toByte(), + 0x0d.toByte(), 0x39.toByte(), 0x36.toByte(), 0x22.toByte(), 0x86.toByte(), 0x18.toByte(), 0x06.toByte(), 0x38.toByte(), 0xf9.toByte(), 0x0d.toByte(), + 0x01.toByte(), 0x06.toByte(), 0xe6.toByte(), 0xc7.toByte(), 0xa6.toByte(), 0xf9.toByte(), 0x04.toByte(), 0x07.toByte(), 0xf2.toByte(), 0xf9.toByte(), + 0xe9.toByte(), 0x09.toByte(), 0xfa.toByte(), 0x09.toByte(), 0x06.toByte(), 0x47.toByte(), 0x29.toByte(), 0x0e.toByte(), 0x02.toByte(), 0x26.toByte(), + 0x17.toByte(), 0x26.toByte(), 0x09.toByte(), 0x27.toByte(), 0x17.toByte(), 0x19.toByte(), 0xd6.toByte(), 0xa8.toByte(), 0x06.toByte(), 0xfb.toByte(), + 0x29.toByte(), 0x04.toByte(), 0x04.toByte(), 0x19.toByte(), 0x06.toByte(), 0x38.toByte(), 0x07.toByte(), 0x26.toByte(), 0xd5.toByte(), 0x29.toByte(), + 0xc4.toByte(), 0x48.toByte(), 0xf7.toByte(), 0x3a.toByte(), 0x27.toByte(), 0x06.toByte(), 0xf4.toByte(), 0xb9.toByte(), 0xc8.toByte(), 0x2d.toByte(), + 0xd4.toByte(), 0x27.toByte(), 0xe4.toByte(), 0x48.toByte(), 0xf4.toByte(), 0x02.toByte(), 0x06.toByte(), 0x48.toByte(), 0x13.toByte(), 0xd9.toByte(), + 0xc4.toByte(), 0x1a.toByte(), 0x22.toByte(), 0xfb.toByte(), 0xe0.toByte(), 0x09.toByte(), 0xe2.toByte(), 0x2f.toByte(), 0x00.toByte(), 0x0c.toByte(), + 0x79.toByte(), 0xb5.toByte(), 0x15.toByte(), 0xc0.toByte(), 0x19.toByte(), 0xc6.toByte(), 0x47.toByte(), 0x99.toByte(), 0x0d.toByte(), 0xb9.toByte(), + 0x19.toByte(), 0x0e.toByte(), 0x05.toByte(), 0x06.toByte(), 0x12.toByte(), 0xb9.toByte(), 0xe6.toByte(), 0xf9.toByte(), 0x06.toByte(), 0x06.toByte(), + 0xf2.toByte(), 0x01.toByte(), 0x06.toByte(), 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), 0xe0.toByte(), 0x80.toByte(), 0xbe.toByte(), 0x3e.toByte(), + 0x28.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xeb.toByte(), 0x04.toByte(), 0x1f.toByte(), 0x06.toByte(), 0x5d.toByte(), + 0xf9.toByte(), 0x00.toByte(), 0x0b.toByte(), 0x07.toByte(), 0xc8.toByte(), 0x09.toByte(), 0xb8.toByte(), 0x16.toByte(), 0x16.toByte(), 0x46.toByte(), + 0x19.toByte(), 0x02.toByte(), 0xf9.toByte(), 0x28.toByte(), 0x3a.toByte(), 0xc6.toByte(), 0x79.toByte(), 0x02.toByte(), 0x86.toByte(), 0x0b.toByte(), + 0xfe.toByte(), 0x31.toByte(), 0xe6.toByte(), 0xf5.toByte(), 0x47.toByte(), 0x56.toByte(), 0x02.toByte(), 0x84.toByte(), 0xe6.toByte(), 0xf9.toByte(), + 0x7b.toByte(), 0x16.toByte(), 0x06.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x09.toByte(), 0x56.toByte(), 0xf9.toByte(), 0x3b.toByte(), 0xe8.toByte(), + 0x04.toByte(), 0xf9.toByte(), 0x44.toByte(), 0x28.toByte(), 0xf3.toByte(), 0xa7.toByte(), 0xe4.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xc2.toByte(), + 0xf4.toByte(), 0x01.toByte(), 0xc7.toByte(), 0xc9.toByte(), 0xe1.toByte(), 0xb9.toByte(), 0xd9.toByte(), 0x09.toByte(), 0x39.toByte(), 0x09.toByte(), + 0xf6.toByte(), 0x07.toByte(), 0x06.toByte(), 0x0e.toByte(), 0x47.toByte(), 0xf7.toByte(), 0x0d.toByte(), 0x02.toByte(), 0x16.toByte(), 0xeb.toByte(), + 0x08.toByte(), 0x09.toByte(), 0x17.toByte(), 0xf2.toByte(), 0xe4.toByte(), 0xc4.toByte(), 0xed.toByte(), 0x38.toByte(), 0x1b.toByte(), 0x17.toByte(), + 0xd5.toByte(), 0x65.toByte(), 0x17.toByte(), 0xf9.toByte(), 0xe9.toByte(), 0x22.toByte(), 0xf7.toByte(), 0xc7.toByte(), 0x06.toByte(), 0x17.toByte(), + 0x09.toByte(), 0x08.toByte(), 0x04.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x78.toByte(), 0x06.toByte(), 0x0a.toByte(), 0x44.toByte(), 0x26.toByte(), + 0xf2.toByte(), 0x06.toByte(), 0xc4.toByte(), 0xf1.toByte(), 0x7a.toByte(), 0x27.toByte(), 0xde.toByte(), 0x78.toByte(), 0x26.toByte(), 0xfe.toByte(), + 0x06.toByte(), 0x09.toByte(), 0x06.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xd7.toByte(), 0x16.toByte(), 0xf8.toByte(), 0x2b.toByte(), 0x19.toByte(), + 0x07.toByte(), 0xd5.toByte(), 0x37.toByte(), 0xe6.toByte(), 0x18.toByte(), 0x06.toByte(), 0x02.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x07.toByte(), + 0xf6.toByte(), 0xf9.toByte(), 0xbb.toByte(), 0xf6.toByte(), 0x01.toByte(), 0xbb.toByte(), 0x59.toByte(), 0x02.toByte(), 0x0f.toByte(), 0xf6.toByte(), + 0x1d.toByte(), 0xf9.toByte(), 0x99.toByte(), 0x0d.toByte(), 0x43.toByte(), 0xe8.toByte(), 0x05.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xc6.toByte(), + 0x16.toByte(), 0x08.toByte(), 0x67.toByte(), 0xcb.toByte(), 0x25.toByte(), 0xb2.toByte(), 0x28.toByte(), 0xf8.toByte(), 0x21.toByte(), 0xf9.toByte(), + 0x16.toByte(), 0x00.toByte(), 0x1e.toByte(), 0xff.toByte(), 0x49.toByte(), 0x06.toByte(), 0x47.toByte(), 0x86.toByte(), 0x15.toByte(), 0x78.toByte(), + 0x74.toByte(), 0xf6.toByte(), 0x44.toByte(), 0x18.toByte(), 0xe6.toByte(), 0xf1.toByte(), 0x26.toByte(), 0x12.toByte(), 0x86.toByte(), 0x26.toByte(), + 0xfe.toByte(), 0x84.toByte(), 0x19.toByte(), 0xfe.toByte(), 0xb8.toByte(), 0x16.toByte(), 0x02.toByte(), 0x79.toByte(), 0x06.toByte(), 0x02.toByte(), + 0xba.toByte(), 0x39.toByte(), 0xfa.toByte(), 0xfb.toByte(), 0xa9.toByte(), 0x16.toByte(), 0x38.toByte(), 0x27.toByte(), 0xf2.toByte(), 0x73.toByte(), + 0xd9.toByte(), 0x0d.toByte(), 0x47.toByte(), 0x19.toByte(), 0x0a.toByte(), 0xc5.toByte(), 0x19.toByte(), 0x12.toByte(), 0x86.toByte(), 0x18.toByte(), + 0xf6.toByte(), 0xf8.toByte(), 0x19.toByte(), 0x0e.toByte(), 0xb9.toByte(), 0xf6.toByte(), 0x01.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x0a.toByte(), + 0x17.toByte(), 0xd5.toByte(), 0xe0.toByte(), 0x27.toByte(), 0x08.toByte(), 0x2a.toByte(), 0xfb.toByte(), 0x35.toByte(), 0x02.toByte(), 0xf5.toByte(), + 0xe8.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xfc.toByte(), 0xf9.toByte(), 0x01.toByte(), 0xf9.toByte(), 0x79.toByte(), 0x0e.toByte(), 0x39.toByte(), + 0xb6.toByte(), 0x06.toByte(), 0x3f.toByte(), 0x46.toByte(), 0xfe.toByte(), 0x70.toByte(), 0x76.toByte(), 0xfa.toByte(), 0x38.toByte(), 0x16.toByte(), + 0xf2.toByte(), 0x02.toByte(), 0x01.toByte(), 0x2d.toByte(), 0x24.toByte(), 0xdd.toByte(), 0xe5.toByte(), 0x29.toByte(), 0x36.toByte(), 0x28.toByte(), + 0xb8.toByte(), 0xe6.toByte(), 0xf7.toByte(), 0x07.toByte(), 0x10.toByte(), 0x07.toByte(), 0x00.toByte(), 0xf0.toByte(), 0x58.toByte(), 0x0d.toByte(), + 0xd3.toByte(), 0x28.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xeb.toByte(), 0x04.toByte(), 0x0a.toByte(), 0x06.toByte(), + 0x5d.toByte(), 0xf9.toByte(), 0x00.toByte(), 0x16.toByte(), 0x07.toByte(), 0x17.toByte(), 0x22.toByte(), 0x04.toByte(), 0xc9.toByte(), 0xf1.toByte(), + 0x46.toByte(), 0xc9.toByte(), 0x05.toByte(), 0xc6.toByte(), 0x16.toByte(), 0xf2.toByte(), 0x85.toByte(), 0x06.toByte(), 0xfe.toByte(), 0x7b.toByte(), + 0xe6.toByte(), 0x0d.toByte(), 0x0a.toByte(), 0x15.toByte(), 0xfa.toByte(), 0x3b.toByte(), 0x46.toByte(), 0xdd.toByte(), 0xbd.toByte(), 0x29.toByte(), + 0x0a.toByte(), 0x04.toByte(), 0x46.toByte(), 0x0a.toByte(), 0xb9.toByte(), 0x19.toByte(), 0xfa.toByte(), 0xc7.toByte(), 0x06.toByte(), 0xfe.toByte(), + 0x02.toByte(), 0x2e.toByte(), 0x08.toByte(), 0xd9.toByte(), 0x22.toByte(), 0x16.toByte(), 0x26.toByte(), 0xe6.toByte(), 0xe9.toByte(), 0x04.toByte(), + 0x0a.toByte(), 0x46.toByte(), 0xd9.toByte(), 0xf7.toByte(), 0xd4.toByte(), 0x56.toByte(), 0xf4.toByte(), 0x1b.toByte(), 0x16.toByte(), 0xf9.toByte(), + 0xf4.toByte(), 0xd7.toByte(), 0x17.toByte(), 0xf7.toByte(), 0xf7.toByte(), 0x18.toByte(), 0x27.toByte(), 0x49.toByte(), 0xfb.toByte(), 0xea.toByte(), + 0xa7.toByte(), 0x22.toByte(), 0x25.toByte(), 0x2b.toByte(), 0x1b.toByte(), 0x2d.toByte(), 0xf6.toByte(), 0xe3.toByte(), 0xd2.toByte(), 0x07.toByte(), + 0x1d.toByte(), 0x0b.toByte(), 0x24.toByte(), 0xc4.toByte(), 0x15.toByte(), 0x39.toByte(), 0x38.toByte(), 0x29.toByte(), 0xc4.toByte(), 0x03.toByte(), + 0x09.toByte(), 0x18.toByte(), 0x0b.toByte(), 0xe4.toByte(), 0xf7.toByte(), 0xf4.toByte(), 0x1b.toByte(), 0x29.toByte(), 0x38.toByte(), 0xf2.toByte(), + 0xd9.toByte(), 0xe1.toByte(), 0x00.toByte(), 0x26.toByte(), 0x07.toByte(), 0x14.toByte(), 0x02.toByte(), 0x7f.toByte(), 0x19.toByte(), 0xe6.toByte(), + 0x39.toByte(), 0x17.toByte(), 0x0e.toByte(), 0x39.toByte(), 0x36.toByte(), 0xf2.toByte(), 0x7a.toByte(), 0xd6.toByte(), 0x05.toByte(), 0x79.toByte(), + 0x56.toByte(), 0x06.toByte(), 0x07.toByte(), 0xf6.toByte(), 0xfd.toByte(), 0xbd.toByte(), 0x16.toByte(), 0xfa.toByte(), 0x78.toByte(), 0x49.toByte(), + 0xfe.toByte(), 0x39.toByte(), 0x26.toByte(), 0x0a.toByte(), 0xb8.toByte(), 0x49.toByte(), 0x06.toByte(), 0x39.toByte(), 0x15.toByte(), 0x0e.toByte(), + 0xc5.toByte(), 0xf4.toByte(), 0xc9.toByte(), 0xf8.toByte(), 0x99.toByte(), 0xd5.toByte(), 0x05.toByte(), 0xf4.toByte(), 0x05.toByte(), 0x46.toByte(), + 0x06.toByte(), 0xe6.toByte(), 0x07.toByte(), 0xd6.toByte(), 0x09.toByte(), 0xf9.toByte(), 0x06.toByte(), 0xfa.toByte(), 0x83.toByte(), 0x09.toByte(), + 0xfe.toByte(), 0x46.toByte(), 0xd6.toByte(), 0x05.toByte(), 0x39.toByte(), 0x14.toByte(), 0xfe.toByte(), 0x45.toByte(), 0x26.toByte(), 0xde.toByte(), + 0x7b.toByte(), 0xe6.toByte(), 0xf9.toByte(), 0x78.toByte(), 0x46.toByte(), 0x06.toByte(), 0x02.toByte(), 0x16.toByte(), 0x37.toByte(), 0xd7.toByte(), + 0xfa.toByte(), 0xe6.toByte(), 0x29.toByte(), 0x22.toByte(), 0x29.toByte(), 0x16.toByte(), 0x18.toByte(), 0xb6.toByte(), 0xd4.toByte(), 0x26.toByte(), + 0x04.toByte(), 0xfd.toByte(), 0xe7.toByte(), 0x07.toByte(), 0x53.toByte(), 0x04.toByte(), 0x29.toByte(), 0x0b.toByte(), 0xe8.toByte(), 0xf9.toByte(), + 0xf7.toByte(), 0x29.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x0f.toByte(), 0x15.toByte(), 0xfa.toByte(), 0x87.toByte(), 0x79.toByte(), 0xd5.toByte(), + 0x05.toByte(), 0xf6.toByte(), 0xfd.toByte(), 0xc4.toByte(), 0xc6.toByte(), 0x0d.toByte(), 0x86.toByte(), 0x16.toByte(), 0xfe.toByte(), 0x46.toByte(), + 0x29.toByte(), 0x06.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x08.toByte(), 0xf9.toByte(), 0xf9.toByte(), 0x18.toByte(), 0x13.toByte(), 0x46.toByte(), + 0x03.toByte(), 0x1a.toByte(), 0xb8.toByte(), 0x0a.toByte(), 0xe4.toByte(), 0x57.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x87.toByte(), 0xf7.toByte(), + 0x05.toByte(), 0x07.toByte(), 0x06.toByte(), 0xda.toByte(), 0x86.toByte(), 0x09.toByte(), 0xf6.toByte(), 0x42.toByte(), 0x26.toByte(), 0x16.toByte(), + 0xc7.toByte(), 0xc9.toByte(), 0x15.toByte(), 0xb9.toByte(), 0xc9.toByte(), 0x01.toByte(), 0x02.toByte(), 0x02.toByte(), 0x02.toByte(), 0xe9.toByte(), + 0x22.toByte(), 0xc9.toByte(), 0x37.toByte(), 0x29.toByte(), 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), 0x06.toByte(), 0x82.toByte(), 0x78.toByte(), + 0x6d.toByte(), 0x29.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xf7.toByte(), 0x04.toByte(), 0x08.toByte(), 0x06.toByte(), + 0x5a.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x05.toByte(), 0xfa.toByte(), 0x0a.toByte(), 0x16.toByte(), 0x05.toByte(), 0x06.toByte(), 0x00.toByte(), + 0x36.toByte(), 0xc2.toByte(), 0x27.toByte(), 0x02.toByte(), 0x06.toByte(), 0xb6.toByte(), 0xe1.toByte(), 0x87.toByte(), 0x19.toByte(), 0xfe.toByte(), + 0x84.toByte(), 0xf7.toByte(), 0x2d.toByte(), 0x8c.toByte(), 0xc6.toByte(), 0xed.toByte(), 0x06.toByte(), 0x68.toByte(), 0xfd.toByte(), 0x83.toByte(), + 0xe7.toByte(), 0x0d.toByte(), 0x79.toByte(), 0xe6.toByte(), 0xd9.toByte(), 0xc5.toByte(), 0xe9.toByte(), 0xf9.toByte(), 0xc4.toByte(), 0xd7.toByte(), + 0xfd.toByte(), 0x06.toByte(), 0xe6.toByte(), 0xf1.toByte(), 0x85.toByte(), 0x29.toByte(), 0xfa.toByte(), 0x06.toByte(), 0xd7.toByte(), 0x15.toByte(), + 0xba.toByte(), 0x16.toByte(), 0xfe.toByte(), 0x05.toByte(), 0x26.toByte(), 0x02.toByte(), 0x05.toByte(), 0xd6.toByte(), 0x35.toByte(), 0x84.toByte(), + 0xc6.toByte(), 0xfd.toByte(), 0x07.toByte(), 0xeb.toByte(), 0xfd.toByte(), 0x47.toByte(), 0xd6.toByte(), 0x0d.toByte(), 0x04.toByte(), 0xb6.toByte(), + 0x22.toByte(), 0xc7.toByte(), 0xd8.toByte(), 0x01.toByte(), 0xbe.toByte(), 0x1b.toByte(), 0xfa.toByte(), 0x07.toByte(), 0x79.toByte(), 0x09.toByte(), + 0x79.toByte(), 0x17.toByte(), 0x0e.toByte(), 0xca.toByte(), 0x28.toByte(), 0xfe.toByte(), 0x04.toByte(), 0xa6.toByte(), 0x05.toByte(), 0xf8.toByte(), + 0xe9.toByte(), 0xfd.toByte(), 0x05.toByte(), 0xf6.toByte(), 0xf1.toByte(), 0x44.toByte(), 0x07.toByte(), 0x16.toByte(), 0x85.toByte(), 0x06.toByte(), + 0xf2.toByte(), 0xf8.toByte(), 0xf9.toByte(), 0x05.toByte(), 0xb9.toByte(), 0x76.toByte(), 0x2e.toByte(), 0x44.toByte(), 0xe6.toByte(), 0x01.toByte(), + 0xf1.toByte(), 0xfb.toByte(), 0x01.toByte(), 0xc4.toByte(), 0xe9.toByte(), 0xfd.toByte(), 0xf9.toByte(), 0x09.toByte(), 0xfe.toByte(), 0x02.toByte(), + 0x0e.toByte(), 0xf4.toByte(), 0x09.toByte(), 0x37.toByte(), 0x19.toByte(), 0x25.toByte(), 0xcb.toByte(), 0x07.toByte(), 0xf6.toByte(), 0x05.toByte(), + 0xe9.toByte(), 0x36.toByte(), 0x16.toByte(), 0x00.toByte(), 0x16.toByte(), 0xb9.toByte(), 0xe6.toByte(), 0xfd.toByte(), 0x46.toByte(), 0x26.toByte(), + 0x2a.toByte(), 0x39.toByte(), 0x07.toByte(), 0xf2.toByte(), 0xf9.toByte(), 0x3b.toByte(), 0xfe.toByte(), 0x78.toByte(), 0x26.toByte(), 0xf2.toByte(), + 0xf9.toByte(), 0x09.toByte(), 0xfa.toByte(), 0xc6.toByte(), 0x39.toByte(), 0xfa.toByte(), 0xb9.toByte(), 0xf6.toByte(), 0x01.toByte(), 0x7b.toByte(), + 0x26.toByte(), 0x0e.toByte(), 0x06.toByte(), 0xf6.toByte(), 0x1d.toByte(), 0x46.toByte(), 0xe9.toByte(), 0x05.toByte(), 0xc4.toByte(), 0x2b.toByte(), + 0x0a.toByte(), 0x05.toByte(), 0x0e.toByte(), 0xcf.toByte(), 0x51.toByte(), 0x3e.toByte(), 0x79.toByte(), 0xba.toByte(), 0x0e.toByte(), 0xfe.toByte(), + 0x6d.toByte(), 0x13.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x17.toByte(), 0xf0.toByte(), 0x05.toByte(), 0xfb.toByte(), 0xeb.toByte(), 0xd8.toByte(), + 0x25.toByte(), 0x09.toByte(), 0x06.toByte(), 0x0b.toByte(), 0x14.toByte(), 0x07.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x38.toByte(), 0xf9.toByte(), + 0x01.toByte(), 0x43.toByte(), 0xf6.toByte(), 0x09.toByte(), 0x86.toByte(), 0x86.toByte(), 0x05.toByte(), 0x05.toByte(), 0x36.toByte(), 0xf6.toByte(), + 0x0e.toByte(), 0xf6.toByte(), 0x05.toByte(), 0xf8.toByte(), 0xc9.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x1e.toByte(), 0x12.toByte(), 0x36.toByte(), + 0x05.toByte(), 0x1a.toByte(), 0x08.toByte(), 0xeb.toByte(), 0x12.toByte(), 0xc4.toByte(), 0x17.toByte(), 0xf9.toByte(), 0xf4.toByte(), 0x26.toByte(), + 0xf9.toByte(), 0x69.toByte(), 0xf7.toByte(), 0x37.toByte(), 0xb6.toByte(), 0x0d.toByte(), 0xda.toByte(), 0x76.toByte(), 0x17.toByte(), 0x14.toByte(), + 0xb7.toByte(), 0x08.toByte(), 0x02.toByte(), 0x69.toByte(), 0xe6.toByte(), 0xe9.toByte(), 0x97.toByte(), 0xf4.toByte(), 0x04.toByte(), 0x46.toByte(), + 0x09.toByte(), 0x29.toByte(), 0xb4.toByte(), 0x19.toByte(), 0x00.toByte(), 0x02.toByte(), 0x3b.toByte(), 0xf6.toByte(), 0x1d.toByte(), 0x42.toByte(), + 0x36.toByte(), 0x02.toByte(), 0xc7.toByte(), 0xab.toByte(), 0xf9.toByte(), 0x10.toByte(), 0x07.toByte(), 0x00.toByte(), 0xc4.toByte(), 0x09.toByte(), + 0xc4.toByte(), 0x12.toByte(), 0x36.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xea.toByte(), 0x04.toByte(), 0x0d.toByte(), + 0x06.toByte(), 0x5f.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x2d.toByte(), 0x13.toByte(), 0x28.toByte(), 0x09.toByte(), 0x49.toByte(), 0xe0.toByte(), + 0x14.toByte(), 0xe4.toByte(), 0x1b.toByte(), 0xe9.toByte(), 0x2f.toByte(), 0x39.toByte(), 0x1b.toByte(), 0xe5.toByte(), 0xd6.toByte(), 0x04.toByte(), + 0x58.toByte(), 0x08.toByte(), 0x09.toByte(), 0xc4.toByte(), 0xf5.toByte(), 0x14.toByte(), 0x0b.toByte(), 0x09.toByte(), 0x08.toByte(), 0x07.toByte(), + 0x69.toByte(), 0xf8.toByte(), 0xf6.toByte(), 0xd6.toByte(), 0xd7.toByte(), 0x12.toByte(), 0x14.toByte(), 0x19.toByte(), 0xfa.toByte(), 0xe9.toByte(), + 0x24.toByte(), 0x15.toByte(), 0x27.toByte(), 0x04.toByte(), 0xf6.toByte(), 0xc8.toByte(), 0x19.toByte(), 0x07.toByte(), 0x17.toByte(), 0xf7.toByte(), + 0x18.toByte(), 0x66.toByte(), 0xf8.toByte(), 0xf9.toByte(), 0xe6.toByte(), 0x25.toByte(), 0x08.toByte(), 0xf6.toByte(), 0xec.toByte(), 0x44.toByte(), + 0x06.toByte(), 0xe5.toByte(), 0xeb.toByte(), 0x17.toByte(), 0x19.toByte(), 0x17.toByte(), 0xc7.toByte(), 0xf9.toByte(), 0xf7.toByte(), 0x09.toByte(), + 0x00.toByte(), 0x36.toByte(), 0x02.toByte(), 0x16.toByte(), 0xfe.toByte(), 0xb9.toByte(), 0xf6.toByte(), 0x21.toByte(), 0x42.toByte(), 0x39.toByte(), + 0xfa.toByte(), 0xf8.toByte(), 0xdb.toByte(), 0x29.toByte(), 0x06.toByte(), 0x56.toByte(), 0x1e.toByte(), 0xb9.toByte(), 0x19.toByte(), 0xf2.toByte(), + 0x7c.toByte(), 0x18.toByte(), 0xf2.toByte(), 0xf9.toByte(), 0x26.toByte(), 0x2e.toByte(), 0x78.toByte(), 0xc6.toByte(), 0xf9.toByte(), 0x38.toByte(), + 0x38.toByte(), 0xfe.toByte(), 0x07.toByte(), 0x36.toByte(), 0x1e.toByte(), 0xfa.toByte(), 0x39.toByte(), 0x02.toByte(), 0x78.toByte(), 0x28.toByte(), + 0x02.toByte(), 0x78.toByte(), 0x56.toByte(), 0x12.toByte(), 0x46.toByte(), 0x26.toByte(), 0xfa.toByte(), 0xfa.toByte(), 0x08.toByte(), 0x06.toByte(), + 0x3b.toByte(), 0x56.toByte(), 0x0e.toByte(), 0xc7.toByte(), 0xc4.toByte(), 0x06.toByte(), 0xba.toByte(), 0xf9.toByte(), 0xdd.toByte(), 0xff.toByte(), + 0x79.toByte(), 0xfa.toByte(), 0x46.toByte(), 0xf9.toByte(), 0x05.toByte(), 0xbc.toByte(), 0x16.toByte(), 0x0e.toByte(), 0x86.toByte(), 0x59.toByte(), + 0x06.toByte(), 0x87.toByte(), 0x66.toByte(), 0xfa.toByte(), 0x78.toByte(), 0x16.toByte(), 0xee.toByte(), 0x7f.toByte(), 0x26.toByte(), 0x06.toByte(), + 0x87.toByte(), 0xf9.toByte(), 0xf1.toByte(), 0xfe.toByte(), 0xf8.toByte(), 0x11.toByte(), 0xb9.toByte(), 0x77.toByte(), 0x0e.toByte(), 0x07.toByte(), + 0x16.toByte(), 0xfe.toByte(), 0xbb.toByte(), 0xd6.toByte(), 0xf9.toByte(), 0x39.toByte(), 0xf6.toByte(), 0x05.toByte(), 0xf8.toByte(), 0x49.toByte(), + 0xfa.toByte(), 0x4c.toByte(), 0x54.toByte(), 0x02.toByte(), 0x38.toByte(), 0x76.toByte(), 0xc9.toByte(), 0xb8.toByte(), 0x26.toByte(), 0x0e.toByte(), + 0x02.toByte(), 0x36.toByte(), 0x09.toByte(), 0xe6.toByte(), 0xda.toByte(), 0x47.toByte(), 0x06.toByte(), 0x22.toByte(), 0xb8.toByte(), 0x14.toByte(), + 0xd9.toByte(), 0x14.toByte(), 0x17.toByte(), 0x2a.toByte(), 0x35.toByte(), 0xe6.toByte(), 0x02.toByte(), 0xdb.toByte(), 0xf8.toByte(), 0x17.toByte(), + 0x12.toByte(), 0x29.toByte(), 0x08.toByte(), 0xfb.toByte(), 0xb6.toByte(), 0x14.toByte(), 0x36.toByte(), 0x17.toByte(), 0x2a.toByte(), 0xf2.toByte(), + 0x38.toByte(), 0xe4.toByte(), 0xea.toByte(), 0xd6.toByte(), 0x06.toByte(), 0x26.toByte(), 0x27.toByte(), 0xe6.toByte(), 0xd6.toByte(), 0xe6.toByte(), + 0x0b.toByte(), 0x16.toByte(), 0x34.toByte(), 0x11.toByte(), 0x29.toByte(), 0xe8.toByte(), 0x1b.toByte(), 0xf6.toByte(), 0x36.toByte(), 0x29.toByte(), + 0x39.toByte(), 0x39.toByte(), 0xc7.toByte(), 0xd9.toByte(), 0xb9.toByte(), 0x34.toByte(), 0x14.toByte(), 0x26.toByte(), 0x09.toByte(), 0x16.toByte(), + 0x19.toByte(), 0xc6.toByte(), 0xf4.toByte(), 0xb4.toByte(), 0x5a.toByte(), 0x3d.toByte(), 0x49.toByte(), 0xf0.toByte(), 0xe5.toByte(), 0xd7.toByte(), + 0xfb.toByte(), 0x35.toByte(), 0x58.toByte(), 0x23.toByte(), 0x00.toByte(), 0x02.toByte(), 0x44.toByte(), 0x06.toByte(), 0x2e.toByte(), 0x44.toByte(), + 0xf6.toByte(), 0xf1.toByte(), 0xf8.toByte(), 0xad.toByte(), 0xfd.toByte(), 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), 0x7d.toByte(), 0xe8.toByte(), + 0x7a.toByte(), 0xbc.toByte(), 0x36.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xe9.toByte(), 0x04.toByte(), 0x15.toByte(), + 0x06.toByte(), 0x51.toByte(), 0xf9.toByte(), 0x00.toByte(), 0x0d.toByte(), 0x47.toByte(), 0x18.toByte(), 0xf6.toByte(), 0xba.toByte(), 0x36.toByte(), + 0x26.toByte(), 0xc6.toByte(), 0x37.toByte(), 0x06.toByte(), 0x78.toByte(), 0xf9.toByte(), 0xf1.toByte(), 0x46.toByte(), 0x86.toByte(), 0x12.toByte(), + 0x06.toByte(), 0xd7.toByte(), 0xfd.toByte(), 0x71.toByte(), 0xf8.toByte(), 0xe9.toByte(), 0x85.toByte(), 0x76.toByte(), 0x06.toByte(), 0x04.toByte(), + 0x02.toByte(), 0x16.toByte(), 0xf9.toByte(), 0xa9.toByte(), 0xcb.toByte(), 0xe6.toByte(), 0x75.toByte(), 0x54.toByte(), 0x1b.toByte(), 0x98.toByte(), + 0xd4.toByte(), 0x13.toByte(), 0x67.toByte(), 0x2b.toByte(), 0x16.toByte(), 0xb6.toByte(), 0xe5.toByte(), 0xcb.toByte(), 0x38.toByte(), 0x36.toByte(), + 0x14.toByte(), 0xe7.toByte(), 0xdb.toByte(), 0x39.toByte(), 0x09.toByte(), 0x23.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x38.toByte(), 0x26.toByte(), + 0x0e.toByte(), 0xba.toByte(), 0xe6.toByte(), 0xf9.toByte(), 0x39.toByte(), 0x86.toByte(), 0xf2.toByte(), 0x84.toByte(), 0x06.toByte(), 0xf6.toByte(), + 0x70.toByte(), 0x36.toByte(), 0x06.toByte(), 0xb9.toByte(), 0x46.toByte(), 0xfa.toByte(), 0x02.toByte(), 0x16.toByte(), 0xf6.toByte(), 0x08.toByte(), + 0x1b.toByte(), 0xe9.toByte(), 0x04.toByte(), 0x08.toByte(), 0x24.toByte(), 0x09.toByte(), 0xf0.toByte(), 0x3b.toByte(), 0xe8.toByte(), 0x2a.toByte(), + 0x07.toByte(), 0xf9.toByte(), 0x08.toByte(), 0xe6.toByte(), 0xf6.toByte(), 0x05.toByte(), 0xe5.toByte(), 0x27.toByte(), 0x06.toByte(), 0x4a.toByte(), + 0xf4.toByte(), 0xf8.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xc7.toByte(), 0x09.toByte(), 0x16.toByte(), 0x07.toByte(), 0x16.toByte(), 0xfe.toByte(), + 0xc6.toByte(), 0x39.toByte(), 0x02.toByte(), 0x07.toByte(), 0x24.toByte(), 0xfa.toByte(), 0xfa.toByte(), 0xc6.toByte(), 0xed.toByte(), 0xb8.toByte(), + 0x26.toByte(), 0xfa.toByte(), 0x02.toByte(), 0x16.toByte(), 0x07.toByte(), 0xe7.toByte(), 0x1b.toByte(), 0x19.toByte(), 0x1a.toByte(), 0xf6.toByte(), + 0x12.toByte(), 0x29.toByte(), 0x16.toByte(), 0xe8.toByte(), 0xe6.toByte(), 0x16.toByte(), 0xf6.toByte(), 0x09.toByte(), 0x14.toByte(), 0x24.toByte(), + 0xc4.toByte(), 0xf8.toByte(), 0x1b.toByte(), 0x48.toByte(), 0x09.toByte(), 0xf4.toByte(), 0x16.toByte(), 0xe7.toByte(), 0x00.toByte(), 0x0e.toByte(), + 0xc6.toByte(), 0xf6.toByte(), 0xf5.toByte(), 0xc4.toByte(), 0x16.toByte(), 0x02.toByte(), 0x87.toByte(), 0x06.toByte(), 0xfe.toByte(), 0xb9.toByte(), + 0xe9.toByte(), 0x25.toByte(), 0x05.toByte(), 0x06.toByte(), 0xfa.toByte(), 0x82.toByte(), 0x8b.toByte(), 0x05.toByte(), 0x02.toByte(), 0x1e.toByte(), + 0x47.toByte(), 0x18.toByte(), 0x05.toByte(), 0xfb.toByte(), 0xf9.toByte(), 0x08.toByte(), 0xc9.toByte(), 0x27.toByte(), 0xf2.toByte(), 0x20.toByte(), + 0xe2.toByte(), 0x08.toByte(), 0x26.toByte(), 0x0b.toByte(), 0x1a.toByte(), 0xcc.toByte(), 0x25.toByte(), 0xc2.toByte(), 0x62.toByte(), 0xd8.toByte(), + 0x19.toByte(), 0xd2.toByte(), 0x17.toByte(), 0x06.toByte(), 0x06.toByte(), 0x08.toByte(), 0x26.toByte(), 0xdf.toByte(), 0x16.toByte(), 0x07.toByte(), + 0x47.toByte(), 0x24.toByte(), 0x06.toByte(), 0xa1.toByte(), 0xc9.toByte(), 0x26.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x84.toByte(), 0x16.toByte(), + 0x0a.toByte(), 0x87.toByte(), 0x69.toByte(), 0x01.toByte(), 0x39.toByte(), 0x36.toByte(), 0x0e.toByte(), 0x83.toByte(), 0x29.toByte(), 0x0e.toByte(), + 0xf9.toByte(), 0xd9.toByte(), 0xf5.toByte(), 0xbb.toByte(), 0x16.toByte(), 0x06.toByte(), 0x02.toByte(), 0x1f.toByte(), 0xf4.toByte(), 0x17.toByte(), + 0x1b.toByte(), 0x0c.toByte(), 0xc6.toByte(), 0xf5.toByte(), 0x52.toByte(), 0xe5.toByte(), 0xd8.toByte(), 0xfb.toByte(), 0x3a.toByte(), 0x49.toByte(), + 0x27.toByte(), 0xe6.toByte(), 0xd6.toByte(), 0xe8.toByte(), 0x45.toByte(), 0xf7.toByte(), 0x01.toByte(), 0xd7.toByte(), 0x28.toByte(), 0x1f.toByte(), + 0x0b.toByte(), 0xc2.toByte(), 0xf4.toByte(), 0x45.toByte(), 0x1a.toByte(), 0x2a.toByte(), 0xf6.toByte(), 0x17.toByte(), 0xf9.toByte(), 0x2b.toByte(), + 0x14.toByte(), 0x18.toByte(), 0x02.toByte(), 0xbc.toByte(), 0xf7.toByte(), 0x06.toByte(), 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), 0x7c.toByte(), + 0x4b.toByte(), 0xc2.toByte(), 0x49.toByte(), 0x37.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xe9.toByte(), 0x04.toByte(), + 0x0d.toByte(), 0x06.toByte(), 0x5d.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x00.toByte(), 0x27.toByte(), 0x38.toByte(), 0x04.toByte(), 0x09.toByte(), + 0x12.toByte(), 0xf7.toByte(), 0xb3.toByte(), 0x3b.toByte(), 0xf3.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xba.toByte(), 0x17.toByte(), 0xe6.toByte(), + 0xbc.toByte(), 0xf6.toByte(), 0xf9.toByte(), 0x47.toByte(), 0x09.toByte(), 0xfa.toByte(), 0x85.toByte(), 0xe9.toByte(), 0x01.toByte(), 0xc7.toByte(), + 0x39.toByte(), 0x06.toByte(), 0x86.toByte(), 0x16.toByte(), 0x06.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x2b.toByte(), 0xd6.toByte(), 0x1b.toByte(), + 0xb7.toByte(), 0xf9.toByte(), 0x07.toByte(), 0x59.toByte(), 0x17.toByte(), 0x28.toByte(), 0xe6.toByte(), 0x39.toByte(), 0xd5.toByte(), 0x00.toByte(), + 0x0e.toByte(), 0xc3.toByte(), 0x49.toByte(), 0xfa.toByte(), 0x84.toByte(), 0xd6.toByte(), 0xf9.toByte(), 0x06.toByte(), 0xb9.toByte(), 0xf1.toByte(), + 0x06.toByte(), 0xf7.toByte(), 0x09.toByte(), 0x8e.toByte(), 0x16.toByte(), 0x06.toByte(), 0x7b.toByte(), 0x76.toByte(), 0xf9.toByte(), 0x02.toByte(), + 0x0e.toByte(), 0xd9.toByte(), 0xe5.toByte(), 0x15.toByte(), 0x1b.toByte(), 0x2b.toByte(), 0xf8.toByte(), 0x23.toByte(), 0xf6.toByte(), 0x30.toByte(), + 0x0b.toByte(), 0x05.toByte(), 0xca.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x46.toByte(), 0xf9.toByte(), 0x0d.toByte(), 0x00.toByte(), 0x06.toByte(), + 0x0e.toByte(), 0x04.toByte(), 0x96.toByte(), 0xf9.toByte(), 0x0b.toByte(), 0xe7.toByte(), 0xf1.toByte(), 0xc2.toByte(), 0x26.toByte(), 0xf5.toByte(), + 0x46.toByte(), 0xd9.toByte(), 0x05.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x02.toByte(), 0x39.toByte(), 0xf4.toByte(), 0xdb.toByte(), 0x19.toByte(), + 0x0d.toByte(), 0x36.toByte(), 0xf7.toByte(), 0x33.toByte(), 0x04.toByte(), 0xd6.toByte(), 0xdb.toByte(), 0x00.toByte(), 0x1e.toByte(), 0xbb.toByte(), + 0x16.toByte(), 0x36.toByte(), 0x46.toByte(), 0x06.toByte(), 0x06.toByte(), 0xf9.toByte(), 0x1b.toByte(), 0xf2.toByte(), 0xc0.toByte(), 0x08.toByte(), + 0x26.toByte(), 0x87.toByte(), 0xb6.toByte(), 0x09.toByte(), 0xc6.toByte(), 0x0b.toByte(), 0x06.toByte(), 0x39.toByte(), 0x36.toByte(), 0x12.toByte(), + 0x78.toByte(), 0x26.toByte(), 0x0a.toByte(), 0x85.toByte(), 0x39.toByte(), 0xfe.toByte(), 0x3b.toByte(), 0x09.toByte(), 0x06.toByte(), 0x38.toByte(), + 0xf6.toByte(), 0x22.toByte(), 0xbb.toByte(), 0xe9.toByte(), 0x01.toByte(), 0xf7.toByte(), 0xeb.toByte(), 0xf9.toByte(), 0xc4.toByte(), 0x29.toByte(), + 0x0a.toByte(), 0xc7.toByte(), 0x09.toByte(), 0x02.toByte(), 0x86.toByte(), 0x29.toByte(), 0x02.toByte(), 0x38.toByte(), 0x26.toByte(), 0x06.toByte(), + 0xf9.toByte(), 0x19.toByte(), 0x02.toByte(), 0x02.toByte(), 0x1e.toByte(), 0xe6.toByte(), 0x39.toByte(), 0x96.toByte(), 0x19.toByte(), 0xd7.toByte(), + 0x58.toByte(), 0xd7.toByte(), 0x09.toByte(), 0x05.toByte(), 0x46.toByte(), 0x06.toByte(), 0xf6.toByte(), 0xe4.toByte(), 0x18.toByte(), 0x16.toByte(), + 0x19.toByte(), 0x15.toByte(), 0x17.toByte(), 0xbb.toByte(), 0xe9.toByte(), 0x27.toByte(), 0x61.toByte(), 0xd6.toByte(), 0xc9.toByte(), 0xea.toByte(), + 0x46.toByte(), 0x26.toByte(), 0x15.toByte(), 0xe7.toByte(), 0xc7.toByte(), 0xe8.toByte(), 0x22.toByte(), 0x19.toByte(), 0x1a.toByte(), 0xe9.toByte(), + 0x06.toByte(), 0x00.toByte(), 0x1e.toByte(), 0xc2.toByte(), 0xd9.toByte(), 0x05.toByte(), 0x87.toByte(), 0xb9.toByte(), 0x0d.toByte(), 0x87.toByte(), + 0x94.toByte(), 0x02.toByte(), 0x03.toByte(), 0x16.toByte(), 0xd6.toByte(), 0x31.toByte(), 0x16.toByte(), 0x02.toByte(), 0x06.toByte(), 0x16.toByte(), + 0xfa.toByte(), 0x46.toByte(), 0x49.toByte(), 0xfa.toByte(), 0x07.toByte(), 0x16.toByte(), 0x1e.toByte(), 0x46.toByte(), 0xa6.toByte(), 0x0d.toByte(), + 0x78.toByte(), 0x09.toByte(), 0x06.toByte(), 0x79.toByte(), 0xb6.toByte(), 0x1a.toByte(), 0x79.toByte(), 0x16.toByte(), 0xfe.toByte(), 0x7e.toByte(), + 0xd8.toByte(), 0x05.toByte(), 0x87.toByte(), 0x19.toByte(), 0x1a.toByte(), 0x04.toByte(), 0x66.toByte(), 0x02.toByte(), 0xbb.toByte(), 0xf8.toByte(), + 0xfd.toByte(), 0xbd.toByte(), 0x06.toByte(), 0x0a.toByte(), 0x47.toByte(), 0xf6.toByte(), 0xf5.toByte(), 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), + 0x3e.toByte(), 0x7b.toByte(), 0x35.toByte(), 0xe0.toByte(), 0x37.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xe9.toByte(), + 0x04.toByte(), 0x08.toByte(), 0x06.toByte(), 0x5f.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x09.toByte(), 0xe6.toByte(), 0x1c.toByte(), 0x24.toByte(), + 0x26.toByte(), 0xd0.toByte(), 0x0a.toByte(), 0x08.toByte(), 0x45.toByte(), 0xe7.toByte(), 0x32.toByte(), 0x3b.toByte(), 0x18.toByte(), 0xf5.toByte(), + 0x19.toByte(), 0x05.toByte(), 0xfc.toByte(), 0xb4.toByte(), 0x18.toByte(), 0x17.toByte(), 0x5a.toByte(), 0xeb.toByte(), 0x06.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x26.toByte(), 0x7c.toByte(), 0xe6.toByte(), 0xfd.toByte(), 0x39.toByte(), 0x86.toByte(), 0x06.toByte(), 0xc6.toByte(), 0x26.toByte(), + 0xf2.toByte(), 0x7e.toByte(), 0x26.toByte(), 0x02.toByte(), 0xbb.toByte(), 0xe9.toByte(), 0x0d.toByte(), 0x84.toByte(), 0x29.toByte(), 0x0e.toByte(), + 0x3b.toByte(), 0xe6.toByte(), 0x05.toByte(), 0x06.toByte(), 0xa6.toByte(), 0x06.toByte(), 0x46.toByte(), 0xa5.toByte(), 0x06.toByte(), 0x31.toByte(), + 0xf9.toByte(), 0xd5.toByte(), 0x71.toByte(), 0x46.toByte(), 0x1a.toByte(), 0xc2.toByte(), 0x36.toByte(), 0xfe.toByte(), 0x7b.toByte(), 0xf9.toByte(), + 0x05.toByte(), 0x39.toByte(), 0xd6.toByte(), 0xf5.toByte(), 0xf9.toByte(), 0x09.toByte(), 0x02.toByte(), 0xb8.toByte(), 0x19.toByte(), 0x02.toByte(), + 0x39.toByte(), 0xd6.toByte(), 0x32.toByte(), 0x46.toByte(), 0x16.toByte(), 0xfa.toByte(), 0x72.toByte(), 0x0b.toByte(), 0x06.toByte(), 0x78.toByte(), + 0x36.toByte(), 0xfa.toByte(), 0x39.toByte(), 0x16.toByte(), 0x02.toByte(), 0x87.toByte(), 0xd6.toByte(), 0xf9.toByte(), 0x07.toByte(), 0xd6.toByte(), + 0x01.toByte(), 0x06.toByte(), 0xf6.toByte(), 0x05.toByte(), 0x02.toByte(), 0x16.toByte(), 0xe4.toByte(), 0xd2.toByte(), 0xf7.toByte(), 0x13.toByte(), + 0x39.toByte(), 0x39.toByte(), 0xfd.toByte(), 0xda.toByte(), 0xf6.toByte(), 0xf9.toByte(), 0x12.toByte(), 0xe4.toByte(), 0xe4.toByte(), 0x29.toByte(), + 0xfd.toByte(), 0x36.toByte(), 0xeb.toByte(), 0x23.toByte(), 0xe4.toByte(), 0xf5.toByte(), 0x0a.toByte(), 0x26.toByte(), 0xf5.toByte(), 0xe7.toByte(), + 0x00.toByte(), 0x0e.toByte(), 0xc4.toByte(), 0xc9.toByte(), 0x35.toByte(), 0x04.toByte(), 0xd6.toByte(), 0x05.toByte(), 0xf9.toByte(), 0x0b.toByte(), + 0xfa.toByte(), 0xf9.toByte(), 0xf6.toByte(), 0xf9.toByte(), 0x46.toByte(), 0x16.toByte(), 0xea.toByte(), 0x82.toByte(), 0x16.toByte(), 0xfa.toByte(), + 0x02.toByte(), 0x0e.toByte(), 0x39.toByte(), 0xe9.toByte(), 0x34.toByte(), 0xd4.toByte(), 0x09.toByte(), 0xda.toByte(), 0x37.toByte(), 0x07.toByte(), + 0x27.toByte(), 0x0b.toByte(), 0xf4.toByte(), 0x07.toByte(), 0x00.toByte(), 0x1e.toByte(), 0x84.toByte(), 0xf9.toByte(), 0x15.toByte(), 0x07.toByte(), + 0x06.toByte(), 0x0e.toByte(), 0x39.toByte(), 0x76.toByte(), 0xfa.toByte(), 0xb9.toByte(), 0x29.toByte(), 0xea.toByte(), 0xb0.toByte(), 0xf6.toByte(), + 0x05.toByte(), 0x38.toByte(), 0x76.toByte(), 0x0a.toByte(), 0x03.toByte(), 0xf6.toByte(), 0x15.toByte(), 0x3e.toByte(), 0xe9.toByte(), 0x15.toByte(), + 0xc5.toByte(), 0x59.toByte(), 0xfa.toByte(), 0x3a.toByte(), 0xe9.toByte(), 0x09.toByte(), 0xbb.toByte(), 0x16.toByte(), 0x0e.toByte(), 0x07.toByte(), + 0x79.toByte(), 0xfe.toByte(), 0xc6.toByte(), 0x19.toByte(), 0x2e.toByte(), 0xbf.toByte(), 0x26.toByte(), 0x06.toByte(), 0xf8.toByte(), 0x68.toByte(), + 0xfa.toByte(), 0x38.toByte(), 0x36.toByte(), 0xf2.toByte(), 0x7e.toByte(), 0x36.toByte(), 0x02.toByte(), 0xbb.toByte(), 0x36.toByte(), 0xfa.toByte(), + 0x02.toByte(), 0x0e.toByte(), 0x38.toByte(), 0xe2.toByte(), 0xd8.toByte(), 0x96.toByte(), 0x58.toByte(), 0x29.toByte(), 0x5b.toByte(), 0xa4.toByte(), + 0xf6.toByte(), 0x33.toByte(), 0x29.toByte(), 0x16.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xbb.toByte(), 0xe9.toByte(), 0x2d.toByte(), 0x0c.toByte(), + 0x36.toByte(), 0xf6.toByte(), 0x7b.toByte(), 0x78.toByte(), 0x15.toByte(), 0xf8.toByte(), 0x16.toByte(), 0x0e.toByte(), 0x06.toByte(), 0xb9.toByte(), + 0x15.toByte(), 0x84.toByte(), 0xf9.toByte(), 0x01.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xe7.toByte(), 0xc8.toByte(), 0x1b.toByte(), 0x02.toByte(), + 0xd6.toByte(), 0x15.toByte(), 0xf9.toByte(), 0x16.toByte(), 0x16.toByte(), 0x16.toByte(), 0x06.toByte(), 0xed.toByte(), 0x11.toByte(), 0x07.toByte(), + 0x00.toByte(), 0x3a.toByte(), 0xbd.toByte(), 0x4f.toByte(), 0x7c.toByte(), 0x34.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), + 0xe9.toByte(), 0x04.toByte(), 0x0f.toByte(), 0x06.toByte(), 0x5d.toByte(), 0xf9.toByte(), 0x00.toByte(), 0x09.toByte(), 0x41.toByte(), 0xf7.toByte(), + 0x01.toByte(), 0xc4.toByte(), 0xb9.toByte(), 0xf9.toByte(), 0x44.toByte(), 0xf9.toByte(), 0xf9.toByte(), 0x46.toByte(), 0x16.toByte(), 0x2a.toByte(), + 0x04.toByte(), 0xf6.toByte(), 0xf9.toByte(), 0xbb.toByte(), 0xfb.toByte(), 0x01.toByte(), 0x06.toByte(), 0x06.toByte(), 0x06.toByte(), 0xbb.toByte(), + 0x09.toByte(), 0x0e.toByte(), 0xc2.toByte(), 0x19.toByte(), 0xfe.toByte(), 0x04.toByte(), 0xf9.toByte(), 0xf9.toByte(), 0xc6.toByte(), 0x26.toByte(), + 0x16.toByte(), 0x3b.toByte(), 0x02.toByte(), 0x16.toByte(), 0xf5.toByte(), 0xc9.toByte(), 0x15.toByte(), 0xc6.toByte(), 0xf4.toByte(), 0x16.toByte(), + 0x25.toByte(), 0x39.toByte(), 0xf7.toByte(), 0x1a.toByte(), 0xf2.toByte(), 0xdb.toByte(), 0xe6.toByte(), 0x58.toByte(), 0x17.toByte(), 0x27.toByte(), + 0x17.toByte(), 0xe0.toByte(), 0xf9.toByte(), 0xb8.toByte(), 0xfc.toByte(), 0x06.toByte(), 0xe5.toByte(), 0x1a.toByte(), 0x00.toByte(), 0x26.toByte(), + 0x46.toByte(), 0xf6.toByte(), 0x09.toByte(), 0x82.toByte(), 0xd6.toByte(), 0xfd.toByte(), 0xf8.toByte(), 0x19.toByte(), 0x0a.toByte(), 0x05.toByte(), + 0x27.toByte(), 0x06.toByte(), 0x7d.toByte(), 0xa9.toByte(), 0x22.toByte(), 0x06.toByte(), 0x06.toByte(), 0xfa.toByte(), 0x7f.toByte(), 0x2b.toByte(), + 0xfa.toByte(), 0x47.toByte(), 0x89.toByte(), 0x05.toByte(), 0x47.toByte(), 0x17.toByte(), 0x0e.toByte(), 0x3b.toByte(), 0x27.toByte(), 0x06.toByte(), + 0xc6.toByte(), 0x49.toByte(), 0xee.toByte(), 0x7a.toByte(), 0xf9.toByte(), 0x05.toByte(), 0xc6.toByte(), 0x06.toByte(), 0x02.toByte(), 0x06.toByte(), + 0x06.toByte(), 0x06.toByte(), 0x47.toByte(), 0xe6.toByte(), 0x0d.toByte(), 0xc7.toByte(), 0x07.toByte(), 0x06.toByte(), 0xc4.toByte(), 0x19.toByte(), + 0xd2.toByte(), 0x46.toByte(), 0x16.toByte(), 0xfa.toByte(), 0xc6.toByte(), 0x17.toByte(), 0xfa.toByte(), 0x44.toByte(), 0xf9.toByte(), 0xed.toByte(), + 0xc4.toByte(), 0xf9.toByte(), 0x09.toByte(), 0x02.toByte(), 0xe4.toByte(), 0x05.toByte(), 0xf8.toByte(), 0xe9.toByte(), 0xfd.toByte(), 0x39.toByte(), + 0xe6.toByte(), 0x01.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xe9.toByte(), 0xc9.toByte(), 0xf4.toByte(), 0x29.toByte(), 0x26.toByte(), 0x06.toByte(), + 0x20.toByte(), 0xd7.toByte(), 0x3a.toByte(), 0xec.toByte(), 0x28.toByte(), 0xe5.toByte(), 0x00.toByte(), 0x16.toByte(), 0x85.toByte(), 0x19.toByte(), + 0xfa.toByte(), 0x79.toByte(), 0xe7.toByte(), 0x01.toByte(), 0x07.toByte(), 0x06.toByte(), 0xf2.toByte(), 0x8f.toByte(), 0xf6.toByte(), 0x05.toByte(), + 0x39.toByte(), 0xa6.toByte(), 0xf1.toByte(), 0xc4.toByte(), 0x09.toByte(), 0xf2.toByte(), 0x06.toByte(), 0xf4.toByte(), 0xf9.toByte(), 0x45.toByte(), + 0x16.toByte(), 0xe2.toByte(), 0xf9.toByte(), 0xf6.toByte(), 0x0d.toByte(), 0x02.toByte(), 0x26.toByte(), 0x02.toByte(), 0xbb.toByte(), 0xa9.toByte(), + 0x05.toByte(), 0xc7.toByte(), 0x29.toByte(), 0xfe.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xf7.toByte(), 0x09.toByte(), 0x17.toByte(), 0x1b.toByte(), + 0x06.toByte(), 0xe6.toByte(), 0x13.toByte(), 0x28.toByte(), 0x37.toByte(), 0xbb.toByte(), 0xf4.toByte(), 0x36.toByte(), 0x00.toByte(), 0x16.toByte(), + 0x4f.toByte(), 0xf7.toByte(), 0x01.toByte(), 0xc5.toByte(), 0x48.toByte(), 0x05.toByte(), 0x46.toByte(), 0xa6.toByte(), 0x11.toByte(), 0x83.toByte(), + 0xc9.toByte(), 0x05.toByte(), 0x85.toByte(), 0x09.toByte(), 0x0a.toByte(), 0x45.toByte(), 0xf9.toByte(), 0x09.toByte(), 0xbd.toByte(), 0xe9.toByte(), + 0x05.toByte(), 0xc7.toByte(), 0xf9.toByte(), 0xf9.toByte(), 0xc4.toByte(), 0x74.toByte(), 0xfa.toByte(), 0x85.toByte(), 0x06.toByte(), 0xde.toByte(), + 0x70.toByte(), 0x16.toByte(), 0xfa.toByte(), 0xc6.toByte(), 0x39.toByte(), 0xfa.toByte(), 0x02.toByte(), 0x08.toByte(), 0xf9.toByte(), 0x38.toByte(), + 0x26.toByte(), 0x1b.toByte(), 0xe4.toByte(), 0xf6.toByte(), 0x27.toByte(), 0x56.toByte(), 0x39.toByte(), 0x36.toByte(), 0x0d.toByte(), 0xc5.toByte(), + 0xfa.toByte(), 0xe4.toByte(), 0x1b.toByte(), 0x28.toByte(), 0x37.toByte(), 0xe6.toByte(), 0xa4.toByte(), 0xe9.toByte(), 0x49.toByte(), 0x11.toByte(), + 0x07.toByte(), 0x00.toByte(), 0x00.toByte(), 0xfa.toByte(), 0x10.toByte(), 0x15.toByte(), 0x35.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), + 0x86.toByte(), 0xf4.toByte(), 0x04.toByte(), 0x08.toByte(), 0x06.toByte(), 0x5c.toByte(), 0xf9.toByte(), 0x05.toByte(), 0x07.toByte(), 0x46.toByte(), + 0x07.toByte(), 0x02.toByte(), 0x16.toByte(), 0xe4.toByte(), 0xe5.toByte(), 0xf8.toByte(), 0x18.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x39.toByte(), + 0x37.toByte(), 0xf9.toByte(), 0x15.toByte(), 0xf4.toByte(), 0x18.toByte(), 0xd8.toByte(), 0x09.toByte(), 0xe3.toByte(), 0x17.toByte(), 0xe6.toByte(), + 0x4b.toByte(), 0x1b.toByte(), 0x3b.toByte(), 0x29.toByte(), 0xe2.toByte(), 0xd5.toByte(), 0xbb.toByte(), 0x00.toByte(), 0x16.toByte(), 0xf8.toByte(), + 0x16.toByte(), 0x02.toByte(), 0x83.toByte(), 0xf6.toByte(), 0xf1.toByte(), 0xb9.toByte(), 0xf6.toByte(), 0xf9.toByte(), 0x86.toByte(), 0xf6.toByte(), + 0xfd.toByte(), 0x04.toByte(), 0xa6.toByte(), 0x16.toByte(), 0x45.toByte(), 0xc6.toByte(), 0xf9.toByte(), 0xb3.toByte(), 0xe8.toByte(), 0xf1.toByte(), + 0x02.toByte(), 0x16.toByte(), 0x06.toByte(), 0x44.toByte(), 0xb6.toByte(), 0xf9.toByte(), 0x44.toByte(), 0xf9.toByte(), 0x0d.toByte(), 0x01.toByte(), + 0xd6.toByte(), 0x11.toByte(), 0x06.toByte(), 0xb6.toByte(), 0x09.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xc7.toByte(), 0x19.toByte(), 0xe5.toByte(), + 0x08.toByte(), 0xe6.toByte(), 0x0d.toByte(), 0x34.toByte(), 0x26.toByte(), 0xe4.toByte(), 0x05.toByte(), 0xd9.toByte(), 0x36.toByte(), 0x00.toByte(), + 0x0e.toByte(), 0x06.toByte(), 0xf6.toByte(), 0x25.toByte(), 0x07.toByte(), 0xd6.toByte(), 0x01.toByte(), 0x06.toByte(), 0xb8.toByte(), 0x0d.toByte(), + 0x4e.toByte(), 0x26.toByte(), 0x2e.toByte(), 0xc2.toByte(), 0xb8.toByte(), 0x05.toByte(), 0xf8.toByte(), 0xdb.toByte(), 0x0d.toByte(), 0x02.toByte(), + 0x0e.toByte(), 0xb9.toByte(), 0x79.toByte(), 0x05.toByte(), 0x0b.toByte(), 0x96.toByte(), 0x09.toByte(), 0x5b.toByte(), 0x76.toByte(), 0x2b.toByte(), + 0x05.toByte(), 0xb6.toByte(), 0x13.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x79.toByte(), 0x19.toByte(), 0xfe.toByte(), 0x38.toByte(), 0x46.toByte(), + 0xfe.toByte(), 0x3b.toByte(), 0xb6.toByte(), 0x0a.toByte(), 0xc6.toByte(), 0x29.toByte(), 0x16.toByte(), 0x3e.toByte(), 0x29.toByte(), 0x02.toByte(), + 0xc4.toByte(), 0x08.toByte(), 0x16.toByte(), 0x02.toByte(), 0x16.toByte(), 0x26.toByte(), 0xef.toByte(), 0xd4.toByte(), 0xf8.toByte(), 0xf6.toByte(), + 0x59.toByte(), 0xe4.toByte(), 0x65.toByte(), 0xc6.toByte(), 0xf7.toByte(), 0xb6.toByte(), 0x26.toByte(), 0x29.toByte(), 0x09.toByte(), 0x16.toByte(), + 0x07.toByte(), 0xf7.toByte(), 0x0d.toByte(), 0x38.toByte(), 0x16.toByte(), 0xd3.toByte(), 0xe6.toByte(), 0xf9.toByte(), 0x29.toByte(), 0x05.toByte(), + 0x0e.toByte(), 0x07.toByte(), 0x5a.toByte(), 0x41.toByte(), 0x11.toByte(), 0x80.toByte(), 0xc1.toByte(), 0x31.toByte(), 0x16.toByte(), 0x5b.toByte(), + 0x00.toByte(), 0x16.toByte(), 0xc6.toByte(), 0xc9.toByte(), 0x0d.toByte(), 0xc4.toByte(), 0x59.toByte(), 0x0a.toByte(), 0xc5.toByte(), 0x77.toByte(), + 0xfa.toByte(), 0x3f.toByte(), 0x29.toByte(), 0xda.toByte(), 0xbe.toByte(), 0xf6.toByte(), 0x0d.toByte(), 0x39.toByte(), 0x26.toByte(), 0xfe.toByte(), + 0x02.toByte(), 0x16.toByte(), 0x06.toByte(), 0x46.toByte(), 0xf6.toByte(), 0x0d.toByte(), 0xf9.toByte(), 0xe6.toByte(), 0x05.toByte(), 0x46.toByte(), + 0x19.toByte(), 0xf2.toByte(), 0x42.toByte(), 0x36.toByte(), 0x26.toByte(), 0xfa.toByte(), 0xa9.toByte(), 0x05.toByte(), 0x02.toByte(), 0x0e.toByte(), + 0x9d.toByte(), 0x04.toByte(), 0xe2.toByte(), 0xe7.toByte(), 0x08.toByte(), 0xfb.toByte(), 0x12.toByte(), 0x24.toByte(), 0x45.toByte(), 0x6a.toByte(), + 0x08.toByte(), 0xd8.toByte(), 0x00.toByte(), 0x16.toByte(), 0x8d.toByte(), 0x06.toByte(), 0x02.toByte(), 0xc7.toByte(), 0x06.toByte(), 0xe9.toByte(), + 0x45.toByte(), 0x29.toByte(), 0xfe.toByte(), 0x43.toByte(), 0xf9.toByte(), 0x09.toByte(), 0x81.toByte(), 0x26.toByte(), 0x1e.toByte(), 0x38.toByte(), + 0x26.toByte(), 0xf2.toByte(), 0xbb.toByte(), 0x2b.toByte(), 0x02.toByte(), 0xb1.toByte(), 0x29.toByte(), 0x12.toByte(), 0x47.toByte(), 0xd9.toByte(), + 0xfd.toByte(), 0xf8.toByte(), 0x09.toByte(), 0x0a.toByte(), 0x87.toByte(), 0x37.toByte(), 0x0e.toByte(), 0xc7.toByte(), 0x09.toByte(), 0xf6.toByte(), + 0x13.toByte(), 0x07.toByte(), 0x00.toByte(), 0xb4.toByte(), 0xeb.toByte(), 0x27.toByte(), 0xa6.toByte(), 0x35.toByte(), 0x99.toByte(), 0x13.toByte(), + 0x0c.toByte(), 0x86.toByte(), 0xf1.toByte(), 0x04.toByte(), 0x09.toByte(), 0x06.toByte(), 0x51.toByte(), 0xf9.toByte(), 0x00.toByte(), 0x01.toByte(), + 0xc4.toByte(), 0x37.toByte(), 0x06.toByte(), 0x7b.toByte(), 0xe6.toByte(), 0xe1.toByte(), 0x79.toByte(), 0xfb.toByte(), 0xf5.toByte(), 0xc7.toByte(), + 0x26.toByte(), 0x26.toByte(), 0xc5.toByte(), 0xf6.toByte(), 0xf5.toByte(), 0x39.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xeb.toByte(), 0x58.toByte(), + 0x13.toByte(), 0x18.toByte(), 0xa9.toByte(), 0xf9.toByte(), 0x17.toByte(), 0x16.toByte(), 0xfd.toByte(), 0xe5.toByte(), 0x05.toByte(), 0x25.toByte(), + 0x00.toByte(), 0x0e.toByte(), 0x00.toByte(), 0x07.toByte(), 0x06.toByte(), 0x46.toByte(), 0x76.toByte(), 0xf5.toByte(), 0x3a.toByte(), 0x36.toByte(), + 0xfa.toByte(), 0x07.toByte(), 0x06.toByte(), 0x0e.toByte(), 0x3b.toByte(), 0x06.toByte(), 0xfe.toByte(), 0x86.toByte(), 0x18.toByte(), 0x0a.toByte(), + 0x02.toByte(), 0x0e.toByte(), 0x02.toByte(), 0x07.toByte(), 0x12.toByte(), 0xe8.toByte(), 0x29.toByte(), 0xba.toByte(), 0x37.toByte(), 0x37.toByte(), + 0x27.toByte(), 0xfc.toByte(), 0xd2.toByte(), 0x16.toByte(), 0x00.toByte(), 0x16.toByte(), 0x05.toByte(), 0xd6.toByte(), 0x01.toByte(), 0x44.toByte(), + 0xd6.toByte(), 0xf9.toByte(), 0x47.toByte(), 0x16.toByte(), 0xfa.toByte(), 0x04.toByte(), 0x06.toByte(), 0x2e.toByte(), 0x0c.toByte(), 0xd6.toByte(), + 0x05.toByte(), 0xf8.toByte(), 0x7b.toByte(), 0x05.toByte(), 0x44.toByte(), 0x16.toByte(), 0x1a.toByte(), 0x39.toByte(), 0x36.toByte(), 0xfe.toByte(), + 0xc7.toByte(), 0xda.toByte(), 0x01.toByte(), 0x86.toByte(), 0xd6.toByte(), 0x09.toByte(), 0xc2.toByte(), 0x09.toByte(), 0xfa.toByte(), 0x38.toByte(), + 0xe9.toByte(), 0x05.toByte(), 0x05.toByte(), 0x0e.toByte(), 0x87.toByte(), 0xa2.toByte(), 0xe5.toByte(), 0xf7.toByte(), 0x25.toByte(), 0xfb.toByte(), + 0xc6.toByte(), 0x17.toByte(), 0x06.toByte(), 0x00.toByte(), 0x46.toByte(), 0xc7.toByte(), 0x09.toByte(), 0x0e.toByte(), 0x78.toByte(), 0x86.toByte(), + 0x36.toByte(), 0x38.toByte(), 0x26.toByte(), 0x06.toByte(), 0x3c.toByte(), 0x3b.toByte(), 0x06.toByte(), 0xc7.toByte(), 0xe9.toByte(), 0x01.toByte(), + 0x44.toByte(), 0xc9.toByte(), 0x05.toByte(), 0x06.toByte(), 0xc9.toByte(), 0x25.toByte(), 0x05.toByte(), 0x69.toByte(), 0x16.toByte(), 0x04.toByte(), + 0x59.toByte(), 0x16.toByte(), 0xbd.toByte(), 0xf9.toByte(), 0x11.toByte(), 0x3f.toByte(), 0xf9.toByte(), 0x05.toByte(), 0x87.toByte(), 0x88.toByte(), + 0x0a.toByte(), 0x39.toByte(), 0x16.toByte(), 0x02.toByte(), 0xb1.toByte(), 0x16.toByte(), 0xfa.toByte(), 0xc7.toByte(), 0x99.toByte(), 0xfe.toByte(), + 0xb9.toByte(), 0xe6.toByte(), 0xf9.toByte(), 0xb1.toByte(), 0x26.toByte(), 0xf2.toByte(), 0xb9.toByte(), 0x49.toByte(), 0xf2.toByte(), 0x86.toByte(), + 0x06.toByte(), 0x06.toByte(), 0x79.toByte(), 0x16.toByte(), 0x02.toByte(), 0x38.toByte(), 0xf6.toByte(), 0x05.toByte(), 0xc4.toByte(), 0x29.toByte(), + 0x22.toByte(), 0x79.toByte(), 0xf6.toByte(), 0x0d.toByte(), 0xc7.toByte(), 0x38.toByte(), 0xfe.toByte(), 0x06.toByte(), 0x39.toByte(), 0xee.toByte(), + 0xb8.toByte(), 0xf6.toByte(), 0x01.toByte(), 0xb8.toByte(), 0xe6.toByte(), 0xf9.toByte(), 0xf9.toByte(), 0xe9.toByte(), 0x01.toByte(), 0x07.toByte(), + 0xf6.toByte(), 0x09.toByte(), 0x47.toByte(), 0xe4.toByte(), 0xf5.toByte(), 0xf9.toByte(), 0x09.toByte(), 0xe6.toByte(), 0xc4.toByte(), 0x16.toByte(), + 0x02.toByte(), 0xc5.toByte(), 0x36.toByte(), 0xfa.toByte(), 0x47.toByte(), 0xb9.toByte(), 0xf9.toByte(), 0xce.toByte(), 0x46.toByte(), 0x12.toByte(), + 0xc7.toByte(), 0xa4.toByte(), 0xf1.toByte(), 0x4f.toByte(), 0xe6.toByte(), 0xe1.toByte(), 0xba.toByte(), 0x26.toByte(), 0xf1.toByte(), 0xc5.toByte(), + 0x19.toByte(), 0x0e.toByte(), 0xf8.toByte(), 0xf9.toByte(), 0xfd.toByte(), 0xc6.toByte(), 0xe9.toByte(), 0xf1.toByte(), 0x39.toByte(), 0x17.toByte(), + 0x06.toByte(), 0x4d.toByte(), 0x05.toByte(), 0xf6.toByte(), 0x7b.toByte(), 0x66.toByte(), 0xd5.toByte(), 0xc7.toByte(), 0x36.toByte(), 0xfe.toByte(), + 0x46.toByte(), 0x06.toByte(), 0xf2.toByte(), 0x79.toByte(), 0x76.toByte(), 0x06.toByte(), 0x46.toByte(), 0x25.toByte(), 0xe2.toByte(), 0x12.toByte(), + 0x07.toByte(), 0x00.toByte(), 0xc2.toByte(), 0x13.toByte(), 0x99.toByte(), 0x30.toByte(), 0x32.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), + 0x86.toByte(), 0x06.toByte(), 0x05.toByte(), 0x16.toByte(), 0x06.toByte(), 0x55.toByte(), 0xf9.toByte(), 0x00.toByte(), 0x39.toByte(), 0x31.toByte(), + 0x66.toByte(), 0x26.toByte(), 0xf8.toByte(), 0x19.toByte(), 0x02.toByte(), 0xf9.toByte(), 0x38.toByte(), 0xfa.toByte(), 0x0a.toByte(), 0xd6.toByte(), + 0xe9.toByte(), 0x39.toByte(), 0x77.toByte(), 0x05.toByte(), 0x05.toByte(), 0x07.toByte(), 0xfe.toByte(), 0xfd.toByte(), 0x38.toByte(), 0x02.toByte(), + 0xf9.toByte(), 0xc6.toByte(), 0x0a.toByte(), 0xfa.toByte(), 0xf9.toByte(), 0xf5.toByte(), 0x70.toByte(), 0x19.toByte(), 0x26.toByte(), 0xc4.toByte(), + 0xa9.toByte(), 0x12.toByte(), 0x87.toByte(), 0xfb.toByte(), 0xfd.toByte(), 0xe9.toByte(), 0x29.toByte(), 0x22.toByte(), 0x87.toByte(), 0xa6.toByte(), + 0x06.toByte(), 0x3d.toByte(), 0x1b.toByte(), 0x0e.toByte(), 0x86.toByte(), 0x46.toByte(), 0x3e.toByte(), 0x78.toByte(), 0xb9.toByte(), 0xfd.toByte(), + 0x7b.toByte(), 0x48.toByte(), 0x0e.toByte(), 0x84.toByte(), 0x09.toByte(), 0xf6.toByte(), 0x7a.toByte(), 0x26.toByte(), 0x02.toByte(), 0x86.toByte(), + 0x24.toByte(), 0xfa.toByte(), 0xbc.toByte(), 0x46.toByte(), 0xda.toByte(), 0x3b.toByte(), 0xc6.toByte(), 0xf1.toByte(), 0xf9.toByte(), 0x39.toByte(), + 0x0e.toByte(), 0xb8.toByte(), 0xd9.toByte(), 0x05.toByte(), 0xfb.toByte(), 0x06.toByte(), 0x06.toByte(), 0x84.toByte(), 0x89.toByte(), 0xfe.toByte(), + 0x85.toByte(), 0xe6.toByte(), 0x1d.toByte(), 0x3e.toByte(), 0xc6.toByte(), 0x21.toByte(), 0xf9.toByte(), 0x78.toByte(), 0xfa.toByte(), 0x84.toByte(), + 0x0b.toByte(), 0x02.toByte(), 0xbc.toByte(), 0x06.toByte(), 0x02.toByte(), 0x85.toByte(), 0xf6.toByte(), 0xf1.toByte(), 0x39.toByte(), 0xf6.toByte(), + 0xf1.toByte(), 0xb9.toByte(), 0x16.toByte(), 0xfa.toByte(), 0x39.toByte(), 0x57.toByte(), 0x06.toByte(), 0x87.toByte(), 0xe9.toByte(), 0xf9.toByte(), + 0x79.toByte(), 0x26.toByte(), 0x0e.toByte(), 0xf9.toByte(), 0x29.toByte(), 0x06.toByte(), 0x38.toByte(), 0x36.toByte(), 0xf6.toByte(), 0xf8.toByte(), + 0x09.toByte(), 0x2a.toByte(), 0x78.toByte(), 0xe4.toByte(), 0x05.toByte(), 0x44.toByte(), 0x0b.toByte(), 0xe6.toByte(), 0x87.toByte(), 0xf6.toByte(), + 0x01.toByte(), 0x46.toByte(), 0xf9.toByte(), 0xe9.toByte(), 0xbb.toByte(), 0x39.toByte(), 0x02.toByte(), 0x85.toByte(), 0x36.toByte(), 0x16.toByte(), + 0x06.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xe0.toByte(), 0xe4.toByte(), 0xaa.toByte(), 0x18.toByte(), 0x39.toByte(), 0x45.toByte(), 0xe4.toByte(), + 0xf6.toByte(), 0xf9.toByte(), 0x1b.toByte(), 0x08.toByte(), 0x0a.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x38.toByte(), 0x26.toByte(), 0xfa.toByte(), + 0x45.toByte(), 0xf6.toByte(), 0x05.toByte(), 0x46.toByte(), 0x16.toByte(), 0x02.toByte(), 0x0d.toByte(), 0x04.toByte(), 0x02.toByte(), 0xbd.toByte(), + 0x76.toByte(), 0xe5.toByte(), 0xc6.toByte(), 0x06.toByte(), 0xfe.toByte(), 0x02.toByte(), 0x2e.toByte(), 0x26.toByte(), 0xc7.toByte(), 0xd4.toByte(), + 0xd9.toByte(), 0x17.toByte(), 0x16.toByte(), 0x02.toByte(), 0x19.toByte(), 0xe5.toByte(), 0xe9.toByte(), 0xe6.toByte(), 0x38.toByte(), 0x49.toByte(), + 0x07.toByte(), 0x05.toByte(), 0x09.toByte(), 0xe9.toByte(), 0x3b.toByte(), 0x26.toByte(), 0x46.toByte(), 0xe4.toByte(), 0xe9.toByte(), 0xe6.toByte(), + 0x36.toByte(), 0xdb.toByte(), 0x26.toByte(), 0xc9.toByte(), 0x34.toByte(), 0x09.toByte(), 0x06.toByte(), 0xd7.toByte(), 0x18.toByte(), 0x44.toByte(), + 0x18.toByte(), 0xe4.toByte(), 0xd8.toByte(), 0x15.toByte(), 0x06.toByte(), 0xf3.toByte(), 0x1a.toByte(), 0x18.toByte(), 0x0c.toByte(), 0xd5.toByte(), + 0x06.toByte(), 0x20.toByte(), 0x37.toByte(), 0xe6.toByte(), 0x4a.toByte(), 0xf7.toByte(), 0x38.toByte(), 0xe5.toByte(), 0x1a.toByte(), 0xc4.toByte(), + 0xe8.toByte(), 0xf7.toByte(), 0x29.toByte(), 0x25.toByte(), 0x05.toByte(), 0xeb.toByte(), 0xfb.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x06.toByte(), + 0x26.toByte(), 0x16.toByte(), 0xb9.toByte(), 0xf6.toByte(), 0xf1.toByte(), 0xc6.toByte(), 0x28.toByte(), 0x02.toByte(), 0x4a.toByte(), 0xe4.toByte(), + 0x1d.toByte(), 0x84.toByte(), 0x89.toByte(), 0xed.toByte(), 0x02.toByte(), 0xf9.toByte(), 0x15.toByte(), 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), + 0x21.toByte(), 0x41.toByte(), 0xc1.toByte(), 0xe3.toByte(), 0x32.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xf7.toByte(), + 0x04.toByte(), 0x09.toByte(), 0x06.toByte(), 0x5d.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x29.toByte(), 0xe8.toByte(), 0x4b.toByte(), 0x37.toByte(), + 0x17.toByte(), 0xc9.toByte(), 0xc8.toByte(), 0x09.toByte(), 0xf6.toByte(), 0x64.toByte(), 0x13.toByte(), 0x56.toByte(), 0xc7.toByte(), 0xf3.toByte(), + 0xe5.toByte(), 0xf5.toByte(), 0xdd.toByte(), 0xc9.toByte(), 0x1a.toByte(), 0x15.toByte(), 0x08.toByte(), 0x05.toByte(), 0x1a.toByte(), 0x05.toByte(), + 0x08.toByte(), 0x72.toByte(), 0x0a.toByte(), 0xf7.toByte(), 0x9f.toByte(), 0x37.toByte(), 0xe6.toByte(), 0x19.toByte(), 0x04.toByte(), 0x37.toByte(), + 0xc4.toByte(), 0xbb.toByte(), 0xf9.toByte(), 0xf8.toByte(), 0x24.toByte(), 0xe8.toByte(), 0x12.toByte(), 0xc6.toByte(), 0x37.toByte(), 0x36.toByte(), + 0x66.toByte(), 0x0b.toByte(), 0xf9.toByte(), 0xa9.toByte(), 0xe4.toByte(), 0xe9.toByte(), 0x77.toByte(), 0xf7.toByte(), 0x46.toByte(), 0x27.toByte(), + 0x37.toByte(), 0xe7.toByte(), 0xb6.toByte(), 0xed.toByte(), 0x1b.toByte(), 0x26.toByte(), 0x15.toByte(), 0xe0.toByte(), 0xf4.toByte(), 0xf9.toByte(), + 0x1b.toByte(), 0xe8.toByte(), 0x13.toByte(), 0xe9.toByte(), 0x67.toByte(), 0x1d.toByte(), 0x36.toByte(), 0x09.toByte(), 0x00.toByte(), 0x26.toByte(), + 0x7b.toByte(), 0xe9.toByte(), 0xf9.toByte(), 0xc6.toByte(), 0xf9.toByte(), 0x15.toByte(), 0x86.toByte(), 0x19.toByte(), 0x02.toByte(), 0x38.toByte(), + 0x29.toByte(), 0x16.toByte(), 0xc7.toByte(), 0xe6.toByte(), 0x16.toByte(), 0x86.toByte(), 0xc9.toByte(), 0xfd.toByte(), 0x73.toByte(), 0xf6.toByte(), + 0x01.toByte(), 0xc5.toByte(), 0x89.toByte(), 0x0e.toByte(), 0xc7.toByte(), 0xa9.toByte(), 0x01.toByte(), 0x3c.toByte(), 0xf9.toByte(), 0x05.toByte(), + 0xc3.toByte(), 0xc9.toByte(), 0x1d.toByte(), 0x06.toByte(), 0xf6.toByte(), 0x01.toByte(), 0x44.toByte(), 0x08.toByte(), 0x06.toByte(), 0x04.toByte(), + 0xc9.toByte(), 0x21.toByte(), 0xc6.toByte(), 0xd6.toByte(), 0x0d.toByte(), 0xc7.toByte(), 0x28.toByte(), 0xfe.toByte(), 0x84.toByte(), 0x56.toByte(), + 0x06.toByte(), 0x38.toByte(), 0x07.toByte(), 0xfa.toByte(), 0xbe.toByte(), 0x29.toByte(), 0xfa.toByte(), 0xba.toByte(), 0x49.toByte(), 0x0e.toByte(), + 0x79.toByte(), 0x16.toByte(), 0x06.toByte(), 0x79.toByte(), 0x09.toByte(), 0x06.toByte(), 0x84.toByte(), 0x36.toByte(), 0x02.toByte(), 0xbb.toByte(), + 0xf6.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x1a.toByte(), 0xf4.toByte(), 0x37.toByte(), 0x02.toByte(), 0xf6.toByte(), 0x44.toByte(), + 0xeb.toByte(), 0xf9.toByte(), 0xc6.toByte(), 0x03.toByte(), 0x26.toByte(), 0x37.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x7e.toByte(), 0x06.toByte(), + 0xfe.toByte(), 0xfa.toByte(), 0x99.toByte(), 0x06.toByte(), 0xb8.toByte(), 0x39.toByte(), 0xfe.toByte(), 0xff.toByte(), 0x06.toByte(), 0x0a.toByte(), + 0x78.toByte(), 0x66.toByte(), 0x06.toByte(), 0x44.toByte(), 0x46.toByte(), 0xfa.toByte(), 0x02.toByte(), 0x33.toByte(), 0xb8.toByte(), 0x99.toByte(), + 0xfa.toByte(), 0xf6.toByte(), 0x19.toByte(), 0x44.toByte(), 0x1a.toByte(), 0x24.toByte(), 0x0b.toByte(), 0x27.toByte(), 0x19.toByte(), 0xe7.toByte(), + 0x26.toByte(), 0xf7.toByte(), 0xf4.toByte(), 0xd7.toByte(), 0xe9.toByte(), 0x08.toByte(), 0x53.toByte(), 0xc9.toByte(), 0x27.toByte(), 0xcd.toByte(), + 0x3b.toByte(), 0x06.toByte(), 0x04.toByte(), 0xe7.toByte(), 0x29.toByte(), 0xf6.toByte(), 0x22.toByte(), 0xf7.toByte(), 0x04.toByte(), 0xe9.toByte(), + 0x19.toByte(), 0x04.toByte(), 0x35.toByte(), 0xe6.toByte(), 0xfb.toByte(), 0xec.toByte(), 0x1b.toByte(), 0x46.toByte(), 0x16.toByte(), 0xf2.toByte(), + 0xe2.toByte(), 0x16.toByte(), 0x56.toByte(), 0xf7.toByte(), 0x96.toByte(), 0xd8.toByte(), 0x49.toByte(), 0x19.toByte(), 0xf4.toByte(), 0x36.toByte(), + 0x44.toByte(), 0x0b.toByte(), 0xe4.toByte(), 0xe9.toByte(), 0x17.toByte(), 0x08.toByte(), 0xf6.toByte(), 0x24.toByte(), 0xc8.toByte(), 0xf7.toByte(), + 0xdb.toByte(), 0x12.toByte(), 0x27.toByte(), 0xe5.toByte(), 0xe9.toByte(), 0xe9.toByte(), 0x47.toByte(), 0xe8.toByte(), 0x0b.toByte(), 0x07.toByte(), + 0x47.toByte(), 0x2b.toByte(), 0x1a.toByte(), 0xe8.toByte(), 0xc4.toByte(), 0x11.toByte(), 0x1b.toByte(), 0x09.toByte(), 0x10.toByte(), 0x07.toByte(), + 0x00.toByte(), 0xa2.toByte(), 0xd4.toByte(), 0x75.toByte(), 0x82.toByte(), 0x33.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), + 0xeb.toByte(), 0x04.toByte(), 0x08.toByte(), 0x06.toByte(), 0x5d.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x0c.toByte(), 0xf0.toByte(), 0xa7.toByte(), + 0xe2.toByte(), 0x03.toByte(), 0xd8.toByte(), 0x0b.toByte(), 0xc9.toByte(), 0x77.toByte(), 0xe5.toByte(), 0x3a.toByte(), 0xc6.toByte(), 0x1b.toByte(), + 0x35.toByte(), 0x35.toByte(), 0xe2.toByte(), 0x00.toByte(), 0x26.toByte(), 0x79.toByte(), 0xf9.toByte(), 0xe5.toByte(), 0x7a.toByte(), 0x36.toByte(), + 0x02.toByte(), 0x46.toByte(), 0x34.toByte(), 0x0e.toByte(), 0x44.toByte(), 0x06.toByte(), 0xf6.toByte(), 0x07.toByte(), 0x46.toByte(), 0x1a.toByte(), + 0xb8.toByte(), 0xd7.toByte(), 0xfd.toByte(), 0xf9.toByte(), 0x2b.toByte(), 0xe6.toByte(), 0xf8.toByte(), 0x99.toByte(), 0x0d.toByte(), 0x07.toByte(), + 0x16.toByte(), 0x16.toByte(), 0x86.toByte(), 0xd6.toByte(), 0x01.toByte(), 0x7b.toByte(), 0x19.toByte(), 0xf6.toByte(), 0x86.toByte(), 0x67.toByte(), + 0x02.toByte(), 0x47.toByte(), 0xe6.toByte(), 0xd9.toByte(), 0xbb.toByte(), 0x09.toByte(), 0x0e.toByte(), 0x03.toByte(), 0xe4.toByte(), 0x01.toByte(), + 0x82.toByte(), 0xd9.toByte(), 0xe1.toByte(), 0x44.toByte(), 0x26.toByte(), 0x26.toByte(), 0x45.toByte(), 0xf7.toByte(), 0xf9.toByte(), 0x3d.toByte(), + 0xf8.toByte(), 0x05.toByte(), 0x06.toByte(), 0xb6.toByte(), 0x01.toByte(), 0x84.toByte(), 0xe9.toByte(), 0x01.toByte(), 0x84.toByte(), 0xe6.toByte(), + 0x05.toByte(), 0x05.toByte(), 0x16.toByte(), 0xfe.toByte(), 0x46.toByte(), 0x39.toByte(), 0x0e.toByte(), 0x02.toByte(), 0x2e.toByte(), 0x27.toByte(), + 0x19.toByte(), 0x1b.toByte(), 0xb4.toByte(), 0xec.toByte(), 0xe6.toByte(), 0x0b.toByte(), 0x24.toByte(), 0x16.toByte(), 0xf6.toByte(), 0x03.toByte(), + 0xf6.toByte(), 0x26.toByte(), 0x2b.toByte(), 0x16.toByte(), 0x1a.toByte(), 0x15.toByte(), 0xe6.toByte(), 0x07.toByte(), 0x49.toByte(), 0x17.toByte(), + 0xf9.toByte(), 0xd4.toByte(), 0x19.toByte(), 0xf4.toByte(), 0x19.toByte(), 0x08.toByte(), 0x49.toByte(), 0xf9.toByte(), 0xe8.toByte(), 0x17.toByte(), + 0x27.toByte(), 0xe4.toByte(), 0xf8.toByte(), 0xd6.toByte(), 0xe8.toByte(), 0x16.toByte(), 0xd6.toByte(), 0x55.toByte(), 0xf4.toByte(), 0x26.toByte(), + 0xd6.toByte(), 0x07.toByte(), 0x13.toByte(), 0x17.toByte(), 0xe5.toByte(), 0x19.toByte(), 0xca.toByte(), 0x1d.toByte(), 0x09.toByte(), 0x26.toByte(), + 0x19.toByte(), 0xdb.toByte(), 0xd4.toByte(), 0x29.toByte(), 0x09.toByte(), 0x46.toByte(), 0x37.toByte(), 0x22.toByte(), 0x06.toByte(), 0x00.toByte(), + 0x0e.toByte(), 0x44.toByte(), 0xd9.toByte(), 0xfd.toByte(), 0x86.toByte(), 0x19.toByte(), 0xfe.toByte(), 0x05.toByte(), 0x86.toByte(), 0x0a.toByte(), + 0x46.toByte(), 0xd7.toByte(), 0x09.toByte(), 0xbe.toByte(), 0x09.toByte(), 0xe2.toByte(), 0xc4.toByte(), 0xf8.toByte(), 0x09.toByte(), 0x02.toByte(), + 0x16.toByte(), 0x36.toByte(), 0x16.toByte(), 0xe7.toByte(), 0x32.toByte(), 0x29.toByte(), 0x28.toByte(), 0x27.toByte(), 0x04.toByte(), 0xc4.toByte(), + 0xc9.toByte(), 0xf9.toByte(), 0x26.toByte(), 0x04.toByte(), 0x06.toByte(), 0xe9.toByte(), 0xec.toByte(), 0xb9.toByte(), 0x27.toByte(), 0x01.toByte(), + 0x44.toByte(), 0xf7.toByte(), 0xda.toByte(), 0xf6.toByte(), 0xe1.toByte(), 0x00.toByte(), 0x16.toByte(), 0x85.toByte(), 0x06.toByte(), 0xfe.toByte(), + 0xf0.toByte(), 0xf6.toByte(), 0x01.toByte(), 0x86.toByte(), 0xf6.toByte(), 0x0d.toByte(), 0x80.toByte(), 0x09.toByte(), 0x16.toByte(), 0x44.toByte(), + 0x89.toByte(), 0x09.toByte(), 0xbb.toByte(), 0x08.toByte(), 0xfa.toByte(), 0x02.toByte(), 0x06.toByte(), 0xfa.toByte(), 0xc6.toByte(), 0xa6.toByte(), + 0xf9.toByte(), 0xc4.toByte(), 0xe6.toByte(), 0xf5.toByte(), 0x8e.toByte(), 0xe9.toByte(), 0xf9.toByte(), 0x87.toByte(), 0xc6.toByte(), 0x09.toByte(), + 0x46.toByte(), 0x26.toByte(), 0xfe.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xf4.toByte(), 0xf7.toByte(), 0x04.toByte(), 0x0b.toByte(), 0xd5.toByte(), + 0x0b.toByte(), 0x12.toByte(), 0x26.toByte(), 0x25.toByte(), 0x15.toByte(), 0xcb.toByte(), 0xfd.toByte(), 0x00.toByte(), 0x02.toByte(), 0x04.toByte(), + 0x46.toByte(), 0x12.toByte(), 0xc5.toByte(), 0xb6.toByte(), 0xfd.toByte(), 0xfb.toByte(), 0x95.toByte(), 0xf2.toByte(), 0x10.toByte(), 0x07.toByte(), + 0x00.toByte(), 0x3a.toByte(), 0xf2.toByte(), 0xad.toByte(), 0x20.toByte(), 0x30.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), + 0x06.toByte(), 0x05.toByte(), 0x1c.toByte(), 0x06.toByte(), 0x5a.toByte(), 0xf9.toByte(), 0x00.toByte(), 0x05.toByte(), 0x75.toByte(), 0x0b.toByte(), + 0x06.toByte(), 0x39.toByte(), 0xc9.toByte(), 0x0d.toByte(), 0x04.toByte(), 0x02.toByte(), 0x16.toByte(), 0xf3.toByte(), 0x22.toByte(), 0x17.toByte(), + 0xfa.toByte(), 0x08.toByte(), 0xab.toByte(), 0xfb.toByte(), 0xdb.toByte(), 0x06.toByte(), 0x16.toByte(), 0x35.toByte(), 0x05.toByte(), 0x16.toByte(), + 0x25.toByte(), 0xe8.toByte(), 0x12.toByte(), 0xe7.toByte(), 0x09.toByte(), 0xc8.toByte(), 0xfa.toByte(), 0x34.toByte(), 0x17.toByte(), 0xd6.toByte(), + 0x17.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x07.toByte(), 0x15.toByte(), 0x02.toByte(), 0xbb.toByte(), 0xc9.toByte(), 0xd1.toByte(), 0xb9.toByte(), + 0x09.toByte(), 0x0a.toByte(), 0xc4.toByte(), 0xf6.toByte(), 0x09.toByte(), 0x86.toByte(), 0xf9.toByte(), 0xf9.toByte(), 0x06.toByte(), 0x16.toByte(), + 0x0e.toByte(), 0x02.toByte(), 0x2e.toByte(), 0x05.toByte(), 0x17.toByte(), 0x15.toByte(), 0xeb.toByte(), 0x59.toByte(), 0xcd.toByte(), 0x34.toByte(), + 0xc4.toByte(), 0x29.toByte(), 0xeb.toByte(), 0xe9.toByte(), 0x16.toByte(), 0xe6.toByte(), 0x08.toByte(), 0x00.toByte(), 0x16.toByte(), 0x19.toByte(), + 0xfa.toByte(), 0xf2.toByte(), 0x34.toByte(), 0x16.toByte(), 0x4d.toByte(), 0xd4.toByte(), 0xf7.toByte(), 0xc4.toByte(), 0x19.toByte(), 0x22.toByte(), + 0x26.toByte(), 0xf7.toByte(), 0xad.toByte(), 0xe8.toByte(), 0x48.toByte(), 0x13.toByte(), 0x09.toByte(), 0xe4.toByte(), 0x1d.toByte(), 0x26.toByte(), + 0x39.toByte(), 0x25.toByte(), 0xd9.toByte(), 0xe6.toByte(), 0xd9.toByte(), 0x35.toByte(), 0x55.toByte(), 0x27.toByte(), 0xc8.toByte(), 0x98.toByte(), + 0xfa.toByte(), 0x46.toByte(), 0x76.toByte(), 0x07.toByte(), 0xd7.toByte(), 0x96.toByte(), 0xf2.toByte(), 0x06.toByte(), 0x39.toByte(), 0x69.toByte(), + 0x07.toByte(), 0xd6.toByte(), 0xc7.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x97.toByte(), 0xe4.toByte(), 0x21.toByte(), 0x38.toByte(), 0x06.toByte(), + 0xd9.toByte(), 0x78.toByte(), 0x2b.toByte(), 0x16.toByte(), 0x07.toByte(), 0x36.toByte(), 0x2e.toByte(), 0xc1.toByte(), 0x19.toByte(), 0xfe.toByte(), + 0x07.toByte(), 0x98.toByte(), 0x01.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x29.toByte(), 0xe9.toByte(), 0xe8.toByte(), 0xf4.toByte(), 0xe8.toByte(), + 0x06.toByte(), 0x39.toByte(), 0x07.toByte(), 0xf4.toByte(), 0xd4.toByte(), 0x0a.toByte(), 0x39.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xf8.toByte(), + 0x19.toByte(), 0x0a.toByte(), 0x79.toByte(), 0xf6.toByte(), 0x09.toByte(), 0xc4.toByte(), 0x07.toByte(), 0xfa.toByte(), 0x07.toByte(), 0xe6.toByte(), + 0xe5.toByte(), 0xf9.toByte(), 0x29.toByte(), 0xfa.toByte(), 0x05.toByte(), 0x16.toByte(), 0x02.toByte(), 0x02.toByte(), 0x16.toByte(), 0x0b.toByte(), + 0xeb.toByte(), 0x14.toByte(), 0x04.toByte(), 0x32.toByte(), 0x18.toByte(), 0x0b.toByte(), 0xe9.toByte(), 0xf4.toByte(), 0x00.toByte(), 0xdb.toByte(), + 0xf9.toByte(), 0xef.toByte(), 0xf6.toByte(), 0x27.toByte(), 0x15.toByte(), 0x26.toByte(), 0x27.toByte(), 0xf5.toByte(), 0x04.toByte(), 0xc8.toByte(), + 0xf8.toByte(), 0x39.toByte(), 0x35.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x07.toByte(), 0xe6.toByte(), 0x05.toByte(), 0x38.toByte(), 0xb9.toByte(), + 0x2a.toByte(), 0x87.toByte(), 0x18.toByte(), 0x1a.toByte(), 0xb5.toByte(), 0x9b.toByte(), 0x09.toByte(), 0x46.toByte(), 0x46.toByte(), 0x02.toByte(), + 0xc5.toByte(), 0x46.toByte(), 0xfa.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xf4.toByte(), 0xc7.toByte(), 0x18.toByte(), 0x4b.toByte(), 0xe6.toByte(), + 0xf8.toByte(), 0xd7.toByte(), 0x48.toByte(), 0xd2.toByte(), 0x26.toByte(), 0xc7.toByte(), 0x26.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x84.toByte(), + 0x09.toByte(), 0x0a.toByte(), 0x7d.toByte(), 0xb9.toByte(), 0x1d.toByte(), 0xb9.toByte(), 0x16.toByte(), 0x06.toByte(), 0x86.toByte(), 0x1b.toByte(), + 0xfa.toByte(), 0xc4.toByte(), 0x16.toByte(), 0x12.toByte(), 0xc7.toByte(), 0xe9.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x00.toByte(), 0xe7.toByte(), + 0x19.toByte(), 0x36.toByte(), 0x34.toByte(), 0xc7.toByte(), 0x06.toByte(), 0xcb.toByte(), 0x19.toByte(), 0xf6.toByte(), 0x11.toByte(), 0x07.toByte(), + 0x00.toByte(), 0x40.toByte(), 0x4b.toByte(), 0x9a.toByte(), 0xbf.toByte(), 0x30.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), + 0xf7.toByte(), 0x04.toByte(), 0x17.toByte(), 0x06.toByte(), 0x5f.toByte(), 0xf9.toByte(), 0x05.toByte(), 0x07.toByte(), 0xc7.toByte(), 0x07.toByte(), + 0x00.toByte(), 0x0e.toByte(), 0x86.toByte(), 0xf9.toByte(), 0x05.toByte(), 0xb9.toByte(), 0xc6.toByte(), 0x1e.toByte(), 0x07.toByte(), 0x16.toByte(), + 0x02.toByte(), 0x73.toByte(), 0x08.toByte(), 0x06.toByte(), 0x39.toByte(), 0x26.toByte(), 0x02.toByte(), 0x38.toByte(), 0xe6.toByte(), 0x05.toByte(), + 0x02.toByte(), 0x0e.toByte(), 0xf8.toByte(), 0xe8.toByte(), 0xe6.toByte(), 0xf4.toByte(), 0x11.toByte(), 0xf5.toByte(), 0x1a.toByte(), 0x1a.toByte(), + 0xd8.toByte(), 0x32.toByte(), 0x00.toByte(), 0x27.toByte(), 0x00.toByte(), 0x16.toByte(), 0x3b.toByte(), 0xd9.toByte(), 0xf9.toByte(), 0xf8.toByte(), + 0xe9.toByte(), 0x09.toByte(), 0x38.toByte(), 0x29.toByte(), 0x02.toByte(), 0x42.toByte(), 0xf6.toByte(), 0x09.toByte(), 0x3b.toByte(), 0x76.toByte(), + 0xfe.toByte(), 0xc6.toByte(), 0xa4.toByte(), 0x02.toByte(), 0x3f.toByte(), 0x16.toByte(), 0xde.toByte(), 0xf1.toByte(), 0xd6.toByte(), 0xf9.toByte(), + 0x06.toByte(), 0x29.toByte(), 0xf6.toByte(), 0x07.toByte(), 0x27.toByte(), 0x0a.toByte(), 0xf8.toByte(), 0xf9.toByte(), 0x01.toByte(), 0x79.toByte(), + 0x19.toByte(), 0x0e.toByte(), 0x02.toByte(), 0x16.toByte(), 0xd4.toByte(), 0x1b.toByte(), 0xf8.toByte(), 0xf9.toByte(), 0x03.toByte(), 0x39.toByte(), + 0x25.toByte(), 0xe8.toByte(), 0xe5.toByte(), 0x16.toByte(), 0x19.toByte(), 0x19.toByte(), 0xe4.toByte(), 0x29.toByte(), 0xe6.toByte(), 0x18.toByte(), + 0xd5.toByte(), 0xf6.toByte(), 0x09.toByte(), 0xf8.toByte(), 0xf9.toByte(), 0xe6.toByte(), 0x29.toByte(), 0x66.toByte(), 0x00.toByte(), 0x0e.toByte(), + 0x38.toByte(), 0x06.toByte(), 0x06.toByte(), 0x06.toByte(), 0x46.toByte(), 0x0a.toByte(), 0xf9.toByte(), 0x36.toByte(), 0x06.toByte(), 0x46.toByte(), + 0xe6.toByte(), 0xf1.toByte(), 0x3d.toByte(), 0xc6.toByte(), 0x01.toByte(), 0x86.toByte(), 0xb4.toByte(), 0x06.toByte(), 0x02.toByte(), 0x0e.toByte(), + 0xe4.toByte(), 0xc4.toByte(), 0xdf.toByte(), 0x16.toByte(), 0xd6.toByte(), 0x57.toByte(), 0xf6.toByte(), 0xf7.toByte(), 0xed.toByte(), 0x09.toByte(), + 0x17.toByte(), 0xd0.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xc6.toByte(), 0xe6.toByte(), 0x05.toByte(), 0xbe.toByte(), 0x06.toByte(), 0x0e.toByte(), + 0xb9.toByte(), 0x69.toByte(), 0xfa.toByte(), 0xb8.toByte(), 0x08.toByte(), 0x0e.toByte(), 0x78.toByte(), 0x16.toByte(), 0x0a.toByte(), 0xb9.toByte(), + 0xf6.toByte(), 0x05.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xe7.toByte(), 0xf5.toByte(), 0x07.toByte(), 0xf7.toByte(), 0x04.toByte(), 0xe4.toByte(), + 0xeb.toByte(), 0xc9.toByte(), 0xeb.toByte(), 0x34.toByte(), 0x44.toByte(), 0x36.toByte(), 0x00.toByte(), 0x26.toByte(), 0x7b.toByte(), 0xc6.toByte(), + 0xf5.toByte(), 0x38.toByte(), 0x59.toByte(), 0x0a.toByte(), 0x06.toByte(), 0x44.toByte(), 0x1a.toByte(), 0xfb.toByte(), 0xf8.toByte(), 0xed.toByte(), + 0xc6.toByte(), 0xd9.toByte(), 0x05.toByte(), 0xb9.toByte(), 0x14.toByte(), 0x06.toByte(), 0x82.toByte(), 0x36.toByte(), 0xde.toByte(), 0x78.toByte(), + 0xf9.toByte(), 0x0d.toByte(), 0xf8.toByte(), 0xe6.toByte(), 0x05.toByte(), 0xc0.toByte(), 0xe9.toByte(), 0x01.toByte(), 0x45.toByte(), 0x97.toByte(), + 0xfd.toByte(), 0xc7.toByte(), 0x06.toByte(), 0xfe.toByte(), 0x07.toByte(), 0x16.toByte(), 0xda.toByte(), 0x39.toByte(), 0x26.toByte(), 0xfa.toByte(), + 0x78.toByte(), 0x26.toByte(), 0x0e.toByte(), 0x7b.toByte(), 0x16.toByte(), 0x06.toByte(), 0xfc.toByte(), 0x26.toByte(), 0x02.toByte(), 0x06.toByte(), + 0x25.toByte(), 0xea.toByte(), 0x07.toByte(), 0xe7.toByte(), 0xd9.toByte(), 0x7b.toByte(), 0x16.toByte(), 0xf2.toByte(), 0xc5.toByte(), 0x39.toByte(), + 0x0e.toByte(), 0x3b.toByte(), 0xd6.toByte(), 0xf5.toByte(), 0xbb.toByte(), 0x16.toByte(), 0xf6.toByte(), 0x86.toByte(), 0x96.toByte(), 0x0e.toByte(), + 0x02.toByte(), 0x0d.toByte(), 0x37.toByte(), 0x04.toByte(), 0xea.toByte(), 0x06.toByte(), 0xdb.toByte(), 0xf9.toByte(), 0xe6.toByte(), 0x49.toByte(), + 0x18.toByte(), 0x17.toByte(), 0x16.toByte(), 0x05.toByte(), 0xf8.toByte(), 0xf9.toByte(), 0x0b.toByte(), 0xf6.toByte(), 0x09.toByte(), 0x11.toByte(), + 0x07.toByte(), 0x00.toByte(), 0x19.toByte(), 0xe6.toByte(), 0x7b.toByte(), 0x5f.toByte(), 0x31.toByte(), 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), + 0x86.toByte(), 0xf4.toByte(), 0x04.toByte(), 0x0b.toByte(), 0x06.toByte(), 0x51.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x02.toByte(), 0x28.toByte(), + 0xc2.toByte(), 0x0b.toByte(), 0xe6.toByte(), 0x1a.toByte(), 0x17.toByte(), 0x00.toByte(), 0x26.toByte(), 0x00.toByte(), 0x56.toByte(), 0x0a.toByte(), + 0xc2.toByte(), 0xd9.toByte(), 0x01.toByte(), 0xf9.toByte(), 0xb9.toByte(), 0x05.toByte(), 0x39.toByte(), 0xe6.toByte(), 0x05.toByte(), 0x44.toByte(), + 0xd9.toByte(), 0xfd.toByte(), 0xf9.toByte(), 0xe6.toByte(), 0x06.toByte(), 0x07.toByte(), 0xf6.toByte(), 0xf1.toByte(), 0xb2.toByte(), 0x06.toByte(), + 0x06.toByte(), 0x04.toByte(), 0xf6.toByte(), 0xfd.toByte(), 0x39.toByte(), 0xf6.toByte(), 0x15.toByte(), 0xc7.toByte(), 0x16.toByte(), 0x02.toByte(), + 0x86.toByte(), 0x18.toByte(), 0xea.toByte(), 0x86.toByte(), 0x16.toByte(), 0x0a.toByte(), 0x06.toByte(), 0xe7.toByte(), 0xf5.toByte(), 0x04.toByte(), + 0xf6.toByte(), 0xf9.toByte(), 0x79.toByte(), 0xe4.toByte(), 0x01.toByte(), 0x79.toByte(), 0x16.toByte(), 0xda.toByte(), 0x05.toByte(), 0xf6.toByte(), + 0xf9.toByte(), 0x87.toByte(), 0x07.toByte(), 0xfe.toByte(), 0x47.toByte(), 0xf9.toByte(), 0xf1.toByte(), 0xb9.toByte(), 0x06.toByte(), 0x16.toByte(), + 0xf9.toByte(), 0x17.toByte(), 0xfe.toByte(), 0xc7.toByte(), 0x19.toByte(), 0xd6.toByte(), 0xf9.toByte(), 0x36.toByte(), 0xf6.toByte(), 0x02.toByte(), + 0x36.toByte(), 0x44.toByte(), 0x07.toByte(), 0x38.toByte(), 0xfd.toByte(), 0x29.toByte(), 0x14.toByte(), 0x11.toByte(), 0xe4.toByte(), 0x0b.toByte(), + 0xca.toByte(), 0xe9.toByte(), 0xe6.toByte(), 0x41.toByte(), 0xf5.toByte(), 0xf2.toByte(), 0xcf.toByte(), 0x57.toByte(), 0x26.toByte(), 0xf0.toByte(), + 0xb9.toByte(), 0x08.toByte(), 0xff.toByte(), 0x1b.toByte(), 0x26.toByte(), 0x31.toByte(), 0xd7.toByte(), 0xd6.toByte(), 0x0c.toByte(), 0x68.toByte(), + 0xf5.toByte(), 0xd2.toByte(), 0xe2.toByte(), 0x39.toByte(), 0x4d.toByte(), 0x17.toByte(), 0xc9.toByte(), 0x02.toByte(), 0xd8.toByte(), 0x37.toByte(), + 0xf9.toByte(), 0xf4.toByte(), 0xfa.toByte(), 0x0a.toByte(), 0x48.toByte(), 0x05.toByte(), 0x04.toByte(), 0xd9.toByte(), 0x14.toByte(), 0x29.toByte(), + 0x14.toByte(), 0x09.toByte(), 0xe8.toByte(), 0x4b.toByte(), 0xe8.toByte(), 0x11.toByte(), 0x06.toByte(), 0x19.toByte(), 0xec.toByte(), 0xa4.toByte(), + 0x24.toByte(), 0x05.toByte(), 0x56.toByte(), 0xc8.toByte(), 0x49.toByte(), 0xba.toByte(), 0x57.toByte(), 0xf4.toByte(), 0x52.toByte(), 0x07.toByte(), + 0x09.toByte(), 0xf7.toByte(), 0xe8.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x46.toByte(), 0xd9.toByte(), 0xf9.toByte(), 0x4e.toByte(), 0xf9.toByte(), + 0x01.toByte(), 0x79.toByte(), 0x59.toByte(), 0x01.toByte(), 0x47.toByte(), 0x26.toByte(), 0x02.toByte(), 0x44.toByte(), 0xf6.toByte(), 0x15.toByte(), + 0x78.toByte(), 0xf6.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x01.toByte(), 0xe5.toByte(), 0x02.toByte(), 0x08.toByte(), 0x19.toByte(), + 0xf9.toByte(), 0xcb.toByte(), 0xd4.toByte(), 0x17.toByte(), 0x04.toByte(), 0x28.toByte(), 0x0a.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x7b.toByte(), + 0x16.toByte(), 0xfe.toByte(), 0x56.toByte(), 0x16.toByte(), 0x06.toByte(), 0x87.toByte(), 0x16.toByte(), 0xfd.toByte(), 0x06.toByte(), 0x19.toByte(), + 0xfe.toByte(), 0x80.toByte(), 0x19.toByte(), 0xfa.toByte(), 0xfb.toByte(), 0xe6.toByte(), 0x01.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x36.toByte(), + 0xe6.toByte(), 0x03.toByte(), 0x26.toByte(), 0x20.toByte(), 0x2c.toByte(), 0xd4.toByte(), 0x3f.toByte(), 0xf5.toByte(), 0x39.toByte(), 0xb4.toByte(), + 0x0b.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xc6.toByte(), 0x29.toByte(), 0x06.toByte(), 0x79.toByte(), 0x36.toByte(), 0x0e.toByte(), 0xb8.toByte(), + 0x24.toByte(), 0x0a.toByte(), 0x3d.toByte(), 0xe6.toByte(), 0xf5.toByte(), 0x38.toByte(), 0x19.toByte(), 0x16.toByte(), 0x46.toByte(), 0x09.toByte(), + 0x0a.toByte(), 0x02.toByte(), 0x0d.toByte(), 0xa7.toByte(), 0x36.toByte(), 0x09.toByte(), 0x06.toByte(), 0x99.toByte(), 0xe5.toByte(), 0xf5.toByte(), + 0x77.toByte(), 0x28.toByte(), 0x28.toByte(), 0xc6.toByte(), 0x26.toByte(), 0xf6.toByte(), 0x08.toByte(), 0xc4.toByte(), 0x07.toByte(), 0x04.toByte(), + 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), 0xc3.toByte(), 0xcc.toByte(), 0xa4.toByte(), 0xf2.toByte(), 0x31.toByte(), 0x99.toByte(), 0x13.toByte(), + 0x0c.toByte(), 0x86.toByte(), 0xe9.toByte(), 0x04.toByte(), 0x09.toByte(), 0x06.toByte(), 0x5e.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x02.toByte(), + 0xd6.toByte(), 0x44.toByte(), 0xd6.toByte(), 0x39.toByte(), 0xf8.toByte(), 0x17.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xf8.toByte(), 0x29.toByte(), + 0xfa.toByte(), 0x87.toByte(), 0xe6.toByte(), 0x09.toByte(), 0xb9.toByte(), 0x19.toByte(), 0xfa.toByte(), 0x85.toByte(), 0x09.toByte(), 0x0a.toByte(), + 0x3b.toByte(), 0x86.toByte(), 0x02.toByte(), 0x86.toByte(), 0xd9.toByte(), 0xf9.toByte(), 0x02.toByte(), 0x16.toByte(), 0xef.toByte(), 0x14.toByte(), + 0x37.toByte(), 0x0b.toByte(), 0x26.toByte(), 0xd2.toByte(), 0x08.toByte(), 0xeb.toByte(), 0xcb.toByte(), 0x02.toByte(), 0xe0.toByte(), 0x56.toByte(), + 0x36.toByte(), 0x0d.toByte(), 0xf9.toByte(), 0xe5.toByte(), 0x14.toByte(), 0x32.toByte(), 0xfc.toByte(), 0x1d.toByte(), 0xfd.toByte(), 0x13.toByte(), + 0x01.toByte(), 0x24.toByte(), 0x00.toByte(), 0x16.toByte(), 0xc6.toByte(), 0xc6.toByte(), 0x31.toByte(), 0xcc.toByte(), 0x09.toByte(), 0xfa.toByte(), + 0x46.toByte(), 0x3b.toByte(), 0xfd.toByte(), 0xba.toByte(), 0xe9.toByte(), 0x01.toByte(), 0x84.toByte(), 0x06.toByte(), 0x06.toByte(), 0x05.toByte(), + 0xf6.toByte(), 0xfd.toByte(), 0x46.toByte(), 0x06.toByte(), 0x16.toByte(), 0x84.toByte(), 0x26.toByte(), 0x02.toByte(), 0x39.toByte(), 0xf8.toByte(), + 0xf9.toByte(), 0x86.toByte(), 0x09.toByte(), 0x0a.toByte(), 0x06.toByte(), 0xc6.toByte(), 0x15.toByte(), 0x07.toByte(), 0x09.toByte(), 0x02.toByte(), + 0x02.toByte(), 0x16.toByte(), 0xc0.toByte(), 0xfb.toByte(), 0x09.toByte(), 0x64.toByte(), 0x27.toByte(), 0xf6.toByte(), 0xaa.toByte(), 0xe7.toByte(), + 0xc7.toByte(), 0x64.toByte(), 0x04.toByte(), 0xf8.toByte(), 0xd7.toByte(), 0x04.toByte(), 0x24.toByte(), 0x56.toByte(), 0x17.toByte(), 0x26.toByte(), + 0xb6.toByte(), 0x49.toByte(), 0xec.toByte(), 0x4b.toByte(), 0xc8.toByte(), 0xd8.toByte(), 0x00.toByte(), 0x26.toByte(), 0xc7.toByte(), 0x08.toByte(), + 0x06.toByte(), 0x01.toByte(), 0xf7.toByte(), 0x19.toByte(), 0x06.toByte(), 0xf6.toByte(), 0x01.toByte(), 0x07.toByte(), 0x28.toByte(), 0x02.toByte(), + 0xc6.toByte(), 0xe9.toByte(), 0x01.toByte(), 0x3a.toByte(), 0xf6.toByte(), 0x05.toByte(), 0x38.toByte(), 0x06.toByte(), 0x06.toByte(), 0x47.toByte(), + 0x17.toByte(), 0x02.toByte(), 0xba.toByte(), 0xc6.toByte(), 0xf5.toByte(), 0x46.toByte(), 0x49.toByte(), 0xfa.toByte(), 0x02.toByte(), 0xc6.toByte(), + 0x12.toByte(), 0x3b.toByte(), 0x27.toByte(), 0x06.toByte(), 0xfe.toByte(), 0x19.toByte(), 0xfe.toByte(), 0xbd.toByte(), 0x66.toByte(), 0xf2.toByte(), + 0x44.toByte(), 0x59.toByte(), 0x0e.toByte(), 0x31.toByte(), 0x06.toByte(), 0x0e.toByte(), 0xb8.toByte(), 0x79.toByte(), 0x02.toByte(), 0x06.toByte(), + 0x76.toByte(), 0xfa.toByte(), 0x3e.toByte(), 0x07.toByte(), 0x06.toByte(), 0x70.toByte(), 0x76.toByte(), 0xf2.toByte(), 0x06.toByte(), 0x56.toByte(), + 0xf2.toByte(), 0xfd.toByte(), 0x29.toByte(), 0x02.toByte(), 0x78.toByte(), 0x56.toByte(), 0x02.toByte(), 0x39.toByte(), 0x26.toByte(), 0xfa.toByte(), + 0x02.toByte(), 0x16.toByte(), 0x0c.toByte(), 0x19.toByte(), 0x38.toByte(), 0xe2.toByte(), 0xf6.toByte(), 0xf2.toByte(), 0x0b.toByte(), 0x14.toByte(), + 0xfb.toByte(), 0x09.toByte(), 0xe6.toByte(), 0x08.toByte(), 0x29.toByte(), 0x26.toByte(), 0xf2.toByte(), 0x19.toByte(), 0x1b.toByte(), 0xfb.toByte(), + 0xb9.toByte(), 0xd6.toByte(), 0x36.toByte(), 0x47.toByte(), 0x14.toByte(), 0x2b.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xc0.toByte(), 0x06.toByte(), + 0x32.toByte(), 0x8a.toByte(), 0xc9.toByte(), 0xf1.toByte(), 0x78.toByte(), 0x7b.toByte(), 0xf9.toByte(), 0x47.toByte(), 0x16.toByte(), 0x02.toByte(), + 0x86.toByte(), 0x06.toByte(), 0xfe.toByte(), 0xb9.toByte(), 0xf9.toByte(), 0xfd.toByte(), 0x02.toByte(), 0x09.toByte(), 0x56.toByte(), 0x07.toByte(), + 0x07.toByte(), 0xc8.toByte(), 0xf7.toByte(), 0x0b.toByte(), 0x09.toByte(), 0x16.toByte(), 0xf9.toByte(), 0xf9.toByte(), 0x27.toByte(), 0x07.toByte(), + 0x27.toByte(), 0xe6.toByte(), 0x35.toByte(), 0x25.toByte(), 0x6b.toByte(), 0xea.toByte(), 0xd8.toByte(), 0xd2.toByte(), 0xe7.toByte(), 0x36.toByte(), + 0x08.toByte(), 0x10.toByte(), 0x07.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xca.toByte(), 0xb8.toByte(), 0x8e.toByte(), 0x3e.toByte(), 0x99.toByte(), + 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xf7.toByte(), 0x04.toByte(), 0x16.toByte(), 0x06.toByte(), 0x5e.toByte(), 0xf9.toByte(), 0x00.toByte(), + 0x0e.toByte(), 0xc7.toByte(), 0xe9.toByte(), 0x2d.toByte(), 0x06.toByte(), 0xf6.toByte(), 0xf9.toByte(), 0x47.toByte(), 0xfb.toByte(), 0x0d.toByte(), + 0x47.toByte(), 0x06.toByte(), 0xfa.toByte(), 0xb8.toByte(), 0xe9.toByte(), 0xfd.toByte(), 0x87.toByte(), 0x46.toByte(), 0x0e.toByte(), 0x02.toByte(), + 0x0e.toByte(), 0x29.toByte(), 0xca.toByte(), 0x38.toByte(), 0x05.toByte(), 0x47.toByte(), 0xf2.toByte(), 0xf8.toByte(), 0xf9.toByte(), 0x19.toByte(), + 0xc5.toByte(), 0xd9.toByte(), 0xf5.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x44.toByte(), 0x17.toByte(), 0x16.toByte(), 0x3c.toByte(), 0xd6.toByte(), + 0x05.toByte(), 0x39.toByte(), 0x75.toByte(), 0x0e.toByte(), 0x78.toByte(), 0xb9.toByte(), 0xc9.toByte(), 0xfd.toByte(), 0xf6.toByte(), 0x01.toByte(), + 0x40.toByte(), 0x36.toByte(), 0xfe.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xd4.toByte(), 0x08.toByte(), 0x28.toByte(), 0x39.toByte(), 0x19.toByte(), + 0xe5.toByte(), 0x19.toByte(), 0xd6.toByte(), 0x2c.toByte(), 0x03.toByte(), 0x38.toByte(), 0xd4.toByte(), 0x00.toByte(), 0x16.toByte(), 0xfd.toByte(), + 0x29.toByte(), 0xfe.toByte(), 0xfa.toByte(), 0x36.toByte(), 0xfa.toByte(), 0x86.toByte(), 0xa9.toByte(), 0x16.toByte(), 0x78.toByte(), 0xc6.toByte(), + 0x05.toByte(), 0xf1.toByte(), 0x49.toByte(), 0xfa.toByte(), 0xf8.toByte(), 0x07.toByte(), 0x06.toByte(), 0xb8.toByte(), 0x26.toByte(), 0xe2.toByte(), + 0xc4.toByte(), 0x39.toByte(), 0xfa.toByte(), 0xc7.toByte(), 0x27.toByte(), 0x06.toByte(), 0x3b.toByte(), 0x06.toByte(), 0xda.toByte(), 0xb9.toByte(), + 0x36.toByte(), 0xfa.toByte(), 0x46.toByte(), 0xf7.toByte(), 0xfd.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x39.toByte(), 0xcb.toByte(), 0x06.toByte(), + 0xe4.toByte(), 0xe9.toByte(), 0x18.toByte(), 0x27.toByte(), 0x55.toByte(), 0x05.toByte(), 0xe9.toByte(), 0xf4.toByte(), 0xc7.toByte(), 0x00.toByte(), + 0x16.toByte(), 0x3a.toByte(), 0x26.toByte(), 0xfe.toByte(), 0x79.toByte(), 0x06.toByte(), 0x02.toByte(), 0xc7.toByte(), 0xe9.toByte(), 0x01.toByte(), + 0x85.toByte(), 0xf6.toByte(), 0x05.toByte(), 0x06.toByte(), 0xe6.toByte(), 0x25.toByte(), 0x06.toByte(), 0x27.toByte(), 0xfa.toByte(), 0x86.toByte(), + 0x2b.toByte(), 0xfa.toByte(), 0xfa.toByte(), 0xf9.toByte(), 0xf9.toByte(), 0x38.toByte(), 0x56.toByte(), 0xfe.toByte(), 0x47.toByte(), 0xd9.toByte(), + 0xf9.toByte(), 0x38.toByte(), 0x07.toByte(), 0xfa.toByte(), 0x79.toByte(), 0x16.toByte(), 0xfa.toByte(), 0x02.toByte(), 0x1e.toByte(), 0x69.toByte(), + 0xf4.toByte(), 0xf6.toByte(), 0xe8.toByte(), 0x17.toByte(), 0x44.toByte(), 0xd4.toByte(), 0x1d.toByte(), 0xe4.toByte(), 0x06.toByte(), 0xe7.toByte(), + 0xe9.toByte(), 0x15.toByte(), 0x16.toByte(), 0x26.toByte(), 0xe8.toByte(), 0x04.toByte(), 0x09.toByte(), 0x29.toByte(), 0x02.toByte(), 0xe4.toByte(), + 0x96.toByte(), 0x3b.toByte(), 0xe9.toByte(), 0x6b.toByte(), 0xc7.toByte(), 0x39.toByte(), 0x13.toByte(), 0x58.toByte(), 0x25.toByte(), 0x1b.toByte(), + 0xe9.toByte(), 0xea.toByte(), 0x64.toByte(), 0xe7.toByte(), 0x09.toByte(), 0x00.toByte(), 0x0e.toByte(), 0xf8.toByte(), 0x09.toByte(), 0x06.toByte(), + 0x44.toByte(), 0x06.toByte(), 0xe6.toByte(), 0x39.toByte(), 0x06.toByte(), 0x06.toByte(), 0xc6.toByte(), 0x07.toByte(), 0x06.toByte(), 0x84.toByte(), + 0x16.toByte(), 0xe2.toByte(), 0x47.toByte(), 0xd6.toByte(), 0xf1.toByte(), 0x02.toByte(), 0x0e.toByte(), 0x59.toByte(), 0x17.toByte(), 0x07.toByte(), + 0xd4.toByte(), 0xd7.toByte(), 0x15.toByte(), 0x06.toByte(), 0xf9.toByte(), 0x09.toByte(), 0x79.toByte(), 0x25.toByte(), 0xd9.toByte(), 0x00.toByte(), + 0x0b.toByte(), 0x8e.toByte(), 0xd8.toByte(), 0x09.toByte(), 0x84.toByte(), 0xd6.toByte(), 0xf1.toByte(), 0xf9.toByte(), 0x09.toByte(), 0x0a.toByte(), + 0x06.toByte(), 0x06.toByte(), 0xfa.toByte(), 0xfb.toByte(), 0x99.toByte(), 0x01.toByte(), 0x86.toByte(), 0xd9.toByte(), 0xfd.toByte(), 0x0f.toByte(), + 0xf6.toByte(), 0x2d.toByte(), 0x82.toByte(), 0xc6.toByte(), 0x05.toByte(), 0x07.toByte(), 0xfb.toByte(), 0x05.toByte(), 0xc3.toByte(), 0xf9.toByte(), + 0x05.toByte(), 0x11.toByte(), 0x07.toByte(), 0x00.toByte(), 0x03.toByte(), 0x60.toByte(), 0xf7.toByte(), 0x2c.toByte(), 0x3f.toByte(), 0x99.toByte(), + 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xf5.toByte(), 0x04.toByte(), 0x08.toByte(), 0x06.toByte(), 0x50.toByte(), 0xf9.toByte(), 0x02.toByte(), + 0x04.toByte(), 0xfc.toByte(), 0x67.toByte(), 0x26.toByte(), 0x00.toByte(), 0x0e.toByte(), 0x4e.toByte(), 0x26.toByte(), 0xe6.toByte(), 0xc6.toByte(), + 0xa9.toByte(), 0x01.toByte(), 0x38.toByte(), 0x07.toByte(), 0x0e.toByte(), 0x46.toByte(), 0x46.toByte(), 0x0a.toByte(), 0x39.toByte(), 0xb9.toByte(), + 0xfd.toByte(), 0xc6.toByte(), 0xf9.toByte(), 0x01.toByte(), 0x02.toByte(), 0x36.toByte(), 0xf3.toByte(), 0xf6.toByte(), 0x03.toByte(), 0xe9.toByte(), + 0x18.toByte(), 0xc6.toByte(), 0x0b.toByte(), 0x19.toByte(), 0x1a.toByte(), 0x04.toByte(), 0xd4.toByte(), 0x34.toByte(), 0x26.toByte(), 0x5d.toByte(), + 0xf9.toByte(), 0x09.toByte(), 0xd4.toByte(), 0x18.toByte(), 0x02.toByte(), 0x24.toByte(), 0x03.toByte(), 0xfc.toByte(), 0xf8.toByte(), 0xed.toByte(), + 0x07.toByte(), 0xf7.toByte(), 0x24.toByte(), 0x33.toByte(), 0xf9.toByte(), 0x18.toByte(), 0xe8.toByte(), 0xf6.toByte(), 0x47.toByte(), 0x19.toByte(), + 0xb9.toByte(), 0xd6.toByte(), 0x16.toByte(), 0x67.toByte(), 0x14.toByte(), 0x07.toByte(), 0xeb.toByte(), 0xfb.toByte(), 0x16.toByte(), 0x17.toByte(), + 0xf7.toByte(), 0x05.toByte(), 0x09.toByte(), 0xe8.toByte(), 0x18.toByte(), 0x14.toByte(), 0x32.toByte(), 0xd6.toByte(), 0xd9.toByte(), 0x0b.toByte(), + 0x17.toByte(), 0x15.toByte(), 0x17.toByte(), 0xd9.toByte(), 0x0d.toByte(), 0xf7.toByte(), 0x56.toByte(), 0xf4.toByte(), 0x28.toByte(), 0xe6.toByte(), + 0xf6.toByte(), 0x06.toByte(), 0xf9.toByte(), 0x19.toByte(), 0xf7.toByte(), 0x30.toByte(), 0x05.toByte(), 0x0b.toByte(), 0x00.toByte(), 0x1e.toByte(), + 0x3c.toByte(), 0xe9.toByte(), 0x0d.toByte(), 0x4e.toByte(), 0x37.toByte(), 0x02.toByte(), 0x46.toByte(), 0xd9.toByte(), 0xe9.toByte(), 0x3b.toByte(), + 0x16.toByte(), 0xfa.toByte(), 0x47.toByte(), 0x06.toByte(), 0x0a.toByte(), 0x04.toByte(), 0xd7.toByte(), 0xf9.toByte(), 0xf9.toByte(), 0xd9.toByte(), + 0x05.toByte(), 0x02.toByte(), 0x16.toByte(), 0x02.toByte(), 0x47.toByte(), 0x89.toByte(), 0x09.toByte(), 0xc4.toByte(), 0xf9.toByte(), 0x09.toByte(), + 0x4f.toByte(), 0xd9.toByte(), 0xf9.toByte(), 0xb9.toByte(), 0x79.toByte(), 0x01.toByte(), 0x49.toByte(), 0x27.toByte(), 0x0e.toByte(), 0x43.toByte(), + 0x59.toByte(), 0xed.toByte(), 0xf9.toByte(), 0xd9.toByte(), 0x01.toByte(), 0xf8.toByte(), 0x46.toByte(), 0xfa.toByte(), 0xc6.toByte(), 0x29.toByte(), + 0x02.toByte(), 0x78.toByte(), 0xf6.toByte(), 0x01.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xea.toByte(), 0x54.toByte(), 0x04.toByte(), 0x32.toByte(), + 0xdd.toByte(), 0x1b.toByte(), 0xb9.toByte(), 0xe5.toByte(), 0x37.toByte(), 0x1b.toByte(), 0x37.toByte(), 0xf6.toByte(), 0x00.toByte(), 0x16.toByte(), + 0xb8.toByte(), 0xd6.toByte(), 0x11.toByte(), 0xb8.toByte(), 0x19.toByte(), 0x0e.toByte(), 0xb8.toByte(), 0xf8.toByte(), 0x15.toByte(), 0x87.toByte(), + 0x19.toByte(), 0x2a.toByte(), 0x05.toByte(), 0x79.toByte(), 0x0a.toByte(), 0xc6.toByte(), 0xfb.toByte(), 0x09.toByte(), 0x7e.toByte(), 0x08.toByte(), + 0x02.toByte(), 0xb8.toByte(), 0xe9.toByte(), 0x05.toByte(), 0xf9.toByte(), 0x28.toByte(), 0xfe.toByte(), 0x87.toByte(), 0xd6.toByte(), 0x25.toByte(), + 0x79.toByte(), 0x47.toByte(), 0x0e.toByte(), 0x87.toByte(), 0x29.toByte(), 0xfe.toByte(), 0x02.toByte(), 0x1e.toByte(), 0x0f.toByte(), 0x06.toByte(), + 0x28.toByte(), 0xd2.toByte(), 0xf2.toByte(), 0xf6.toByte(), 0x28.toByte(), 0x49.toByte(), 0x17.toByte(), 0x04.toByte(), 0xaa.toByte(), 0x26.toByte(), + 0xf9.toByte(), 0xf6.toByte(), 0xc7.toByte(), 0x17.toByte(), 0x27.toByte(), 0x09.toByte(), 0xe6.toByte(), 0x54.toByte(), 0x07.toByte(), 0x33.toByte(), + 0xa9.toByte(), 0x09.toByte(), 0xec.toByte(), 0xe8.toByte(), 0x02.toByte(), 0x32.toByte(), 0xf5.toByte(), 0x29.toByte(), 0xc8.toByte(), 0x36.toByte(), + 0xe8.toByte(), 0x52.toByte(), 0x45.toByte(), 0x04.toByte(), 0x00.toByte(), 0x01.toByte(), 0x47.toByte(), 0xd9.toByte(), 0xf9.toByte(), 0x38.toByte(), + 0x86.toByte(), 0x01.toByte(), 0x84.toByte(), 0xf9.toByte(), 0x05.toByte(), 0x44.toByte(), 0x19.toByte(), 0x06.toByte(), 0x85.toByte(), 0xe9.toByte(), + 0x09.toByte(), 0x06.toByte(), 0x84.toByte(), 0x06.toByte(), 0x00.toByte(), 0xb6.toByte(), 0x60.toByte(), 0x5d.toByte(), 0x71.toByte(), 0x3f.toByte(), + 0x99.toByte(), 0x13.toByte(), 0x0c.toByte(), 0x86.toByte(), 0xf5.toByte(), 0x04.toByte(), 0x08.toByte(), 0x06.toByte(), 0x5d.toByte(), 0xf9.toByte(), + 0x02.toByte(), 0x26.toByte(), 0x07.toByte(), 0x0a.toByte(), 0x08.toByte(), 0x33.toByte(), 0xc4.toByte(), 0x17.toByte(), 0xdf.toByte(), 0x37.toByte(), + 0x18.toByte(), 0x04.toByte(), 0xf9.toByte(), 0xc6.toByte(), 0xf9.toByte(), 0x07.toByte(), 0x16.toByte(), 0x27.toByte(), 0xe6.toByte(), 0x19.toByte(), + 0x19.toByte(), 0xe8.toByte(), 0xe6.toByte(), 0x37.toByte(), 0x57.toByte(), 0x17.toByte(), 0xa6.toByte(), 0xd7.toByte(), 0x16.toByte(), 0x77.toByte(), + 0x28.toByte(), 0x26.toByte(), 0xc8.toByte(), 0xf6.toByte(), 0xeb.toByte(), 0x54.toByte(), 0xf9.toByte(), 0x07.toByte(), 0xb9.toByte(), 0xe9.toByte(), + 0x06.toByte(), 0x27.toByte(), 0x44.toByte(), 0x16.toByte(), 0xeb.toByte(), 0xe7.toByte(), 0x04.toByte(), 0x23.toByte(), 0xf7.toByte(), 0xe9.toByte(), + 0x00.toByte(), 0x16.toByte(), 0xb8.toByte(), 0xc9.toByte(), 0x09.toByte(), 0x46.toByte(), 0x26.toByte(), 0x06.toByte(), 0x04.toByte(), 0x16.toByte(), + 0xfa.toByte(), 0xfb.toByte(), 0x08.toByte(), 0xf6.toByte(), 0x79.toByte(), 0x87.toByte(), 0x0a.toByte(), 0x46.toByte(), 0x46.toByte(), 0xfe.toByte(), + 0xbe.toByte(), 0xc6.toByte(), 0xf1.toByte(), 0x06.toByte(), 0x66.toByte(), 0xfe.toByte(), 0xc7.toByte(), 0xd9.toByte(), 0x0d.toByte(), 0xfd.toByte(), + 0xf6.toByte(), 0x09.toByte(), 0x02.toByte(), 0x86.toByte(), 0xfe.toByte(), 0x45.toByte(), 0x07.toByte(), 0xf2.toByte(), 0x02.toByte(), 0x16.toByte(), + 0xfd.toByte(), 0xd8.toByte(), 0x08.toByte(), 0xf6.toByte(), 0xc6.toByte(), 0x27.toByte(), 0x2a.toByte(), 0x29.toByte(), 0x2d.toByte(), 0x09.toByte(), + 0x24.toByte(), 0xe4.toByte(), 0xd6.toByte(), 0xf9.toByte(), 0x29.toByte(), 0x27.toByte(), 0x16.toByte(), 0x03.toByte(), 0xfa.toByte(), 0x37.toByte(), + 0xca.toByte(), 0x14.toByte(), 0xe5.toByte(), 0x37.toByte() + ) + } + + private val offlineFrameWithoutData = byteArrayOf( + // index type data: + // 0 security strategy + 0x00.toByte(), + // 1..16 header + 0x2b.toByte(), 0x4c.toByte(), 0x7c.toByte(), 0x3d.toByte(), 0x01.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x00.toByte(), 0xed.toByte(), 0xb7.toByte(), 0x3b.toByte(), 0x5e.toByte(), + // 17..36 start time + 0x32.toByte(), 0x30.toByte(), 0x31.toByte(), 0x37.toByte(), 0x2d.toByte(), 0x30.toByte(), 0x31.toByte(), 0x2d.toByte(), 0x30.toByte(), 0x31.toByte(), + 0x20.toByte(), 0x31.toByte(), 0x30.toByte(), 0x3a.toByte(), 0x32.toByte(), 0x36.toByte(), 0x3a.toByte(), 0x32.toByte(), 0x37.toByte(), 0x00.toByte(), + // 37 settings length + 0x00.toByte(), + // 38 payload security strategy + 0x00.toByte(), + ) + + private object MockAccOfflineRecordingData { + // index type data: + // 0 security 00 + // 1..4 magic 2B 4C 7C 3D + val headerMagic: UInt = 0x3D7C4C2Bu + + // 5..8 version 01 00 00 00 + val headerVersion: UInt = 0x01u + + // 9..12 free 00 00 00 00 + val headerFree: UInt = 0x00u + + // 13..16 eswHash 04 79 C3 5F + val headerEswHash: UInt = 0x5FC37904u + + // 17..36 startTime + val expectedStartTime = String( + byteArrayOf( + 0x32.toByte(), 0x30.toByte(), 0x31.toByte(), 0x37.toByte(), 0x2D.toByte(), 0x30.toByte(), 0x31.toByte(), 0x2D.toByte(), 0x31.toByte(), 0x39.toByte(), + 0x20.toByte(), 0x30.toByte(), 0x38.toByte(), 0x3A.toByte(), 0x33.toByte(), 0x32.toByte(), 0x3A.toByte(), 0x35.toByte(), 0x32.toByte(), 0x00.toByte(), + ) + ) + + private val startTimeInIso8601 = String(expectedStartTime.dropLast(1).toByteArray()).replace(' ', 'T') + "Z" + val startTime: Calendar = DateTime(startTimeInIso8601).toCalendar(null) + + // index type data: + // 37..43 settings 34 00 10 00 08 00 03 + // 37..38 sample rate 34 00 + const val expectedSampleRate = 0x0034 + + // 39..40 resolution 10 00 + const val expectedResolution = 0x0010 + + // 41..42 range 08 00 + const val expectedRange = 0x0008 + + // 43 channels 03 + const val expectedChannels = 0x03 + + // index type data: + // 64..69 reference sample 0xF5, 0xFF, 0x1C, 0x00, 0xE5, 0x03, + // Sample 0 (aka. reference sample): + // channel 0: FF F5 => 0xFFF5 => -11 + const val sample0Channel0 = -11 + + // channel 1: 1C 00 => 0x001C => 28 + const val sample0Channel1 = 28 + + // channel 2: E5 03 => 0x03E5 => 997 + const val sample0Channel2 = 997 + + // 70: Delta size 0x04 (4bits) + // 71: Sample amount 0x13 (Delta block contains 19 samples) + // 72: Sample 1 - channel 0, size 4 bits: 1111 | 0001 0xF1 + const val sample1Channel0 = sample0Channel0 + 1 + const val sample1Channel1 = sample0Channel1 - 1 + + // 73: Sample 1 - channel 2, size 4 bits: 1110 | 0000 0xE0 + const val sample1Channel2 = sample0Channel2 + 0 + + const val expectedLastSampleTimeStamp = 0x777D25DB22F2730uL //extracted from accOfflineFrame + val accOfflineFrame = byteArrayOf( + 0x00.toByte(), + 0x2B.toByte(), 0x4C.toByte(), 0x7C.toByte(), 0x3D.toByte(), 0x01.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x00.toByte(), 0x04.toByte(), 0x79.toByte(), 0xC3.toByte(), 0x5F.toByte(), + 0x32.toByte(), 0x30.toByte(), 0x31.toByte(), 0x37.toByte(), 0x2D.toByte(), 0x30.toByte(), 0x31.toByte(), 0x2D.toByte(), 0x31.toByte(), 0x39.toByte(), + 0x20.toByte(), 0x30.toByte(), 0x38.toByte(), 0x3A.toByte(), 0x33.toByte(), 0x32.toByte(), 0x3A.toByte(), 0x35.toByte(), 0x32.toByte(), 0x00.toByte(), + 0x0F.toByte(), + 0x00.toByte(), 0x01.toByte(), 0x34.toByte(), 0x00.toByte(), + 0x01.toByte(), 0x01.toByte(), 0x10.toByte(), 0x00.toByte(), + 0x02.toByte(), 0x01.toByte(), 0x08.toByte(), 0x00.toByte(), + 0x04.toByte(), 0x01.toByte(), 0x03.toByte(), + 0x00.toByte(), + + 0xE4.toByte(), 0x00.toByte(), + 0x02.toByte(), + 0x24.toByte(), 0x0C.toByte(), 0xB5.toByte(), 0x20.toByte(), 0x5C.toByte(), 0xD2.toByte(), 0x77.toByte(), 0x07.toByte(), + 0x81.toByte(), + 0xF5.toByte(), 0xFF.toByte(), 0x1C.toByte(), 0x00.toByte(), 0xE5.toByte(), 0x03.toByte(), + 0x04.toByte(), 0x13.toByte(), + 0xF1.toByte(), 0xE0.toByte(), + 0x53.toByte(), + 0xE0.toByte(), 0x30.toByte(), 0xBF.toByte(), 0x1E.toByte(), 0x01.toByte(), 0x3E.toByte(), 0x1F.toByte(), 0x2B.toByte(), 0x14.toByte(), 0xDF.toByte(), 0x02.toByte(), 0x00.toByte(), 0x1F.toByte(), + 0x00.toByte(), 0xF0.toByte(), 0xE3.toByte(), 0xD5.toByte(), 0xB3.toByte(), 0xE3.toByte(), 0xC3.toByte(), 0xDF.toByte(), 0xF1.toByte(), 0x22.toByte(), 0xC2.toByte(), 0x1D.toByte(), 0x04.toByte(), + 0x06.toByte(), 0x14.toByte(), 0x84.toByte(), 0x10.toByte(), 0xF0.toByte(), 0xFE.toByte(), 0x3F.toByte(), 0xEC.toByte(), 0x3F.toByte(), 0x30.toByte(), 0xFC.toByte(), 0x00.toByte(), 0x11.toByte(), + 0xF8.toByte(), 0x7B.toByte(), 0x10.toByte(), 0x24.toByte(), 0xBE.toByte(), 0x8F.toByte(), 0x1B.toByte(), 0x80.toByte(), 0xA0.toByte(), 0x13.toByte(), 0x02.toByte(), 0xB0.toByte(), 0xF7.toByte(), + 0x00.toByte(), 0x01.toByte(), 0xF4.toByte(), 0x80.toByte(), 0x20.toByte(), 0xF4.toByte(), 0x3C.toByte(), 0x30.toByte(), 0x08.toByte(), 0x44.toByte(), 0x00.toByte(), 0xFC.toByte(), 0x3D.toByte(), + 0x0F.toByte(), 0x00.toByte(), 0xC4.toByte(), 0x2E.toByte(), 0x00.toByte(), 0x46.toByte(), 0x0F.toByte(), 0xF8.toByte(), 0x04.toByte(), 0x14.toByte(), 0x1F.toByte(), 0x34.toByte(), 0xBF.toByte(), + 0x21.toByte(), 0xF4.toByte(), 0xAC.toByte(), 0x31.toByte(), 0x01.toByte(), 0x1F.toByte(), 0x1E.toByte(), 0x03.toByte(), 0x24.toByte(), 0xD1.toByte(), 0xE9.toByte(), 0x20.toByte(), 0xEF.toByte(), + 0x46.toByte(), 0x0E.toByte(), 0x0E.toByte(), 0x4A.toByte(), 0x04.toByte(), 0xFF.toByte(), 0xD4.toByte(), 0xE4.toByte(), 0xAF.toByte(), 0x4D.toByte(), 0x12.toByte(), 0x0E.toByte(), 0xF5.toByte(), + 0xCC.toByte(), 0x06.toByte(), 0x14.toByte(), 0x02.toByte(), 0x01.toByte(), 0xF8.toByte(), 0x40.toByte(), 0x01.toByte(), 0x08.toByte(), 0xBA.toByte(), 0x2F.toByte(), 0xF8.toByte(), 0xC1.toByte(), + 0x8F.toByte(), 0x04.toByte(), 0x3E.toByte(), 0x2F.toByte(), 0xF0.toByte(), 0x7D.toByte(), 0x10.toByte(), 0x10.toByte(), 0xFB.toByte(), 0x10.toByte(), 0x00.toByte(), 0xFA.toByte(), 0xEE.toByte(), + 0x17.toByte(), 0x82.toByte(), 0x01.toByte(), 0xF8.toByte(), 0x41.toByte(), 0x00.toByte(), 0xF4.toByte(), 0x80.toByte(), 0x20.toByte(), 0xF8.toByte(), 0xFE.toByte(), 0xFF.toByte(), 0x0B.toByte(), + 0x41.toByte(), 0x30.toByte(), 0xF4.toByte(), 0xC0.toByte(), 0x1F.toByte(), 0xFC.toByte(), 0xFF.toByte(), 0x00.toByte(), 0x00.toByte(), 0x04.toByte(), 0x14.toByte(), 0x5F.toByte(), 0x0E.toByte(), + 0x0C.toByte(), 0x01.toByte(), 0xC0.toByte(), 0xF2.toByte(), 0xE1.toByte(), 0x02.toByte(), 0x3F.toByte(), 0x4D.toByte(), 0x4D.toByte(), 0x2D.toByte(), 0x2E.toByte(), 0x0D.toByte(), 0x20.toByte(), + 0xF3.toByte(), 0xE1.toByte(), 0xEF.toByte(), 0x31.toByte(), 0xE2.toByte(), 0x2C.toByte(), 0xE3.toByte(), 0xFF.toByte(), 0xD2.toByte(), 0xF0.toByte(), 0x03.toByte(), 0xB5.toByte(), 0xCF.toByte(), + 0x06.toByte(), 0xC1.toByte(), 0x06.toByte(), 0x09.toByte(), 0xBE.toByte(), 0x3F.toByte(), 0x0C.toByte(), 0x05.toByte(), 0x30.toByte(), 0xF8.toByte(), 0x3B.toByte(), 0x1F.toByte(), 0xF8.toByte(), + 0x40.toByte(), 0x5F.toByte(), 0xF0.toByte(), 0x88.toByte(), 0x9F.toByte(), 0xF0.toByte(), 0x41.toByte(), 0xFF.toByte(), 0xF7.toByte(), 0x7F.toByte(), 0x7F.toByte(), 0x00.toByte(), 0xE4.toByte(), + 0x00.toByte(), 0x02.toByte(), 0x7A.toByte(), 0xFE.toByte(), 0x3E.toByte(), 0xA4.toByte(), 0x5C.toByte(), 0xD2.toByte(), 0x77.toByte(), 0x07.toByte(), 0x81.toByte(), 0xF3.toByte(), 0xFF.toByte(), + 0x1C.toByte(), 0x00.toByte(), 0xED.toByte(), 0x03.toByte(), 0x04.toByte(), 0x1E.toByte(), 0x33.toByte(), 0xFA.toByte(), 0x4E.toByte(), 0xDE.toByte(), 0x29.toByte(), 0x22.toByte(), 0xEE.toByte(), + 0x05.toByte(), 0xBF.toByte(), 0x12.toByte(), 0x02.toByte(), 0x10.toByte(), 0xF0.toByte(), 0xFC.toByte(), 0x53.toByte(), 0x5F.toByte(), 0x2C.toByte(), 0x2B.toByte(), 0x4D.toByte(), 0xE0.toByte(), + 0xDB.toByte(), 0x05.toByte(), 0xE4.toByte(), 0xBE.toByte(), 0x12.toByte(), 0xC5.toByte(), 0xAF.toByte(), 0xF5.toByte(), 0xC3.toByte(), 0x02.toByte(), 0x06.toByte(), 0x90.toByte(), 0x20.toByte(), + 0xF3.toByte(), 0xEF.toByte(), 0x47.toByte(), 0xA3.toByte(), 0x1B.toByte(), 0x44.toByte(), 0x1F.toByte(), 0x1D.toByte(), 0x0D.toByte(), 0xEF.toByte(), 0x1E.toByte(), 0x61.toByte(), 0x06.toByte(), + 0x28.toByte(), 0xFA.toByte(), 0x80.toByte(), 0x1B.toByte(), 0x40.toByte(), 0x01.toByte(), 0xFC.toByte(), 0xFD.toByte(), 0xEF.toByte(), 0x07.toByte(), 0xFF.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x7E.toByte(), 0xF0.toByte(), 0xFF.toByte(), 0xC1.toByte(), 0x3F.toByte(), 0xF8.toByte(), 0xC2.toByte(), 0x4E.toByte(), 0xFC.toByte(), 0xFF.toByte(), 0x0E.toByte(), 0x14.toByte(), 0xC1.toByte(), + 0x00.toByte(), 0x10.toByte(), 0x7C.toByte(), 0x9F.toByte(), 0x0F.toByte(), 0x43.toByte(), 0xE0.toByte(), 0xFF.toByte(), 0x84.toByte(), 0xF0.toByte(), 0xFB.toByte(), 0xBF.toByte(), 0x6F.toByte(), + 0xF4.toByte(), 0x85.toByte(), 0x1E.toByte(), 0xEC.toByte(), 0xC0.toByte(), 0x20.toByte(), 0xF8.toByte(), 0x3F.toByte(), 0x81.toByte(), 0x08.toByte(), 0xBC.toByte(), 0xDE.toByte(), 0xFF.toByte(), + 0x7E.toByte(), 0x00.toByte(), 0x14.toByte(), 0xFF.toByte(), 0xA0.toByte(), 0x0F.toByte(), 0x44.toByte(), 0x21.toByte(), 0xF8.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0x07.toByte(), 0x3C.toByte(), + 0xE0.toByte(), 0x0B.toByte(), 0x3D.toByte(), 0xE0.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xE0.toByte(), 0x13.toByte(), 0x3F.toByte(), 0xA0.toByte(), 0xEF.toByte(), 0x7F.toByte(), 0x41.toByte(), + 0x1C.toByte(), 0x7F.toByte(), 0x0F.toByte(), 0x0C.toByte(), 0xC2.toByte(), 0x0D.toByte(), 0x10.toByte(), 0x8B.toByte(), 0xFE.toByte(), 0xF3.toByte(), 0xBF.toByte(), 0x1F.toByte(), 0x2C.toByte(), + 0x04.toByte(), 0x28.toByte(), 0x32.toByte(), 0x2C.toByte(), 0xAD.toByte(), 0x1B.toByte(), 0x33.toByte(), 0x0D.toByte(), 0x1D.toByte(), 0x5F.toByte(), 0x10.toByte(), 0x0D.toByte(), 0x00.toByte(), + 0xD0.toByte(), 0x10.toByte(), 0x21.toByte(), 0x5E.toByte(), 0x50.toByte(), 0x00.toByte(), 0xFE.toByte(), 0x2E.toByte(), 0x0C.toByte(), 0x30.toByte(), 0xDE.toByte(), 0x21.toByte(), 0xCE.toByte(), + 0x32.toByte(), 0xB5.toByte(), 0xCC.toByte(), 0xE1.toByte(), 0x30.toByte(), 0x03.toByte(), 0x40.toByte(), 0xF2.toByte(), 0x00.toByte(), 0xAC.toByte(), 0x40.toByte(), 0x26.toByte(), 0xC1.toByte(), + 0xDD.toByte(), 0xE0.toByte(), 0x13.toByte(), 0xE5.toByte(), 0x0E.toByte(), 0x44.toByte(), 0xBC.toByte(), 0x5C.toByte(), 0x5F.toByte(), 0x1B.toByte(), 0x21.toByte(), 0xD2.toByte(), 0xF0.toByte(), + 0xFF.toByte(), 0xF2.toByte(), 0xF3.toByte(), 0x00.toByte(), 0xEF.toByte(), 0x4F.toByte(), 0xB1.toByte(), 0x40.toByte(), 0xD5.toByte(), 0xDA.toByte(), 0x06.toByte(), 0x04.toByte(), 0x7F.toByte(), + 0x21.toByte(), 0x0C.toByte(), 0x03.toByte(), 0xD0.toByte(), 0xF7.toByte(), 0xBE.toByte(), 0xDF.toByte(), 0xFF.toByte(), 0xE4.toByte(), 0x00.toByte(), 0x02.toByte(), 0x4E.toByte(), 0xA9.toByte(), + 0x5A.toByte(), 0x1C.toByte(), 0x5D.toByte(), 0xD2.toByte(), 0x77.toByte(), 0x07.toByte(), 0x81.toByte(), 0xF2.toByte(), 0xFF.toByte(), 0x21.toByte(), 0x00.toByte(), 0xE7.toByte(), 0x03.toByte(), + 0x06.toByte(), 0x0F.toByte(), 0xC2.toByte(), 0x0D.toByte(), 0x00.toByte(), 0x42.toByte(), 0xFF.toByte(), 0x13.toByte(), 0x43.toByte(), 0xB0.toByte(), 0x07.toByte(), 0x00.toByte(), 0xE0.toByte(), + 0x17.toByte(), 0xC2.toByte(), 0xC0.toByte(), 0xFB.toByte(), 0x81.toByte(), 0x0F.toByte(), 0xF8.toByte(), 0x03.toByte(), 0xF1.toByte(), 0xF3.toByte(), 0xBD.toByte(), 0x20.toByte(), 0x08.toByte(), + 0xBD.toByte(), 0xE0.toByte(), 0x0F.toByte(), 0xFF.toByte(), 0x90.toByte(), 0xF7.toByte(), 0x3D.toByte(), 0x41.toByte(), 0x08.toByte(), 0x00.toByte(), 0x04.toByte(), 0x0A.toByte(), 0xE0.toByte(), + 0xF6.toByte(), 0xE0.toByte(), 0x50.toByte(), 0xCF.toByte(), 0xFD.toByte(), 0xC2.toByte(), 0xEC.toByte(), 0x33.toByte(), 0x46.toByte(), 0xE1.toByte(), 0xDE.toByte(), 0x0F.toByte(), 0xD3.toByte(), + 0xDE.toByte(), 0x06.toByte(), 0x0A.toByte(), 0x02.toByte(), 0x30.toByte(), 0x0C.toByte(), 0xC3.toByte(), 0xFF.toByte(), 0xF7.toByte(), 0x00.toByte(), 0x10.toByte(), 0x10.toByte(), 0x80.toByte(), + 0xA0.toByte(), 0x07.toByte(), 0x3E.toByte(), 0xE1.toByte(), 0x0B.toByte(), 0x3E.toByte(), 0xD0.toByte(), 0xF3.toByte(), 0x04.toByte(), 0x81.toByte(), 0xFC.toByte(), 0x00.toByte(), 0x0E.toByte(), + 0x04.toByte(), 0x14.toByte(), 0xAC.toByte(), 0x17.toByte(), 0xDF.toByte(), 0x11.toByte(), 0x44.toByte(), 0x2E.toByte(), 0x6E.toByte(), 0xD9.toByte(), 0x22.toByte(), 0xF2.toByte(), 0xC0.toByte(), + 0xDE.toByte(), 0x11.toByte(), 0xE1.toByte(), 0x02.toByte(), 0xD3.toByte(), 0x12.toByte(), 0x3D.toByte(), 0x1F.toByte(), 0x0D.toByte(), 0xE3.toByte(), 0x2F.toByte(), 0x24.toByte(), 0xFB.toByte(), + 0x00.toByte(), 0x0F.toByte(), 0x34.toByte(), 0xFF.toByte(), 0x2D.toByte(), 0x0C.toByte(), 0x06.toByte(), 0x14.toByte(), 0xBE.toByte(), 0x10.toByte(), 0x08.toByte(), 0x02.toByte(), 0xD0.toByte(), + 0xEB.toByte(), 0xFD.toByte(), 0x50.toByte(), 0xFC.toByte(), 0xBD.toByte(), 0xDF.toByte(), 0x0B.toByte(), 0x48.toByte(), 0xC1.toByte(), 0xE7.toByte(), 0x00.toByte(), 0x30.toByte(), 0x04.toByte(), + 0x45.toByte(), 0xF0.toByte(), 0xF7.toByte(), 0xBC.toByte(), 0x1F.toByte(), 0xF8.toByte(), 0x03.toByte(), 0x20.toByte(), 0xF8.toByte(), 0x42.toByte(), 0x10.toByte(), 0xFC.toByte(), 0x3F.toByte(), + 0x00.toByte(), 0x10.toByte(), 0x3C.toByte(), 0xB0.toByte(), 0x0F.toByte(), 0x40.toByte(), 0x00.toByte(), 0x04.toByte(), 0x3F.toByte(), 0x00.toByte(), 0x00.toByte(), 0x41.toByte(), 0x0F.toByte(), + 0xE8.toByte(), 0x04.toByte(), 0x14.toByte(), 0xF3.toByte(), 0xF2.toByte(), 0x52.toByte(), 0xD2.toByte(), 0xFC.toByte(), 0x3F.toByte(), 0x6B.toByte(), 0x50.toByte(), 0x0D.toByte(), 0x01.toByte(), + 0xB4.toByte(), 0xC4.toByte(), 0xF4.toByte(), 0xFF.toByte(), 0xF1.toByte(), 0xF1.toByte(), 0x10.toByte(), 0x3C.toByte(), 0x6D.toByte(), 0x00.toByte(), 0xCD.toByte(), 0x21.toByte(), 0x11.toByte(), + 0x4D.toByte(), 0x00.toByte(), 0x3F.toByte(), 0x21.toByte(), 0xFD.toByte(), 0xF0.toByte(), 0xE2.toByte(), 0x06.toByte(), 0x09.toByte(), 0x7F.toByte(), 0x60.toByte(), 0xFC.toByte(), 0x3A.toByte(), + 0x3F.toByte(), 0x10.toByte(), 0x3F.toByte(), 0xF0.toByte(), 0xFB.toByte(), 0xBE.toByte(), 0x4F.toByte(), 0xF8.toByte(), 0x04.toByte(), 0x4F.toByte(), 0xF8.toByte(), 0x01.toByte(), 0x10.toByte(), + 0x0C.toByte(), 0xBD.toByte(), 0x8F.toByte(), 0x03.toByte(), 0xE4.toByte(), 0x00.toByte(), 0x02.toByte(), 0xB2.toByte(), 0x58.toByte(), 0x8A.toByte(), 0x55.toByte(), 0x5D.toByte(), 0xD2.toByte(), + 0x77.toByte(), 0x07.toByte(), 0x81.toByte(), 0xF3.toByte(), 0xFF.toByte(), 0x1D.toByte(), 0x00.toByte(), 0xE8.toByte(), 0x03.toByte(), 0x0C.toByte(), 0x0A.toByte(), 0x03.toByte(), 0xC0.toByte(), + 0xFF.toByte(), 0xFE.toByte(), 0x1F.toByte(), 0x00.toByte(), 0x03.toByte(), 0x20.toByte(), 0x00.toByte(), 0x89.toByte(), 0x7F.toByte(), 0x11.toByte(), 0x64.toByte(), 0xA1.toByte(), 0xF8.toByte(), + 0xD2.toByte(), 0x80.toByte(), 0xA0.toByte(), 0x9B.toByte(), 0xA3.toByte(), 0xD3.toByte(), 0x7F.toByte(), 0x46.toByte(), 0xCF.toByte(), 0x38.toByte(), 0x41.toByte(), 0xE2.toByte(), 0x11.toByte(), + 0xA0.toByte(), 0xF6.toByte(), 0x2C.toByte(), 0x40.toByte(), 0x04.toByte(), 0xEA.toByte(), 0x04.toByte(), 0x07.toByte(), 0x9C.toByte(), 0x4F.toByte(), 0xBD.toByte(), 0x68.toByte(), 0xBF.toByte(), + 0x00.toByte(), 0xBB.toByte(), 0xAF.toByte(), 0xFB.toByte(), 0x0A.toByte(), 0x14.toByte(), 0xF0.toByte(), 0x93.toByte(), 0x20.toByte(), 0xBF.toByte(), 0x11.toByte(), 0x3C.toByte(), 0x37.toByte(), + 0x33.toByte(), 0x75.toByte(), 0x08.toByte(), 0xAE.toByte(), 0x63.toByte(), 0x41.toByte(), 0x01.toByte(), 0x1E.toByte(), 0x91.toByte(), 0x1F.toByte(), 0xD1.toByte(), 0x2F.toByte(), 0x13.toByte(), + 0x39.toByte(), 0x48.toByte(), 0xCF.toByte(), 0x80.toByte(), 0xD2.toByte(), 0x83.toByte(), 0xA4.toByte(), 0x2F.toByte(), 0x3D.toByte(), 0x15.toByte(), 0xA1.toByte(), 0x03.toByte(), 0x7F.toByte(), + 0x7E.toByte(), 0x09.toByte(), 0xF6.toByte(), 0x4F.toByte(), 0xDF.toByte(), 0x55.toByte(), 0xDF.toByte(), 0xF5.toByte(), 0xA3.toByte(), 0x92.toByte(), 0xCD.toByte(), 0xFA.toByte(), 0x5A.toByte(), + 0x08.toByte(), 0xDC.toByte(), 0xF6.toByte(), 0xB3.toByte(), 0xBF.toByte(), 0xC0.toByte(), 0x71.toByte(), 0x87.toByte(), 0x0C.toByte(), 0xCC.toByte(), 0x5F.toByte(), 0x11.toByte(), 0x86.toByte(), + 0x09.toByte(), 0x98.toByte(), 0x08.toByte(), 0x12.toByte(), 0x48.toByte(), 0xFB.toByte(), 0x0F.toByte(), 0xD8.toByte(), 0x1E.toByte(), 0x76.toByte(), 0xF3.toByte(), 0xE6.toByte(), 0x47.toByte(), + 0xBF.toByte(), 0xBB.toByte(), 0xD4.toByte(), 0x0C.toByte(), 0x13.toByte(), 0x87.toByte(), 0x70.toByte(), 0x04.toByte(), 0xD3.toByte(), 0xAF.toByte(), 0x05.toByte(), 0xB9.toByte(), 0xBF.toByte(), + 0x02.toByte(), 0x0A.toByte(), 0x90.toByte(), 0xF5.toByte(), 0xE4.toByte(), 0x6F.toByte(), 0xD8.toByte(), 0x57.toByte(), 0x2F.toByte(), 0xFC.toByte(), 0x01.toByte(), 0x73.toByte(), 0x06.toByte(), + 0xC0.toByte(), 0x1F.toByte(), 0x05.toByte(), 0x4F.toByte(), 0xE0.toByte(), 0xE8.toByte(), 0xD6.toByte(), 0xF0.toByte(), 0xFE.toByte(), 0xEC.toByte(), 0xDF.toByte(), 0xF2.toByte(), 0x76.toByte(), + 0x4F.toByte(), 0x03.toByte(), 0x2B.toByte(), 0x60.toByte(), 0xFB.toByte(), 0x63.toByte(), 0x2F.toByte(), 0x0E.toByte(), 0x0B.toByte(), 0x80.toByte(), 0xE8.toByte(), 0x2E.toByte(), 0xFF.toByte(), + 0x09.toByte(), 0xA8.toByte(), 0x40.toByte(), 0x09.toByte(), 0xD5.toByte(), 0x4F.toByte(), 0xFB.toByte(), 0x32.toByte(), 0x80.toByte(), 0xF9.toByte(), 0x08.toByte(), 0xA0.toByte(), 0xFC.toByte(), + 0xB6.toByte(), 0x2F.toByte(), 0xF2.toByte(), 0xFF.toByte(), 0xEF.toByte(), 0xF7.toByte(), 0x83.toByte(), 0x90.toByte(), 0xF8.toByte(), 0xAB.toByte(), 0x90.toByte(), 0xFB.toByte(), 0x97.toByte(), + 0x90.toByte(), 0xF0.toByte(), 0xC9.toByte(), 0xBF.toByte(), 0xF5.toByte(), 0x59.toByte(), 0x91.toByte(), 0xF8.toByte(), 0x59.toByte(), 0xDF.toByte(), 0x0E.toByte(), 0x68.toByte(), 0x0E.toByte(), + 0xE5.toByte(), 0x00.toByte(), 0x02.toByte(), 0x9A.toByte(), 0x82.toByte(), 0x72.toByte(), 0x94.toByte(), 0x5D.toByte(), 0xD2.toByte(), 0x77.toByte(), 0x07.toByte(), 0x81.toByte(), 0x5A.toByte(), + 0x01.toByte(), 0xE1.toByte(), 0xFE.toByte(), 0x10.toByte(), 0xFD.toByte(), 0x0A.toByte(), 0x0A.toByte(), 0xD8.toByte(), 0x9B.toByte(), 0x31.toByte(), 0x78.toByte(), 0xFD.toByte(), 0x0D.toByte(), + 0xAC.toByte(), 0x3D.toByte(), 0x40.toByte(), 0xFF.toByte(), 0x45.toByte(), 0x9C.toByte(), 0x9D.toByte(), 0xD1.toByte(), 0xBD.toByte(), 0xD5.toByte(), 0xA7.toByte(), 0xB0.toByte(), 0x8B.toByte(), + 0x9D.toByte(), 0x6C.toByte(), 0x97.toByte(), 0x89.toByte(), 0x3B.toByte(), 0x76.toByte(), 0x97.toByte(), 0xD1.toByte(), 0x50.toByte(), 0xB0.toByte(), 0xFB.toByte(), 0x06.toByte(), 0x20.toByte(), + 0x71.toByte(), 0xC7.toByte(), 0x07.toByte(), 0xE9.toByte(), 0xDB.toByte(), 0x00.toByte(), 0x10.toByte(), 0x0A.toByte(), 0x8A.toByte(), 0xFF.toByte(), 0x8B.toByte(), 0xFE.toByte(), 0x86.toByte(), + 0x02.toByte(), 0x37.toByte(), 0x01.toByte(), 0x19.toByte(), 0x01.toByte(), 0xE8.toByte(), 0x00.toByte(), 0x7A.toByte(), 0xFD.toByte(), 0x1B.toByte(), 0x01.toByte(), 0x97.toByte(), 0xF7.toByte(), + 0x32.toByte(), 0x05.toByte(), 0x30.toByte(), 0x00.toByte(), 0xF4.toByte(), 0xFF.toByte(), 0x39.toByte(), 0xFD.toByte(), 0xCB.toByte(), 0xFD.toByte(), 0x64.toByte(), 0x04.toByte(), 0xEF.toByte(), + 0xFF.toByte(), 0x1A.toByte(), 0xFF.toByte(), 0x66.toByte(), 0x00.toByte(), 0x77.toByte(), 0x00.toByte(), 0x2D.toByte(), 0x02.toByte(), 0x7C.toByte(), 0xFF.toByte(), 0xD6.toByte(), 0xFF.toByte(), + 0x44.toByte(), 0xFF.toByte(), 0x36.toByte(), 0x00.toByte(), 0xE8.toByte(), 0xFF.toByte(), 0xA6.toByte(), 0xFF.toByte(), 0x50.toByte(), 0x00.toByte(), 0xCB.toByte(), 0xFF.toByte(), 0x2B.toByte(), + 0x00.toByte(), 0x95.toByte(), 0xFF.toByte(), 0x0A.toByte(), 0x14.toByte(), 0x69.toByte(), 0x10.toByte(), 0x6F.toByte(), 0xD3.toByte(), 0xE1.toByte(), 0x02.toByte(), 0x54.toByte(), 0x3A.toByte(), + 0x82.toByte(), 0x0D.toByte(), 0x29.toByte(), 0x38.toByte(), 0xC1.toByte(), 0x7E.toByte(), 0x07.toByte(), 0xFA.toByte(), 0x93.toByte(), 0x80.toByte(), 0x84.toByte(), 0xF8.toByte(), 0xE3.toByte(), + 0xE7.toByte(), 0x0E.toByte(), 0xC0.toByte(), 0x00.toByte(), 0x0B.toByte(), 0xFC.toByte(), 0x5F.toByte(), 0x00.toByte(), 0x01.toByte(), 0x03.toByte(), 0xF8.toByte(), 0x6F.toByte(), 0x40.toByte(), + 0x00.toByte(), 0x04.toByte(), 0xF4.toByte(), 0xFF.toByte(), 0xBF.toByte(), 0xFE.toByte(), 0x00.toByte(), 0xFC.toByte(), 0xEF.toByte(), 0x7F.toByte(), 0xFF.toByte(), 0x05.toByte(), 0x10.toByte(), + 0xD0.toByte(), 0x3F.toByte(), 0xFF.toByte(), 0xFE.toByte(), 0x13.toByte(), 0x20.toByte(), 0x80.toByte(), 0x00.toByte(), 0xFF.toByte(), 0xEF.toByte(), 0xDF.toByte(), 0xBF.toByte(), 0x01.toByte(), + 0x03.toByte(), 0x0C.toByte(), 0xA0.toByte(), 0xBF.toByte(), 0xFF.toByte(), 0x01.toByte(), 0x14.toByte(), 0x80.toByte(), 0x00.toByte(), 0xFF.toByte(), 0xFE.toByte(), 0xDF.toByte(), 0x2F.toByte(), + 0x00.toByte(), 0x00.toByte(), 0x06.toByte(), 0x0E.toByte(), 0x41.toByte(), 0x3F.toByte(), 0x0C.toByte(), 0xC3.toByte(), 0x0E.toByte(), 0x00.toByte(), 0xC1.toByte(), 0x6F.toByte(), 0xF8.toByte(), + 0xC2.toByte(), 0xDD.toByte(), 0xF3.toByte(), 0x44.toByte(), 0x51.toByte(), 0xFC.toByte(), 0x40.toByte(), 0x50.toByte(), 0xF8.toByte(), 0x3C.toByte(), 0x3F.toByte(), 0x04.toByte(), 0x42.toByte(), + 0x0F.toByte(), 0xF4.toByte(), 0x8A.toByte(), 0x10.toByte(), 0xEC.toByte(), 0x3F.toByte(), 0xF0.toByte(), 0xF7.toByte(), 0x01.toByte(), 0x0F.toByte(), 0x58.toByte(), 0x00.toByte(), 0x02.toByte(), + 0x30.toByte(), 0x27.toByte(), 0x2F.toByte(), 0xB2.toByte(), 0x5D.toByte(), 0xD2.toByte(), 0x77.toByte(), 0x07.toByte(), 0x81.toByte(), 0xBF.toByte(), 0xFF.toByte(), 0x18.toByte(), 0x00.toByte(), + 0x09.toByte(), 0xFC.toByte(), 0x04.toByte(), 0x05.toByte(), 0x11.toByte(), 0x1E.toByte(), 0x3E.toByte(), 0x40.toByte(), 0x34.toByte(), 0xDF.toByte(), 0xF9.toByte(), 0x0E.toByte(), 0x08.toByte(), + 0x14.toByte(), 0x06.toByte(), 0xFC.toByte(), 0xFD.toByte(), 0x00.toByte(), 0x04.toByte(), 0x05.toByte(), 0xFD.toByte(), 0x00.toByte(), 0xFD.toByte(), 0x01.toByte(), 0x02.toByte(), 0xEF.toByte(), + 0x01.toByte(), 0xFB.toByte(), 0x20.toByte(), 0x06.toByte(), 0x02.toByte(), 0xE5.toByte(), 0xF4.toByte(), 0x06.toByte(), 0x12.toByte(), 0x07.toByte(), 0xFD.toByte(), 0xFA.toByte(), 0xFB.toByte(), + 0xFF.toByte(), 0xFF.toByte(), 0x03.toByte(), 0x04.toByte(), 0x08.toByte(), 0xFE.toByte(), 0xFB.toByte(), 0xF9.toByte(), 0x03.toByte(), 0x01.toByte(), 0x01.toByte(), 0x02.toByte(), 0xFF.toByte(), + 0x01.toByte(), 0xFB.toByte(), 0xFE.toByte(), 0xFD.toByte(), 0x04.toByte(), 0xFD.toByte(), 0x05.toByte(), 0xFE.toByte(), 0x07.toByte(), 0xFD.toByte(), 0xFF.toByte(), 0xFD.toByte(), 0xFD.toByte(), + 0x04.toByte(), 0x01.toByte(), 0x06.toByte(), 0xFD.toByte(), 0x01.toByte(), 0x01.toByte(), 0xFE.toByte(), 0xFC.toByte(), 0x00.toByte() + ) + } + + private object GyroOfflineMockData { + private const val factor = 0.07f + + // index type data: + // 60 0x05 + // 61..68 time stamp 0xf2, 0x10, 0x3c, 0x9d, 0x1d, 0x52, 0x72, 0x07, + const val expectedFirstSampleTimeStamp = 0x09ef3726208f6fd8 + + // 69 0x80, + // 70..75 reference sample 0x13, 0xec, 0xc0, 0x0f, 0x0b, 0xee, + // Sample 0 (aka. reference sample): + // channel 0: 13 EC => 0xEC13 => -5101 + const val rawSample0Channel0 = -5101 + const val sample0Channel0 = -5101 * factor + + // channel 1: C0 0F => 0x0FC0 => 4032 + const val rawSample0Channel1 = 4032 + const val sample0Channel1 = 4032 * factor + + // channel 2: 0B EE => 0xEE0B => 4597 + const val rawSample0Channel2 = -4597 + const val sample0Channel2 = -4597 * factor + + // 76: Delta size 0x10 (16bits) + // 77: Sample amount 0x23 (Delta block contains 35 samples) + // 78..79: Sample 1 - channel 0, size 16 bits: 0x9d, 0xfc, + const val sample1Channel0 = (rawSample0Channel0 - 867) * factor + + // 80..81: Sample 1 - channel 1, size 16 bits: 0x0a, 0xf3, + const val sample1Channel1 = (rawSample0Channel1 - 3318) * factor + + // 82..83: Sample 1 - channel 2, size 16 bits: 0x14, 0x0e, + const val sample1Channel2 = (rawSample0Channel2 + 3604) * factor + + const val expectedLastSampleTimeStamp = 0x0772521DC6F82904uL//extracted from [gyroOfflineFrame] + + val gyroOfflineFrame = byteArrayOf( + 0x00.toByte(), + 0x2b.toByte(), 0x4c.toByte(), 0x7c.toByte(), 0x3d.toByte(), 0x01.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x00.toByte(), 0xed.toByte(), 0xb7.toByte(), 0x3b.toByte(), 0x5e.toByte(), + + 0x32.toByte(), 0x30.toByte(), 0x31.toByte(), 0x37.toByte(), 0x2d.toByte(), 0x30.toByte(), 0x31.toByte(), 0x2d.toByte(), 0x30.toByte(), 0x31.toByte(), + 0x20.toByte(), 0x31.toByte(), 0x30.toByte(), 0x3a.toByte(), 0x32.toByte(), 0x36.toByte(), 0x3a.toByte(), 0x33.toByte(), 0x32.toByte(), 0x00.toByte(), + + 0x15.toByte(), + 0x00.toByte(), 0x01.toByte(), 0x34.toByte(), 0x00.toByte(), + 0x01.toByte(), 0x01.toByte(), 0x10.toByte(), 0x00.toByte(), + 0x02.toByte(), 0x01.toByte(), 0xD0.toByte(), 0x07.toByte(), + 0x04.toByte(), 0x01.toByte(), 0x03.toByte(), + 0x05.toByte(), 0x01.toByte(), 0x29.toByte(), 0x5C.toByte(), 0x8F.toByte(), 0x3D.toByte(), + 0x00.toByte(), + 0xe4.toByte(), 0x00.toByte(), + 0x05.toByte(), + 0xf2.toByte(), 0x10.toByte(), 0x3c.toByte(), 0x9d.toByte(), 0x1d.toByte(), 0x52.toByte(), 0x72.toByte(), 0x07.toByte(), + 0x80.toByte(), 0x13.toByte(), 0xec.toByte(), 0xc0.toByte(), 0x0f.toByte(), + 0x0b.toByte(), 0xee.toByte(), 0x10.toByte(), 0x23.toByte(), 0x9d.toByte(), 0xfc.toByte(), 0x0a.toByte(), 0xf3.toByte(), 0x14.toByte(), 0x0e.toByte(), + 0x4f.toByte(), 0xfd.toByte(), 0xee.toByte(), 0xf9.toByte(), 0x32.toByte(), 0x06.toByte(), 0xd4.toByte(), 0xfb.toByte(), 0x78.toByte(), 0xf9.toByte(), + 0xbc.toByte(), 0x0b.toByte(), 0xbb.toByte(), 0xf7.toByte(), 0x93.toByte(), 0xf5.toByte(), 0xea.toByte(), 0x0c.toByte(), 0xac.toByte(), 0xf5.toByte(), + 0x86.toByte(), 0xf8.toByte(), 0x55.toByte(), 0x0a.toByte(), 0x2f.toByte(), 0xfa.toByte(), 0x9d.toByte(), 0xff.toByte(), 0x13.toByte(), 0x03.toByte(), + 0x50.toByte(), 0x08.toByte(), 0x54.toByte(), 0x07.toByte(), 0xcb.toByte(), 0xf1.toByte(), 0x33.toByte(), 0x1f.toByte(), 0xf6.toByte(), 0x0c.toByte(), + 0xdd.toByte(), 0xe2.toByte(), 0xce.toByte(), 0x13.toByte(), 0x19.toByte(), 0x05.toByte(), 0x05.toByte(), 0xf5.toByte(), 0xe2.toByte(), 0xf9.toByte(), + 0x85.toByte(), 0xfb.toByte(), 0x9d.toByte(), 0x0a.toByte(), 0x5c.toByte(), 0xf7.toByte(), 0xb2.toByte(), 0xfc.toByte(), 0xc3.toByte(), 0x0b.toByte(), + 0xee.toByte(), 0xff.toByte(), 0x37.toByte(), 0xfe.toByte(), 0x3d.toByte(), 0x06.toByte(), 0xc9.toByte(), 0x02.toByte(), 0x0e.toByte(), 0xff.toByte(), + 0x0a.toByte(), 0x03.toByte(), 0x76.toByte(), 0xf9.toByte(), 0x11.toByte(), 0xfc.toByte(), 0x01.toByte(), 0x09.toByte(), 0x56.toByte(), 0xf9.toByte(), + 0x56.toByte(), 0x0a.toByte(), 0x0d.toByte(), 0x00.toByte(), 0x15.toByte(), 0xf5.toByte(), 0x79.toByte(), 0x0a.toByte(), 0xf0.toByte(), 0xff.toByte(), + 0x1b.toByte(), 0x02.toByte(), 0xec.toByte(), 0x0a.toByte(), 0x92.toByte(), 0xe9.toByte(), 0xb6.toByte(), 0xf7.toByte(), 0x85.toByte(), 0x06.toByte(), + 0x71.toByte(), 0xf0.toByte(), 0x3f.toByte(), 0x05.toByte(), 0xdc.toByte(), 0xff.toByte(), 0x8a.toByte(), 0x04.toByte(), 0x93.toByte(), 0x01.toByte(), + 0x72.toByte(), 0xf0.toByte(), 0x8c.toByte(), 0x12.toByte(), 0x1c.toByte(), 0x0d.toByte(), 0xaa.toByte(), 0xfb.toByte(), 0x5e.toByte(), 0x09.toByte(), + 0x68.toByte(), 0x05.toByte(), 0xf9.toByte(), 0xfe.toByte(), 0x14.toByte(), 0xf9.toByte(), 0x89.toByte(), 0xfd.toByte(), 0xd6.toByte(), 0xfa.toByte(), + 0x58.toByte(), 0xf9.toByte(), 0x4f.toByte(), 0x01.toByte(), 0x1c.toByte(), 0xfe.toByte(), 0xaf.toByte(), 0xfa.toByte(), 0xcc.toByte(), 0xfd.toByte(), + 0xbe.toByte(), 0xff.toByte(), 0x43.toByte(), 0xfd.toByte(), 0x67.toByte(), 0xff.toByte(), 0xaa.toByte(), 0x01.toByte(), 0xbb.toByte(), 0xfb.toByte(), + 0x09.toByte(), 0x0a.toByte(), 0x82.toByte(), 0x03.toByte(), 0xd2.toByte(), 0x02.toByte(), 0x70.toByte(), 0x02.toByte(), 0x93.toByte(), 0x07.toByte(), + 0x97.toByte(), 0xfa.toByte(), 0x8c.toByte(), 0xf9.toByte(), 0x4d.toByte(), 0xfe.toByte(), 0x23.toByte(), 0xfc.toByte(), 0x44.toByte(), 0x02.toByte(), + 0x10.toByte(), 0xff.toByte(), 0xee.toByte(), 0x07.toByte(), 0x3c.toByte(), 0x0d.toByte(), 0x36.toByte(), 0x05.toByte(), 0xc5.toByte(), 0xff.toByte(), + 0x5f.toByte(), 0x06.toByte(), 0xb7.toByte(), 0x05.toByte(), 0xfb.toByte(), 0xfb.toByte(), 0xed.toByte(), 0xff.toByte(), 0x71.toByte(), 0x03.toByte(), + 0x1c.toByte(), 0xfe.toByte(), 0x87.toByte(), 0x04.toByte(), 0x5e.toByte(), 0xfc.toByte(), 0xfb.toByte(), 0xfb.toByte(), 0xfb.toByte(), 0xf5.toByte(), + 0x4d.toByte(), 0xf9.toByte(), 0x7a.toByte(), 0x05.toByte(), 0xe4.toByte(), 0x00.toByte(), 0x05.toByte(), 0x04.toByte(), 0x29.toByte(), 0xf8.toByte(), + 0xc6.toByte(), 0x1d.toByte(), 0x52.toByte(), 0x72.toByte(), 0x07.toByte(), 0x80.toByte(), 0x36.toByte(), 0xf7.toByte(), 0x7a.toByte(), 0xfd.toByte(), + 0x1e.toByte(), 0x04.toByte(), 0x10.toByte(), 0x23.toByte(), 0x02.toByte(), 0x04.toByte(), 0x38.toByte(), 0xff.toByte(), 0x06.toByte(), 0x01.toByte(), + 0x00.toByte(), 0x08.toByte(), 0x5c.toByte(), 0x04.toByte(), 0x97.toByte(), 0xff.toByte(), 0xd6.toByte(), 0xfd.toByte(), 0xc7.toByte(), 0x00.toByte(), + 0xd1.toByte(), 0xfd.toByte(), 0x7f.toByte(), 0x02.toByte(), 0xc8.toByte(), 0xfe.toByte(), 0x1e.toByte(), 0xfd.toByte(), 0x61.toByte(), 0x00.toByte(), + 0x1e.toByte(), 0x00.toByte(), 0x59.toByte(), 0x00.toByte(), 0x4d.toByte(), 0xfe.toByte(), 0x4b.toByte(), 0x00.toByte(), 0xa1.toByte(), 0x03.toByte(), + 0x77.toByte(), 0xf8.toByte(), 0x14.toByte(), 0xff.toByte(), 0xbc.toByte(), 0x05.toByte(), 0xf4.toByte(), 0xf7.toByte(), 0xec.toByte(), 0xfc.toByte(), + 0xfe.toByte(), 0x03.toByte(), 0xf3.toByte(), 0xf7.toByte(), 0x0b.toByte(), 0xf8.toByte(), 0x44.toByte(), 0x01.toByte(), 0x52.toByte(), 0xff.toByte(), + 0x4e.toByte(), 0xf7.toByte(), 0x73.toByte(), 0x0a.toByte(), 0x10.toByte(), 0xf9.toByte(), 0xd0.toByte(), 0xef.toByte(), 0xe4.toByte(), 0xf3.toByte(), + 0xdb.toByte(), 0x00.toByte(), 0x8d.toByte(), 0xfd.toByte(), 0x4e.toByte(), 0xf7.toByte(), 0x5b.toByte(), 0xff.toByte(), 0xa2.toByte(), 0x01.toByte(), + 0xa7.toByte(), 0xfa.toByte(), 0x6b.toByte(), 0xe8.toByte(), 0xb4.toByte(), 0x06.toByte(), 0x76.toByte(), 0xf4.toByte(), 0x38.toByte(), 0xeb.toByte(), + 0x95.toByte(), 0x06.toByte(), 0x1c.toByte(), 0xfb.toByte(), 0x15.toByte(), 0xfb.toByte(), 0x85.toByte(), 0x2b.toByte(), 0x41.toByte(), 0xfe.toByte(), + 0xda.toByte(), 0x36.toByte(), 0xb1.toByte(), 0x03.toByte(), 0xff.toByte(), 0xfc.toByte(), 0x8d.toByte(), 0x1a.toByte(), 0xf5.toByte(), 0xea.toByte(), + 0xc1.toByte(), 0xfd.toByte(), 0x2d.toByte(), 0x27.toByte(), 0xb5.toByte(), 0xf6.toByte(), 0x7b.toByte(), 0x01.toByte(), 0x82.toByte(), 0x00.toByte(), + 0x98.toByte(), 0xe4.toByte(), 0x1e.toByte(), 0x00.toByte(), 0x4c.toByte(), 0xc9.toByte(), 0x0d.toByte(), 0x09.toByte(), 0x9d.toByte(), 0x01.toByte(), + 0x63.toByte(), 0xf7.toByte(), 0xb5.toByte(), 0x22.toByte(), 0x71.toByte(), 0x03.toByte(), 0xfc.toByte(), 0x14.toByte(), 0x69.toByte(), 0x20.toByte(), + 0x82.toByte(), 0x02.toByte(), 0xbe.toByte(), 0x12.toByte(), 0x52.toByte(), 0xee.toByte(), 0xc3.toByte(), 0x00.toByte(), 0x4f.toByte(), 0xe9.toByte(), + 0x2a.toByte(), 0xe0.toByte(), 0x2b.toByte(), 0x01.toByte(), 0xfb.toByte(), 0xfc.toByte(), 0x84.toByte(), 0xea.toByte(), 0x7f.toByte(), 0x07.toByte(), + 0x86.toByte(), 0x11.toByte(), 0xa9.toByte(), 0x12.toByte(), 0x4b.toByte(), 0x04.toByte(), 0x30.toByte(), 0xe9.toByte(), 0x3e.toByte(), 0x16.toByte(), + 0x4d.toByte(), 0x03.toByte(), 0x19.toByte(), 0x08.toByte(), 0xe2.toByte(), 0xf5.toByte(), 0xa5.toByte(), 0x00.toByte(), 0x56.toByte(), 0x0e.toByte(), + 0x85.toByte(), 0xf6.toByte(), 0xf6.toByte(), 0xfe.toByte(), 0xae.toByte(), 0xf1.toByte(), 0x5f.toByte(), 0x0a.toByte(), 0xab.toByte(), 0xfd.toByte(), + 0x21.toByte(), 0x04.toByte(), 0x64.toByte(), 0xfe.toByte(), 0x6e.toByte(), 0xfe.toByte(), 0x66.toByte(), 0xff.toByte(), 0x4f.toByte(), 0xfa.toByte(), + 0xe6.toByte(), 0xff.toByte(), 0xe2.toByte(), 0xff.toByte(), 0x30.toByte(), 0x07.toByte(), 0x6b.toByte(), 0x00.toByte(), 0x08.toByte(), 0x00.toByte(), + 0x45.toByte(), 0xfd.toByte(), 0x23.toByte(), 0x00.toByte() + ) + } + + private object PpgOfflineMockData { + // index type data: + // 59 0x80, + // 60..71 reference sample 0x5d, 0x3b, 0x00, 0x16, 0x5f, 0x00, 0x10, 0xb7, 0x00, 0x59, 0xe6, 0xfd, + // Sample 0 (aka. reference sample): + // channel 0: 5D 3B 00 => 0x003B5D => 15197 + const val sample0Channel0 = 15197 + + // channel 1: 16 5F 00 => 0x005F16 => 24342 + const val sample0Channel1 = 24342 + + // channel 2: 10 B7 00 => 0x00B710 => 46864 + const val sample0Channel2 = 46864 + + // ambient: 59 E6 FD => 0x00B710 => 46864 + const val sample0Ambient = -137639 + + val ppgOfflineFrame = byteArrayOf( + 0x00.toByte(), + 0x2b.toByte(), 0x4c.toByte(), 0x7c.toByte(), 0x3d.toByte(), 0x01.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x00.toByte(), 0xed.toByte(), 0xb7.toByte(), 0x3b.toByte(), 0x5e.toByte(), + 0x32.toByte(), 0x30.toByte(), 0x31.toByte(), 0x37.toByte(), 0x2d.toByte(), 0x30.toByte(), 0x31.toByte(), 0x2d.toByte(), 0x30.toByte(), 0x31.toByte(), + 0x20.toByte(), 0x31.toByte(), 0x30.toByte(), 0x3a.toByte(), 0x32.toByte(), 0x36.toByte(), 0x3a.toByte(), 0x31.toByte(), 0x36.toByte(), 0x00.toByte(), + + 0x0B.toByte(), + 0x00.toByte(), 0x01.toByte(), 0x87.toByte(), 0x00.toByte(), + 0x01.toByte(), 0x01.toByte(), 0x16.toByte(), 0x00.toByte(), + 0x04.toByte(), 0x01.toByte(), 0x04.toByte(), + 0x00.toByte(), + 0xe4.toByte(), 0x00.toByte(), 0x01.toByte(), 0xdc.toByte(), 0x02.toByte(), 0x56.toByte(), + 0xd2.toByte(), 0x19.toByte(), 0x52.toByte(), 0x72.toByte(), 0x07.toByte(), 0x80.toByte(), 0x5d.toByte(), 0x3b.toByte(), 0x00.toByte(), 0x16.toByte(), + 0x5f.toByte(), 0x00.toByte(), 0x10.toByte(), 0xb7.toByte(), 0x00.toByte(), 0x59.toByte(), 0xe6.toByte(), 0xfd.toByte(), 0x18.toByte(), 0x11.toByte(), + 0x5f.toByte(), 0x21.toByte(), 0x00.toByte(), 0xe8.toByte(), 0x3d.toByte(), 0x00.toByte(), 0x8f.toByte(), 0x19.toByte(), 0x00.toByte(), 0xe3.toByte(), + 0xeb.toByte(), 0xff.toByte(), 0x7f.toByte(), 0xca.toByte(), 0xff.toByte(), 0x17.toByte(), 0xc1.toByte(), 0xff.toByte(), 0xa4.toByte(), 0xef.toByte(), + 0xff.toByte(), 0x6f.toByte(), 0x31.toByte(), 0x00.toByte(), 0xf7.toByte(), 0xbf.toByte(), 0xff.toByte(), 0x77.toByte(), 0xc7.toByte(), 0xff.toByte(), + 0x04.toByte(), 0xce.toByte(), 0xff.toByte(), 0x01.toByte(), 0x08.toByte(), 0x00.toByte(), 0x93.toByte(), 0x42.toByte(), 0x00.toByte(), 0x78.toByte(), + 0x5f.toByte(), 0x00.toByte(), 0x7d.toByte(), 0x20.toByte(), 0x00.toByte(), 0x1d.toByte(), 0xcc.toByte(), 0xff.toByte(), 0xbf.toByte(), 0xf5.toByte(), + 0x05.toByte(), 0xda.toByte(), 0x8f.toByte(), 0x06.toByte(), 0x23.toByte(), 0xf1.toByte(), 0x05.toByte(), 0x44.toByte(), 0xe9.toByte(), 0xff.toByte(), + 0x69.toByte(), 0xe9.toByte(), 0x12.toByte(), 0xd4.toByte(), 0x28.toByte(), 0x13.toByte(), 0x2d.toByte(), 0xa7.toByte(), 0x10.toByte(), 0x48.toByte(), + 0xc2.toByte(), 0xfe.toByte(), 0x2c.toByte(), 0xf6.toByte(), 0x06.toByte(), 0x67.toByte(), 0xc1.toByte(), 0x05.toByte(), 0x05.toByte(), 0xb8.toByte(), + 0x08.toByte(), 0xad.toByte(), 0x9c.toByte(), 0x00.toByte(), 0x8e.toByte(), 0x23.toByte(), 0xf2.toByte(), 0x0e.toByte(), 0x56.toByte(), 0xf1.toByte(), + 0xbb.toByte(), 0xa3.toByte(), 0xf1.toByte(), 0xd9.toByte(), 0xd7.toByte(), 0xff.toByte(), 0xc8.toByte(), 0x87.toByte(), 0xf5.toByte(), 0x7a.toByte(), + 0x1d.toByte(), 0xf5.toByte(), 0x57.toByte(), 0xbc.toByte(), 0xf5.toByte(), 0xee.toByte(), 0x96.toByte(), 0xff.toByte(), 0x88.toByte(), 0xd6.toByte(), + 0xfa.toByte(), 0xdd.toByte(), 0x1a.toByte(), 0xfb.toByte(), 0x41.toByte(), 0x30.toByte(), 0xfb.toByte(), 0x06.toByte(), 0xc3.toByte(), 0xff.toByte(), + 0xf3.toByte(), 0xf3.toByte(), 0xfe.toByte(), 0xd2.toByte(), 0x22.toByte(), 0xff.toByte(), 0x24.toByte(), 0x25.toByte(), 0xff.toByte(), 0x6c.toByte(), + 0xbf.toByte(), 0xff.toByte(), 0x79.toByte(), 0x0c.toByte(), 0x02.toByte(), 0x2b.toByte(), 0x21.toByte(), 0x02.toByte(), 0xe7.toByte(), 0x3d.toByte(), + 0x02.toByte(), 0x51.toByte(), 0xc1.toByte(), 0xff.toByte(), 0xa8.toByte(), 0x76.toByte(), 0x00.toByte(), 0xe8.toByte(), 0xf3.toByte(), 0xff.toByte(), + 0xf1.toByte(), 0x89.toByte(), 0xff.toByte(), 0xca.toByte(), 0x2a.toByte(), 0x00.toByte(), 0xf3.toByte(), 0xb6.toByte(), 0xfd.toByte(), 0x5b.toByte(), + 0xa1.toByte(), 0xfe.toByte(), 0xbf.toByte(), 0x93.toByte(), 0xfe.toByte(), 0x4b.toByte(), 0x4b.toByte(), 0x00.toByte(), 0x41.toByte(), 0x20.toByte(), + 0xff.toByte(), 0xa1.toByte(), 0x6f.toByte(), 0xff.toByte(), 0x21.toByte(), 0x5e.toByte(), 0xff.toByte(), 0xb9.toByte(), 0x9f.toByte(), 0x00.toByte(), + 0x29.toByte(), 0xdf.toByte(), 0xff.toByte(), 0x9b.toByte(), 0xec.toByte(), 0xff.toByte(), 0x8b.toByte(), 0xe3.toByte(), 0xff.toByte(), 0x74.toByte(), + 0x56.toByte(), 0x01.toByte(), 0x35.toByte(), 0x44.toByte(), 0xfe.toByte(), 0x3a.toByte(), 0x4d.toByte(), 0xfe.toByte(), 0xf1.toByte(), 0x50.toByte(), + 0xfe.toByte(), 0xf6.toByte(), 0xed.toByte(), 0xfe.toByte(), 0xe0.toByte(), 0x00.toByte(), 0x01.toByte(), 0x53.toByte(), 0x2e.toByte(), 0x38.toByte(), + 0xe9.toByte(), 0x19.toByte(), 0x52.toByte(), 0x72.toByte(), 0x07.toByte(), 0x80.toByte(), 0x2d.toByte(), 0xbc.toByte(), 0xfe.toByte(), 0x7a.toByte(), + 0x35.toByte(), 0xff.toByte(), 0xcc.toByte(), 0xda.toByte(), 0xff.toByte(), 0x39.toByte(), 0xbf.toByte(), 0xfc.toByte(), 0x14.toByte(), 0x14.toByte(), + 0xbf.toByte(), 0x86.toByte(), 0x00.toByte(), 0x08.toByte(), 0x0a.toByte(), 0x51.toByte(), 0x9c.toByte(), 0xc0.toByte(), 0x7c.toByte(), 0x00.toByte(), + 0xb6.toByte(), 0xf1.toByte(), 0x4f.toByte(), 0x2c.toByte(), 0xfc.toByte(), 0x5e.toByte(), 0xc5.toByte(), 0x9f.toByte(), 0x79.toByte(), 0x04.toByte(), + 0x99.toByte(), 0xc9.toByte(), 0x20.toByte(), 0x2c.toByte(), 0x09.toByte(), 0xdf.toByte(), 0x93.toByte(), 0x60.toByte(), 0xb8.toByte(), 0x12.toByte(), + 0xeb.toByte(), 0x9b.toByte(), 0xe1.toByte(), 0x43.toByte(), 0x19.toByte(), 0x70.toByte(), 0x90.toByte(), 0xf1.toByte(), 0x07.toByte(), 0x1e.toByte(), + 0x1f.toByte(), 0xf6.toByte(), 0x00.toByte(), 0x63.toByte(), 0x0f.toByte(), 0xa3.toByte(), 0xf4.toByte(), 0x60.toByte(), 0x5d.toByte(), 0x12.toByte(), + 0x10.toByte(), 0x2d.toByte(), 0xde.toByte(), 0xdd.toByte(), 0xe2.toByte(), 0x56.toByte(), 0x2e.toByte(), 0x4e.toByte(), 0x53.toByte(), 0xe4.toByte(), + 0xc9.toByte(), 0x94.toByte(), 0x6f.toByte(), 0xfd.toByte(), 0xfc.toByte(), 0x20.toByte(), 0x75.toByte(), 0x4f.toByte(), 0x5e.toByte(), 0xe9.toByte(), + 0x36.toByte(), 0xc0.toByte(), 0x3e.toByte(), 0x55.toByte(), 0xed.toByte(), 0xc9.toByte(), 0xeb.toByte(), 0x2e.toByte(), 0xc5.toByte(), 0xf2.toByte(), + 0x27.toByte(), 0xa9.toByte(), 0x0f.toByte(), 0xc9.toByte(), 0xf6.toByte(), 0x83.toByte(), 0x9d.toByte(), 0xaf.toByte(), 0x7b.toByte(), 0x00.toByte(), + 0x10.toByte(), 0x3d.toByte(), 0x11.toByte(), 0x7f.toByte(), 0x12.toByte(), 0x61.toByte(), 0x37.toByte(), 0x01.toByte(), 0x29.toByte(), 0x16.toByte(), + 0x69.toByte(), 0xd6.toByte(), 0x8f.toByte(), 0xb8.toByte(), 0xfd.toByte(), 0xde.toByte(), 0xdc.toByte(), 0x3f.toByte(), 0x9c.toByte(), 0xfe.toByte(), + 0xd6.toByte(), 0x0f.toByte(), 0x9f.toByte(), 0x80.toByte(), 0xf0.toByte(), 0xc8.toByte(), 0x0b.toByte(), 0xbf.toByte(), 0x3c.toByte(), 0xf1.toByte(), + 0x52.toByte(), 0xcd.toByte(), 0x8f.toByte(), 0xa2.toByte(), 0xfc.toByte(), 0xc6.toByte(), 0xca.toByte(), 0x9f.toByte(), 0x00.toByte(), 0xfd.toByte(), + 0x9c.toByte(), 0x55.toByte(), 0x40.toByte(), 0x4c.toByte(), 0x05.toByte(), 0x1e.toByte(), 0x56.toByte(), 0xb0.toByte(), 0x75.toByte(), 0x05.toByte(), + 0xbd.toByte(), 0x40.toByte(), 0x40.toByte(), 0x46.toByte(), 0x04.toByte(), 0xec.toByte(), 0x42.toByte(), 0x70.toByte(), 0x15.toByte(), 0x04.toByte(), + 0x7b.toByte(), 0xaa.toByte(), 0x9f.toByte(), 0x92.toByte(), 0xfa.toByte(), 0xf7.toByte(), 0xaa.toByte(), 0x3f.toByte(), 0xa2.toByte(), 0xfa.toByte(), + 0x20.toByte(), 0x7c.toByte(), 0x4f.toByte(), 0x98.toByte(), 0xf7.toByte(), 0x23.toByte(), 0x79.toByte(), 0x3f.toByte(), 0xa5.toByte(), 0xf7.toByte(), + 0x97.toByte(), 0xf1.toByte(), 0x3f.toByte(), 0x21.toByte(), 0xff.toByte(), 0x1f.toByte(), 0xf2.toByte(), 0x8f.toByte(), 0x3b.toByte(), 0xff.toByte(), + 0x3a.toByte(), 0x84.toByte(), 0xa0.toByte(), 0x35.toByte(), 0x08.toByte(), 0xba.toByte(), 0x83.toByte(), 0x70.toByte(), 0x48.toByte(), 0x08.toByte(), + 0x28.toByte(), 0x86.toByte(), 0xe0.toByte(), 0x77.toByte(), 0x08.toByte(), 0xa4.toByte(), 0x87.toByte(), 0xc0.toByte(), 0x70.toByte(), 0x08.toByte(), + 0xe0.toByte(), 0x00.toByte(), 0x01.toByte(), 0xbc.toByte(), 0xd6.toByte(), 0x19.toByte(), 0x00.toByte(), 0x1a.toByte(), 0x52.toByte(), 0x72.toByte(), + 0x07.toByte(), 0x80.toByte(), 0x42.toByte(), 0x2d.toByte(), 0xff.toByte(), 0xf0.toByte(), 0x4d.toByte(), 0xff.toByte(), 0x43.toByte(), 0xf0.toByte(), + 0xff.toByte(), 0xd4.toByte(), 0x04.toByte(), 0xfe.toByte(), 0x14.toByte(), 0x14.toByte(), 0xdb.toByte(), 0x7c.toByte(), 0x8f.toByte(), 0xae.toByte(), + 0xf7.toByte(), 0x49.toByte(), 0x7c.toByte(), 0x3f.toByte(), 0xcd.toByte(), 0xf7.toByte(), 0x5f.toByte(), 0xc6.toByte(), 0x6f.toByte(), 0x50.toByte(), + 0xfc.toByte(), 0xc3.toByte(), 0xc4.toByte(), 0x0f.toByte(), 0x51.toByte(), 0xfc.toByte(), 0x8f.toByte(), 0x63.toByte(), 0x30.toByte(), 0x52.toByte(), + 0x06.toByte(), 0x47.toByte(), 0x66.toByte(), 0x90.toByte(), 0x51.toByte(), 0x06.toByte(), 0x3f.toByte(), 0x11.toByte(), 0x41.toByte(), 0x30.toByte(), + 0x11.toByte(), 0x59.toByte(), 0x12.toByte(), 0xe1.toByte(), 0x16.toByte(), 0x11.toByte(), 0x43.toByte(), 0xdc.toByte(), 0x6f.toByte(), 0xcf.toByte(), + 0xfd.toByte(), 0x12.toByte(), 0xdc.toByte(), 0xcf.toByte(), 0xcc.toByte(), 0xfd.toByte(), 0xc1.toByte(), 0x4d.toByte(), 0xdf.toByte(), 0xcc.toByte(), + 0xf4.toByte(), 0x55.toByte(), 0x4c.toByte(), 0xcf.toByte(), 0xcc.toByte(), 0xf4.toByte(), 0x1b.toByte(), 0xa7.toByte(), 0xaf.toByte(), 0x39.toByte(), + 0xfa.toByte(), 0xd4.toByte(), 0xa5.toByte(), 0x8f.toByte(), 0x4f.toByte(), 0xfa.toByte(), 0xae.toByte(), 0x4a.toByte(), 0x50.toByte(), 0xc9.toByte(), + 0x04.toByte(), 0x96.toByte(), 0x4c.toByte(), 0x70.toByte(), 0xc2.toByte(), 0x04.toByte(), 0xa2.toByte(), 0xd3.toByte(), 0xe1.toByte(), 0x74.toByte(), + 0x1d.toByte(), 0x92.toByte(), 0xd6.toByte(), 0xc1.toByte(), 0x44.toByte(), 0x1d.toByte(), 0xcb.toByte(), 0xf7.toByte(), 0x4f.toByte(), 0x02.toByte(), + 0x00.toByte(), 0xcc.toByte(), 0xfb.toByte(), 0x6f.toByte(), 0x80.toByte(), 0xff.toByte(), 0x3d.toByte(), 0x51.toByte(), 0x5f.toByte(), 0x3f.toByte(), + 0xf4.toByte(), 0x97.toByte(), 0x6d.toByte(), 0x7f.toByte(), 0xec.toByte(), 0xf2.toByte(), 0xa4.toByte(), 0x78.toByte(), 0xaf.toByte(), 0x93.toByte(), + 0xf7.toByte(), 0x33.toByte(), 0x5b.toByte(), 0xef.toByte(), 0xd9.toByte(), 0xf7.toByte(), 0x7c.toByte(), 0x8d.toByte(), 0x3f.toByte(), 0xea.toByte(), + 0xf8.toByte(), 0xf4.toByte(), 0x89.toByte(), 0x6f.toByte(), 0x4b.toByte(), 0xf9.toByte(), 0x27.toByte(), 0xa1.toByte(), 0x80.toByte(), 0x29.toByte(), + 0x0a.toByte(), 0x37.toByte(), 0x9d.toByte(), 0x30.toByte(), 0x6b.toByte(), 0x0a.toByte(), 0xe3.toByte(), 0x8e.toByte(), 0x3f.toByte(), 0x1a.toByte(), + 0xf9.toByte(), 0x1e.toByte(), 0x91.toByte(), 0x2f.toByte(), 0x39.toByte(), 0xf9.toByte(), 0x62.toByte(), 0xef.toByte(), 0x8f.toByte(), 0x97.toByte(), + 0xfe.toByte(), 0x79.toByte(), 0xea.toByte(), 0xbf.toByte(), 0xe6.toByte(), 0xfe.toByte(), 0x05.toByte(), 0x72.toByte(), 0x80.toByte(), 0x7a.toByte(), + 0x07.toByte(), 0x76.toByte(), 0x75.toByte(), 0xb0.toByte(), 0x57.toByte(), 0x07.toByte(), 0x42.toByte(), 0x66.toByte(), 0xdf.toByte(), 0x40.toByte(), + 0xf6.toByte(), 0x13.toByte(), 0x65.toByte(), 0xff.toByte(), 0x59.toByte(), 0xf6.toByte(), 0x77.toByte(), 0x4a.toByte(), 0xd0.toByte(), 0x89.toByte(), + 0x04.toByte(), 0xa8.toByte(), 0x48.toByte(), 0x80.toByte(), 0x9a.toByte(), 0x04.toByte(), 0xa5.toByte(), 0xf0.toByte(), 0x7f.toByte(), 0x67.toByte(), + 0xff.toByte(), 0xa1.toByte(), 0xf6.toByte(), 0x7f.toByte(), 0x44.toByte(), 0xff.toByte(), 0xe0.toByte(), 0x00.toByte(), 0x01.toByte(), 0x7f.toByte(), + 0x6d.toByte(), 0xfc.toByte(), 0x16.toByte(), 0x1a.toByte(), 0x52.toByte(), 0x72.toByte(), 0x07.toByte(), 0x80.toByte(), 0xd8.toByte(), 0xdc.toByte(), + 0xfe.toByte(), 0x4a.toByte(), 0xfd.toByte(), 0xfe.toByte(), 0xf9.toByte(), 0x9d.toByte(), 0xff.toByte(), 0xb8.toByte(), 0xaa.toByte(), 0xfd.toByte(), + 0x14.toByte(), 0x14.toByte(), 0x46.toByte(), 0x9e.toByte(), 0x20.toByte(), 0xdc.toByte(), 0x09.toByte(), 0x59.toByte(), 0x9e.toByte(), 0x60.toByte(), + 0xdf.toByte(), 0x09.toByte(), 0x96.toByte(), 0xbb.toByte(), 0xff.toByte(), 0xec.toByte(), 0xfb.toByte(), 0x25.toByte(), 0xbd.toByte(), 0x8f.toByte(), + 0xc8.toByte(), 0xfb.toByte(), 0x53.toByte(), 0xdc.toByte(), 0xdf.toByte(), 0x59.toByte(), 0xfd.toByte(), 0xec.toByte(), 0xd8.toByte(), 0x3f.toByte(), + 0x99.toByte(), 0xfd.toByte(), 0x8a.toByte(), 0x88.toByte(), 0xf0.toByte(), 0xd6.toByte(), 0x08.toByte(), 0xb4.toByte(), 0x8b.toByte(), 0x70.toByte(), + 0xa1.toByte(), 0x08.toByte(), 0x92.toByte(), 0x74.toByte(), 0x4f.toByte(), 0x59.toByte(), 0xf7.toByte(), 0x4e.toByte(), 0x75.toByte(), 0xdf.toByte(), + 0x37.toByte(), 0xf7.toByte(), 0xdf.toByte(), 0x31.toByte(), 0xc0.toByte(), 0xe1.toByte(), 0x02.toByte(), 0xd8.toByte(), 0x2d.toByte(), 0x20.toByte(), + 0xec.toByte(), 0x02.toByte(), 0x0c.toByte(), 0x3c.toByte(), 0xc0.toByte(), 0x24.toByte(), 0x04.toByte(), 0x70.toByte(), 0x42.toByte(), 0xc0.toByte(), + 0xcd.toByte(), 0x03.toByte(), 0xdd.toByte(), 0x51.toByte(), 0x2f.toByte(), 0xce.toByte(), 0xf4.toByte(), 0x6b.toByte(), 0x4d.toByte(), 0x4f.toByte(), + 0x9b.toByte(), 0xf4.toByte(), 0x75.toByte(), 0x3c.toByte(), 0x60.toByte(), 0x9b.toByte(), 0x03.toByte(), 0x3c.toByte(), 0x3a.toByte(), 0x20.toByte(), + 0x97.toByte(), 0x03.toByte(), 0xb2.toByte(), 0xae.toByte(), 0xdf.toByte(), 0xf5.toByte(), 0xfa.toByte(), 0xe1.toByte(), 0xb6.toByte(), 0xdf.toByte(), + 0x40.toByte(), 0xfa.toByte(), 0x36.toByte(), 0x6a.toByte(), 0xa0.toByte(), 0x64.toByte(), 0x08.toByte(), 0xde.toByte(), 0x1c.toByte(), 0x60.toByte(), + 0xef.toByte(), 0xf9.toByte(), 0xa8.toByte(), 0xb7.toByte(), 0xc2.toByte(), 0xee.toByte(), 0x29.toByte(), 0x0f.toByte(), 0xde.toByte(), 0xc2.toByte(), + 0xb2.toByte(), 0x33.toByte(), 0xba.toByte(), 0x4f.toByte(), 0xb2.toByte(), 0x6e.toByte(), 0x25.toByte(), 0x71.toByte(), 0x6a.toByte(), 0x42.toByte(), + 0xf9.toByte(), 0x28.toByte(), 0x29.toByte(), 0x56.toByte(), 0x7d.toByte(), 0xe2.toByte(), 0xd5.toByte(), 0x80.toByte(), 0x62.toByte(), 0xad.toByte(), + 0x4f.toByte(), 0xd7.toByte(), 0xe5.toByte(), 0xab.toByte(), 0x2d.toByte(), 0xac.toByte(), 0xd9.toByte(), 0x9e.toByte(), 0x96.toByte(), 0x6d.toByte(), + 0x94.toByte(), 0xd9.toByte(), 0xc3.toByte(), 0xd3.toByte(), 0xbf.toByte(), 0x43.toByte(), 0xfb.toByte(), 0x84.toByte(), 0xbb.toByte(), 0x4f.toByte(), + 0x5e.toByte(), 0xf4.toByte(), 0x34.toByte(), 0xcc.toByte(), 0xaf.toByte(), 0xe8.toByte(), 0xfe.toByte(), 0x70.toByte(), 0xff.toByte(), 0x7f.toByte(), + 0xa7.toByte(), 0xf8.toByte(), 0x86.toByte(), 0x85.toByte(), 0xdf.toByte(), 0x1a.toByte(), 0xf8.toByte(), 0x0e.toByte(), 0x76.toByte(), 0x3f.toByte(), + 0x98.toByte(), 0xfb.toByte(), 0x4e.toByte(), 0x15.toByte(), 0x91.toByte(), 0x9a.toByte(), 0x0f.toByte(), 0x5a.toByte(), 0x24.toByte(), 0xc1.toByte(), + 0x89.toByte(), 0x13.toByte(), 0xa5.toByte(), 0xa6.toByte(), 0x31.toByte(), 0xe3.toByte(), 0x1c.toByte(), 0x23.toByte(), 0xb3.toByte(), 0xe1.toByte(), + 0x90.toByte(), 0x1e.toByte(), 0xe0.toByte(), 0x00.toByte(), 0x01.toByte(), 0x5b.toByte(), 0x82.toByte(), 0xf5.toByte(), 0x2e.toByte(), 0x1a.toByte(), + 0x52.toByte(), 0x72.toByte(), 0x07.toByte(), 0x80.toByte(), 0x38.toByte(), 0xcc.toByte(), 0x01.toByte(), 0xba.toByte(), 0xf1.toByte(), 0x01.toByte(), + 0x46.toByte(), 0x97.toByte(), 0x02.toByte(), 0x11.toByte(), 0x7d.toByte(), 0x00.toByte(), 0x14.toByte(), 0x12.toByte(), 0x4a.toByte(), 0xae.toByte(), + 0x2f.toByte(), 0x2d.toByte(), 0xfa.toByte(), 0x9a.toByte(), 0x9f.toByte(), 0x6f.toByte(), 0x51.toByte(), 0xfa.toByte(), 0x76.toByte(), 0x7a.toByte(), + 0x6f.toByte(), 0x45.toByte(), 0xf6.toByte(), 0x3f.toByte(), 0x75.toByte(), 0x4f.toByte(), 0x7c.toByte(), 0xf6.toByte(), 0xf7.toByte(), 0x9a.toByte(), + 0xaf.toByte(), 0xf3.toByte(), 0xf7.toByte(), 0xaa.toByte(), 0xa5.toByte(), 0xbf.toByte(), 0xb1.toByte(), 0xf7.toByte(), 0xfe.toByte(), 0xde.toByte(), + 0x6f.toByte(), 0xdc.toByte(), 0xfe.toByte(), 0xf6.toByte(), 0x04.toByte(), 0x00.toByte(), 0x68.toByte(), 0xfe.toByte(), 0x30.toByte(), 0xfa.toByte(), + 0xaf.toByte(), 0x0c.toByte(), 0x01.toByte(), 0x13.toByte(), 0xf9.toByte(), 0x1f.toByte(), 0xc3.toByte(), 0x01.toByte(), 0x44.toByte(), 0x3f.toByte(), + 0x10.toByte(), 0x24.toByte(), 0x04.toByte(), 0xc6.toByte(), 0x27.toByte(), 0x40.toByte(), 0x84.toByte(), 0x04.toByte(), 0x91.toByte(), 0xfb.toByte(), + 0x0f.toByte(), 0xac.toByte(), 0xff.toByte(), 0xff.toByte(), 0xf0.toByte(), 0x6f.toByte(), 0xd1.toByte(), 0xfe.toByte(), 0x43.toByte(), 0xb9.toByte(), + 0x0f.toByte(), 0x90.toByte(), 0xfb.toByte(), 0x5f.toByte(), 0xb5.toByte(), 0x8f.toByte(), 0xf5.toByte(), 0xfa.toByte(), 0xb5.toByte(), 0xee.toByte(), + 0xff.toByte(), 0xed.toByte(), 0xfe.toByte(), 0xf5.toByte(), 0xec.toByte(), 0x4f.toByte(), 0x50.toByte(), 0xff.toByte(), 0x6c.toByte(), 0x1a.toByte(), + 0xa0.toByte(), 0x03.toByte(), 0x02.toByte(), 0xea.toByte(), 0x1e.toByte(), 0x80.toByte(), 0xa3.toByte(), 0x02.toByte(), 0x3d.toByte(), 0x0e.toByte(), + 0xb0.toByte(), 0x35.toByte(), 0x01.toByte(), 0xc6.toByte(), 0x14.toByte(), 0x30.toByte(), 0x44.toByte(), 0x01.toByte(), 0xf2.toByte(), 0x24.toByte(), + 0x00.toByte(), 0x34.toByte(), 0x02.toByte(), 0x1b.toByte(), 0x21.toByte(), 0x40.toByte(), 0x61.toByte(), 0x02.toByte(), 0x9c.toByte(), 0x24.toByte(), + 0x00.toByte(), 0x37.toByte(), 0x02.toByte(), 0x18.toByte(), 0x20.toByte(), 0x40.toByte(), 0x7e.toByte(), 0x02.toByte(), 0xa8.toByte(), 0x0f.toByte(), + 0x40.toByte(), 0xe8.toByte(), 0x00.toByte(), 0x36.toByte(), 0x10.toByte(), 0x30.toByte(), 0xfe.toByte(), 0x00.toByte(), 0x0b.toByte(), 0x0c.toByte(), + 0x00.toByte(), 0xc0.toByte(), 0x00.toByte(), 0x7c.toByte(), 0x0c.toByte(), 0x00.toByte(), 0xc0.toByte(), 0x00.toByte(), 0x12.toByte(), 0x09.toByte(), + 0x10.toByte(), 0x8b.toByte(), 0x00.toByte(), 0xab.toByte(), 0x08.toByte(), 0x00.toByte(), 0x86.toByte(), 0x00.toByte(), 0xae.toByte(), 0x03.toByte(), + 0xf0.toByte(), 0x45.toByte(), 0x00.toByte(), 0x0f.toByte(), 0x04.toByte(), 0x30.toByte(), 0x41.toByte(), 0x00.toByte(), 0x9b.toByte(), 0x01.toByte(), + 0x30.toByte(), 0x1d.toByte(), 0x00.toByte(), 0x27.toByte(), 0x02.toByte(), 0x30.toByte(), 0x1c.toByte(), 0x00.toByte(), 0x0c.toByte(), 0x03.toByte(), + 0xa8.toByte(), 0x82.toByte(), 0x24.toByte(), 0x7c.toByte(), 0x82.toByte(), 0x25.toByte(), 0x6c.toByte(), 0x3d.toByte(), 0xc9.toByte(), 0xab.toByte(), + 0xcc.toByte(), 0xca.toByte(), 0x3c.toByte(), 0x6a.toByte(), 0xaf.toByte(), 0xf4.toByte(), 0x5a.toByte(), 0xaa.toByte(), 0xe4.toByte(), 0x00.toByte(), + 0x01.toByte(), 0x3a.toByte(), 0x0c.toByte(), 0x19.toByte(), 0x55.toByte(), 0x1a.toByte(), 0x52.toByte(), 0x72.toByte(), 0x07.toByte(), 0x80.toByte(), + 0xe5.toByte(), 0xdf.toByte(), 0x00.toByte(), 0x2b.toByte(), 0xf6.toByte(), 0x00.toByte(), 0x5e.toByte(), 0xa2.toByte(), 0x01.toByte(), 0x08.toByte(), + 0x8f.toByte(), 0xff.toByte(), 0x0c.toByte(), 0x22.toByte(), 0x6e.toByte(), 0x91.toByte(), 0x25.toByte(), 0x6e.toByte(), 0x02.toByte(), 0x1a.toByte(), + 0xf0.toByte(), 0xe4.toByte(), 0x48.toByte(), 0x9e.toByte(), 0x24.toByte(), 0x43.toByte(), 0x91.toByte(), 0x01.toByte(), 0x0f.toByte(), 0x1a.toByte(), + 0x71.toByte(), 0x16.toByte(), 0x02.toByte(), 0x3d.toByte(), 0xcf.toByte(), 0x6d.toByte(), 0xdd.toByte(), 0xd1.toByte(), 0x2e.toByte(), 0x5f.toByte(), + 0xfd.toByte(), 0x34.toByte(), 0xaf.toByte(), 0xf8.toByte(), 0x11.toByte(), 0x1e.toByte(), 0xe9.toByte(), 0x75.toByte(), 0x9e.toByte(), 0xe0.toByte(), + 0x5b.toByte(), 0xc3.toByte(), 0x3a.toByte(), 0x79.toByte(), 0xf3.toByte(), 0x3c.toByte(), 0xfe.toByte(), 0xa2.toByte(), 0x29.toByte(), 0xcb.toByte(), + 0xf2.toByte(), 0x2e.toByte(), 0x4b.toByte(), 0x7f.toByte(), 0xeb.toByte(), 0xf7.toByte(), 0x5e.toByte(), 0xf2.toByte(), 0x43.toByte(), 0x7b.toByte(), + 0xb8.toByte(), 0x33.toByte(), 0x7b.toByte(), 0xb5.toByte(), 0x03.toByte(), 0x0e.toByte(), 0xce.toByte(), 0x9b.toByte(), 0xad.toByte(), 0xd7.toByte(), + 0x0c.toByte(), 0xde.toByte(), 0xf5.toByte(), 0xb1.toByte(), 0x3e.toByte(), 0xe5.toByte(), 0xb0.toByte(), 0x33.toByte(), 0x39.toByte(), 0x02.toByte(), + 0x14.toByte(), 0x3c.toByte(), 0xa0.toByte(), 0x62.toByte(), 0x28.toByte(), 0x5d.toByte(), 0x02.toByte(), 0x24.toByte(), 0xa0.toByte(), 0xce.toByte(), + 0xe3.toByte(), 0x82.toByte(), 0xde.toByte(), 0xec.toByte(), 0x70.toByte(), 0x8e.toByte(), 0xeb.toByte(), 0x73.toByte(), 0xae.toByte(), 0xe7.toByte(), + 0xa9.toByte(), 0x1e.toByte(), 0xe1.toByte(), 0x43.toByte(), 0x4e.toByte(), 0xe9.toByte(), 0xe7.toByte(), 0x4f.toByte(), 0x0f.toByte(), 0xa1.toByte(), + 0x90.toByte(), 0x0b.toByte(), 0xf7.toByte(), 0x63.toByte(), 0x3f.toByte(), 0x09.toByte(), 0x24.toByte(), 0x3c.toByte(), 0x92.toByte(), 0xb1.toByte(), + 0x07.toByte(), 0x7c.toByte(), 0xb0.toByte(), 0x0a.toByte(), 0xb4.toByte(), 0xcc.toByte(), 0xcb.toByte(), 0x60.toByte(), 0xed.toByte(), 0xd1.toByte(), + 0xd9.toByte(), 0xed.toByte(), 0xe4.toByte(), 0x98.toByte(), 0xdd.toByte(), 0xdf.toByte(), 0x0f.toByte(), 0x2e.toByte(), 0xdd.toByte(), 0x4f.toByte(), + 0x9e.toByte(), 0xe0.toByte(), 0xe0.toByte(), 0xa2.toByte(), 0x39.toByte(), 0x1f.toByte(), 0x43.toByte(), 0x2f.toByte(), 0xba.toByte(), 0x73.toByte(), + 0x3c.toByte(), 0xdb.toByte(), 0x73.toByte(), 0x3f.toByte(), 0x83.toByte(), 0xa0.toByte(), 0xfa.toByte(), 0xc1.toByte(), 0xaf.toByte(), 0x01.toByte(), + 0x88.toByte(), 0x9b.toByte(), 0xc0.toByte(), 0xc2.toByte(), 0x5b.toByte(), 0xbc.toByte(), 0x4b.toByte(), 0xde.toByte(), 0xe0.toByte(), 0x4a.toByte(), + 0xde.toByte(), 0xe6.toByte(), 0xe5.toByte(), 0x0e.toByte(), 0xeb.toByte(), 0xae.toByte(), 0xde.toByte(), 0xe8.toByte(), 0xee.toByte(), 0x52.toByte(), + 0x3d.toByte(), 0xa9.toByte(), 0x63.toByte(), 0x31.toByte(), 0x48.toByte(), 0xd1.toByte(), 0x10.toByte(), 0x31.toByte(), 0x91.toByte(), 0x12.toByte(), + 0x82.toByte(), 0xad.toByte(), 0xc9.toByte(), 0xdc.toByte(), 0x6c.toByte(), 0xda.toByte(), 0xf2.toByte(), 0x4b.toByte(), 0xc1.toByte(), 0x78.toByte(), + 0x6c.toByte(), 0xba.toByte(), 0x29.toByte(), 0x7d.toByte(), 0xd6.toByte(), 0x03.toByte(), 0x5d.toByte(), 0xd4.toByte() + ) + } + + private object PpiOfflineMockData { + // index type data: + // 48 0x00, + // 49 HR 0x00 + const val expectedHeartRate = 0 + + // 50..51 PP interval in ms 0x61, 0x02, + const val expectedIntervalInMs = 609 + + // 52..53 PP Error Estimate 0x1e, 0x00 + const val expectedErrorEstimate = 30 + + // 54 PP flags 0x07 + const val ppFlags = 0x07 + val expectedBlockerBit = if (ppFlags and 0x01 != 0) 0x01 else 0x00 + val expectedSkinContactStatus = if (ppFlags and 0x02 != 0) 0x01 else 0x00 + val expectedSkinContactSupported = if (ppFlags and 0x04 != 0) 0x01 else 0x00 + + // 55 HR 0x40 (64) + const val expectedHeartRate2 = 64 + + // 56..57 PP 0xac, 0x01, (428) + const val expectedIntervalInMs2 = 428 + + // 58..59 PP Error Estimate 0xff, 0x00 + const val expectedErrorEstimate2 = 255 + + // 60 PP flags 0x00 + const val ppFlags2 = 0x00 + val expectedBlockerBit2 = if (ppFlags2 and 0x01 != 0) 0x01 else 0x00 + val expectedSkinContactStatus2 = if (ppFlags2 and 0x02 != 0) 0x01 else 0x00 + val expectedSkinContactSupported2 = if (ppFlags2 and 0x04 != 0) 0x01 else 0x00 + + val ppiOfflineFrame = byteArrayOf( + 0x00.toByte(), + 0x2b.toByte(), 0x4c.toByte(), 0x7c.toByte(), 0x3d.toByte(), 0x01.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x00.toByte(), 0xed.toByte(), 0xb7.toByte(), 0x3b.toByte(), 0x5e.toByte(), + 0x32.toByte(), 0x30.toByte(), 0x31.toByte(), 0x37.toByte(), 0x2d.toByte(), 0x30.toByte(), 0x31.toByte(), 0x2d.toByte(), 0x30.toByte(), 0x33.toByte(), + 0x20.toByte(), 0x31.toByte(), 0x34.toByte(), 0x3a.toByte(), 0x30.toByte(), 0x36.toByte(), 0x3a.toByte(), 0x31.toByte(), 0x32.toByte(), 0x00.toByte(), + + 0x00.toByte(), + 0x00.toByte(), + 0x28.toByte(), 0x00.toByte(), 0x03.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x61.toByte(), 0x02.toByte(), 0x1e.toByte(), + 0x00.toByte(), 0x07.toByte(), 0x40.toByte(), 0xac.toByte(), 0x01.toByte(), 0xff.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0xd7.toByte(), + 0x04.toByte(), 0x1e.toByte(), 0x00.toByte(), 0x07.toByte(), 0x00.toByte(), 0x4b.toByte(), 0x05.toByte(), 0x1e.toByte(), 0x00.toByte(), 0x07.toByte(), + 0x00.toByte(), 0xbe.toByte(), 0x07.toByte(), 0x1e.toByte(), 0x00.toByte(), 0x07.toByte(), 0x16.toByte(), 0x00.toByte(), 0x03.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x1e.toByte(), + 0x04.toByte(), 0x1e.toByte(), 0x00.toByte(), 0x06.toByte(), 0x00.toByte(), 0x8b.toByte(), 0x04.toByte(), 0x1e.toByte(), 0x00.toByte(), 0x06.toByte(), + 0x22.toByte(), 0x00.toByte(), 0x03.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x50.toByte(), 0x04.toByte(), 0x19.toByte(), 0x00.toByte(), 0x06.toByte(), 0x00.toByte(), 0xcb.toByte(), + 0x04.toByte(), 0x15.toByte(), 0x00.toByte(), 0x07.toByte(), 0x00.toByte(), 0x5e.toByte(), 0x04.toByte(), 0x17.toByte(), 0x00.toByte(), 0x06.toByte(), + 0x00.toByte(), 0x95.toByte(), 0x04.toByte(), 0x14.toByte(), 0x00.toByte(), 0x06.toByte() + ) + } + + private object MagOfflineMockData { + private const val factor = 0.0015f + + // 69 0x80, + // 70..75 reference sample 0x63, 0xfe, 0xbb, 0xff, 0x4d, 0xfa, + // Sample 0 (aka. reference sample): + // channel 0: 63 fe => 0xFE63 => -413 + const val rawSample0Channel0 = -413 + const val sample0Channel0 = -413 * factor + + // channel 1: bb ff => 0xFFBB => -69 + const val rawSample0Channel1 = -69 + const val sample0Channel1 = -69 * factor + + // channel 2: 4d fa => 0xFA4D => -1459 + const val rawSample0Channel2 = -1459 + const val sample0Channel2 = -1459 * factor + + // 76: Delta size 0x06 (6bits) + // 77: Sample amount 0x27 (Delta block contains 39 samples) + // 78: Sample 1 - channel 0, size 6 bits: 00 001110 0x0e + const val sample1Channel0 = (rawSample0Channel0 + 14) * factor + + // 79: Sample 1 - channel 1, size 8 bits: 0101 1111 0x5f, + const val sample1Channel1 = (rawSample0Channel1 - 4) * factor + + // 80: Sample 1 - channel 2, size 8 bits: 111111 00 0xfc, + const val sample1Channel2 = (rawSample0Channel2 + 5) * factor + + const val expectedLastSampleTimeStamp = 0x09fbe2b73f7c2a46uL //extracted from [magOfflineFrame] + + val magOfflineFrame = byteArrayOf( + 0x00.toByte(), + 0x2b.toByte(), 0x4c.toByte(), 0x7c.toByte(), 0x3d.toByte(), 0x01.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x00.toByte(), 0x54.toByte(), 0x9f.toByte(), 0x5d.toByte(), 0x67.toByte(), + + 0x32.toByte(), 0x30.toByte(), 0x32.toByte(), 0x32.toByte(), 0x2d.toByte(), 0x31.toByte(), 0x30.toByte(), 0x2d.toByte(), 0x31.toByte(), 0x38.toByte(), + 0x20.toByte(), 0x31.toByte(), 0x31.toByte(), 0x3a.toByte(), 0x31.toByte(), 0x37.toByte(), 0x3a.toByte(), 0x32.toByte(), 0x30.toByte(), 0x00.toByte(), + + 0x15.toByte(), + 0x00.toByte(), 0x01.toByte(), 0x64.toByte(), 0x00.toByte(), + 0x01.toByte(), 0x01.toByte(), 0x10.toByte(), 0x00.toByte(), + 0x02.toByte(), 0x01.toByte(), 0x32.toByte(), 0x00.toByte(), + 0x04.toByte(), 0x01.toByte(), 0x03.toByte(), + 0x05.toByte(), 0x01.toByte(), 0xA6.toByte(), 0x9B.toByte(), 0xC4.toByte(), 0x3A.toByte(), + 0x00.toByte(), + 0xdf.toByte(), 0x00.toByte(), + + 0x06.toByte(), + 0x96.toByte(), 0x18.toByte(), 0x9f.toByte(), 0x00.toByte(), 0xb7.toByte(), 0xe2.toByte(), 0xfb.toByte(), 0x09.toByte(), + 0x80.toByte(), + 0x63.toByte(), 0xfe.toByte(), 0xbb.toByte(), 0xff.toByte(), + 0x4d.toByte(), 0xfa.toByte(), 0x06.toByte(), 0x27.toByte(), 0x0e.toByte(), 0x5f.toByte(), 0xfc.toByte(), 0x7c.toByte(), 0x70.toByte(), 0x17.toByte(), + 0x3a.toByte(), 0x3f.toByte(), 0x14.toByte(), 0x39.toByte(), 0xd0.toByte(), 0xfb.toByte(), 0xc4.toByte(), 0x6f.toByte(), 0xe0.toByte(), 0x82.toByte(), + 0xc1.toByte(), 0xff.toByte(), 0xbb.toByte(), 0x13.toByte(), 0xfc.toByte(), 0x84.toByte(), 0x5f.toByte(), 0xd8.toByte(), 0xc1.toByte(), 0xaf.toByte(), + 0xfb.toByte(), 0x8c.toByte(), 0x22.toByte(), 0xf0.toByte(), 0xc4.toByte(), 0x1f.toByte(), 0xe3.toByte(), 0xc1.toByte(), 0xdf.toByte(), 0xfb.toByte(), + 0xc1.toByte(), 0x0f.toByte(), 0x1c.toByte(), 0x01.toByte(), 0x22.toByte(), 0x00.toByte(), 0xc0.toByte(), 0x2e.toByte(), 0xec.toByte(), 0xff.toByte(), + 0xdf.toByte(), 0xff.toByte(), 0x7f.toByte(), 0xdf.toByte(), 0x0b.toByte(), 0x01.toByte(), 0x00.toByte(), 0x10.toByte(), 0xc0.toByte(), 0x0f.toByte(), + 0x0c.toByte(), 0xbe.toByte(), 0x6f.toByte(), 0x08.toByte(), 0xbc.toByte(), 0xef.toByte(), 0xff.toByte(), 0xb8.toByte(), 0x1f.toByte(), 0xfc.toByte(), + 0x84.toByte(), 0x10.toByte(), 0xf8.toByte(), 0x81.toByte(), 0xf0.toByte(), 0x03.toByte(), 0x81.toByte(), 0xd0.toByte(), 0xfb.toByte(), 0x41.toByte(), + 0xf0.toByte(), 0x03.toByte(), 0x02.toByte(), 0x12.toByte(), 0x04.toByte(), 0x01.toByte(), 0xf0.toByte(), 0xeb.toByte(), 0x7d.toByte(), 0xff.toByte(), + 0x0b.toByte(), 0x01.toByte(), 0x03.toByte(), 0x08.toByte(), 0x98.toByte(), 0x04.toByte(), 0xe8.toByte(), 0x7a.toByte(), 0xe3.toByte(), 0xd8.toByte(), + 0xc1.toByte(), 0x2e.toByte(), 0xcf.toByte(), 0x06.toByte(), 0x10.toByte(), 0xfd.toByte(), 0xef.toByte(), 0x0b.toByte(), 0xfe.toByte(), 0x20.toByte(), + 0x0c.toByte(), 0xbf.toByte(), 0xf0.toByte(), 0x13.toByte(), 0xc0.toByte(), 0x1e.toByte(), 0x0c.toByte(), 0x00.toByte(), 0xfe.toByte(), 0x13.toByte(), + 0xc2.toByte(), 0xbe.toByte(), 0x00.toByte(), 0x01.toByte(), 0x4f.toByte(), 0xfc.toByte(), 0xb8.toByte(), 0x30.toByte(), 0x08.toByte(), 0x3e.toByte(), + 0x00.toByte(), 0x00.toByte(), 0x3d.toByte(), 0xf0.toByte(), 0x0b.toByte(), 0xfc.toByte(), 0x3f.toByte(), 0x08.toByte(), 0x84.toByte(), 0x0f.toByte(), + 0xfc.toByte(), 0x04.toByte(), 0x08.toByte(), 0xec.toByte(), 0xec.toByte(), 0xe1.toByte(), 0x15.toByte(), 0x22.toByte(), 0x40.toByte(), 0xbe.toByte(), + 0x4d.toByte(), 0x0e.toByte(), 0x6c.toByte(), 0xcd.toByte(), 0xe1.toByte(), 0x06.toByte(), 0x08.toByte(), 0xc2.toByte(), 0x3f.toByte(), 0x04.toByte(), + 0xc1.toByte(), 0x00.toByte(), 0xf8.toByte(), 0x40.toByte(), 0x10.toByte(), 0xf4.toByte(), 0x0b.toByte(), 0xc3.toByte(), 0x03.toByte(), 0xc0.toByte(), + 0x60.toByte(), 0xdb.toByte(), 0x45.toByte(), 0x1f.toByte(), 0x04.toByte(), 0x04.toByte(), 0x08.toByte(), 0xfe.toByte(), 0x10.toByte(), 0x0d.toByte(), + 0x13.toByte(), 0x30.toByte(), 0x10.toByte(), 0xc2.toByte(), 0xd2.toByte(), 0xd0.toByte(), 0xfe.toByte(), 0x40.toByte(), 0x30.toByte(), 0x06.toByte(), + 0x08.toByte(), 0x85.toByte(), 0x2f.toByte(), 0xf0.toByte(), 0x40.toByte(), 0x7f.toByte(), 0x17.toByte(), 0x79.toByte(), 0xe0.toByte(), 0xfb.toByte(), + 0x86.toByte(), 0x8f.toByte(), 0x08.toByte(), 0x40.toByte(), 0x71.toByte(), 0xf4.toByte(), 0x3b.toByte(), 0x4f.toByte(), 0xec.toByte(), 0xe4.toByte(), + 0x00.toByte(), 0x06.toByte(), 0x46.toByte(), 0x2a.toByte(), 0x7c.toByte(), 0x3f.toByte(), 0xb7.toByte(), 0xe2.toByte(), 0xfb.toByte(), 0x09.toByte(), + 0x80.toByte(), 0x60.toByte(), 0xfe.toByte(), 0xb8.toByte(), 0xff.toByte(), 0x53.toByte(), 0xfa.toByte(), 0x04.toByte(), 0x07.toByte(), 0xd4.toByte(), + 0x52.toByte(), 0xdb.toByte(), 0xfe.toByte(), 0xce.toByte(), 0xd6.toByte(), 0x21.toByte(), 0x25.toByte(), 0x4e.toByte(), 0x03.toByte(), 0x0c.toByte(), + 0x06.toByte(), 0x18.toByte(), 0xc5.toByte(), 0xde.toByte(), 0xf7.toByte(), 0xc3.toByte(), 0x8f.toByte(), 0x17.toByte(), 0x47.toByte(), 0xf1.toByte(), + 0xfb.toByte(), 0xc6.toByte(), 0xff.toByte(), 0xfb.toByte(), 0xc2.toByte(), 0x5f.toByte(), 0xff.toByte(), 0xbc.toByte(), 0xee.toByte(), 0xf3.toByte(), + 0x88.toByte(), 0x40.toByte(), 0x18.toByte(), 0x7f.toByte(), 0x61.toByte(), 0xdc.toByte(), 0x44.toByte(), 0x5e.toByte(), 0xec.toByte(), 0x39.toByte(), + 0xd1.toByte(), 0x13.toByte(), 0x7a.toByte(), 0x41.toByte(), 0x08.toByte(), 0x83.toByte(), 0x3f.toByte(), 0xe8.toByte(), 0xf7.toByte(), 0x80.toByte(), + 0xfb.toByte(), 0x89.toByte(), 0xff.toByte(), 0x0b.toByte(), 0x04.toByte(), 0xc0.toByte(), 0x07.toByte(), 0x04.toByte(), 0x2f.toByte(), 0x00.toByte(), + 0xbf.toByte(), 0xe0.toByte(), 0xff.toByte(), 0x3b.toByte(), 0x10.toByte(), 0x00.toByte(), 0x04.toByte(), 0x10.toByte(), 0x31.toByte(), 0xef.toByte(), + 0x1b.toByte(), 0xde.toByte(), 0x33.toByte(), 0xe6.toByte(), 0x23.toByte(), 0xc2.toByte(), 0xec.toByte(), 0x03.toByte(), 0xfe.toByte(), 0xef.toByte(), + 0xfa.toByte(), 0x1f.toByte(), 0x40.toByte(), 0x03.toByte(), 0x62.toByte(), 0xc4.toByte(), 0x20.toByte(), 0xb0.toByte(), 0x0c.toByte(), 0xef.toByte(), + 0xf2.toByte(), 0x32.toByte(), 0x06.toByte(), 0x10.toByte(), 0xbf.toByte(), 0xe0.toByte(), 0x03.toByte(), 0x3f.toByte(), 0x20.toByte(), 0xf4.toByte(), + 0x02.toByte(), 0x30.toByte(), 0x00.toByte(), 0x42.toByte(), 0xff.toByte(), 0x0f.toByte(), 0x7e.toByte(), 0xb0.toByte(), 0x0f.toByte(), 0xbd.toByte(), + 0xd2.toByte(), 0xfb.toByte(), 0x07.toByte(), 0x50.toByte(), 0xdc.toByte(), 0x82.toByte(), 0xc1.toByte(), 0x07.toByte(), 0xbe.toByte(), 0x20.toByte(), + 0xe8.toByte(), 0xbb.toByte(), 0xf0.toByte(), 0xef.toByte(), 0x81.toByte(), 0x20.toByte(), 0xec.toByte(), 0x02.toByte(), 0x01.toByte(), 0xf8.toByte(), + 0x04.toByte(), 0x18.toByte(), 0x42.toByte(), 0xdc.toByte(), 0x0e.toByte(), 0x02.toByte(), 0xe1.toByte(), 0x6f.toByte(), 0xee.toByte(), 0x20.toByte(), + 0xb1.toByte(), 0x40.toByte(), 0x30.toByte(), 0x10.toByte(), 0xa4.toByte(), 0xc4.toByte(), 0x11.toByte(), 0x49.toByte(), 0x3e.toByte(), 0x0d.toByte(), + 0xf7.toByte(), 0x31.toByte(), 0x03.toByte(), 0x00.toByte(), 0x1e.toByte(), 0x4f.toByte(), 0xec.toByte(), 0xcf.toByte(), 0xce.toByte(), 0x1d.toByte(), + 0xe1.toByte(), 0xe2.toByte(), 0x10.toByte(), 0x20.toByte(), 0x1e.toByte(), 0xf1.toByte(), 0x2e.toByte(), 0xfe.toByte(), 0x06.toByte(), 0x11.toByte(), + 0xfe.toByte(), 0x40.toByte(), 0x00.toByte(), 0xc5.toByte(), 0x30.toByte(), 0xf4.toByte(), 0xbc.toByte(), 0xd0.toByte(), 0xfb.toByte(), 0xbd.toByte(), + 0x20.toByte(), 0xf8.toByte(), 0xc3.toByte(), 0x00.toByte(), 0xfc.toByte(), 0xfd.toByte(), 0xa0.toByte(), 0x00.toByte(), 0x40.toByte(), 0xf0.toByte(), + 0xff.toByte(), 0xb5.toByte(), 0xe0.toByte(), 0xff.toByte(), 0x7e.toByte(), 0xff.toByte(), 0xff.toByte(), 0x80.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x05.toByte(), 0x20.toByte(), 0xf4.toByte(), 0x44.toByte(), 0xc0.toByte(), 0x03.toByte(), 0xbc.toByte(), 0x00.toByte(), 0x00.toByte() + ) + } + + private object HrOfflineMockData { + const val expectedSize = 9 // calculated from hrOfflineFrame + + // index + // 39 0x00, + const val hrSample0 = 0 + + // 40 0xFF, + const val hrSample1 = 255 + + // 41 0x32, + const val hrSample2 = 50 + + // last index 0x55, + const val hrSampleLast = 85 + + val hrOfflineFrame = byteArrayOf( + 0x00.toByte(), + // header + 0x2b.toByte(), 0x4c.toByte(), 0x7c.toByte(), 0x3d.toByte(), 0x01.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), + 0x00.toByte(), 0x00.toByte(), 0x08.toByte(), 0xfb.toByte(), 0xce.toByte(), 0x14.toByte(), + + // start time + 0x32.toByte(), 0x30.toByte(), 0x32.toByte(), 0x32.toByte(), 0x2d.toByte(), 0x31.toByte(), 0x32.toByte(), 0x2d.toByte(), 0x30.toByte(), 0x37.toByte(), + 0x20.toByte(), 0x30.toByte(), 0x37.toByte(), 0x3a.toByte(), 0x30.toByte(), 0x34.toByte(), 0x3a.toByte(), 0x32.toByte(), 0x38.toByte(), 0x00.toByte(), + + // settings + 0x00.toByte(), + + // security strategy + 0x00.toByte(), + + // packet size 19 bytes + 0x13.toByte(), 0x00.toByte(), + + // THE DATA + // measurement type + 0x0e.toByte(), //Offline hr + // time stamp + 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00, + // data raw.toByte(), type 0 + 0x00.toByte(), + 0x00.toByte(), 0xFF.toByte(), 0x32.toByte(), 0x32.toByte(), 0x33.toByte(), 0x33.toByte(), 0x34.toByte(), 0x35.toByte(), 0x55.toByte() + ) + } +} \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/common/ble/TypeUtilsTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/common/ble/TypeUtilsTest.kt index 0c3808ab..87fd19ad 100644 --- a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/common/ble/TypeUtilsTest.kt +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/common/ble/TypeUtilsTest.kt @@ -1,6 +1,6 @@ package com.polar.androidcommunications.common.ble -import org.junit.Assert +import org.junit.Assert.assertEquals import org.junit.Assert.assertThrows import org.junit.Test @@ -16,7 +16,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToUnsignedByte(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -29,7 +29,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToUnsignedInt(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -42,7 +42,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToUnsignedInt(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -55,7 +55,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToUnsignedInt(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -68,7 +68,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToUnsignedInt(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -92,7 +92,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToUnsignedLong(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -105,7 +105,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToUnsignedLong(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -118,7 +118,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToUnsignedLong(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -131,7 +131,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToUnsignedLong(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -155,7 +155,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToSignedInt(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -168,7 +168,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToSignedInt(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -181,7 +181,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToSignedInt(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @Test @@ -194,7 +194,7 @@ class TypeUtilsTest { val result = TypeUtils.convertArrayToSignedInt(byteArray) // Assert - Assert.assertEquals(expectedValue, result) + assertEquals(expectedValue, result) } @@ -208,4 +208,20 @@ class TypeUtilsTest { TypeUtils.convertArrayToSignedInt(byteArray) } } -} \ No newline at end of file + + @Test + fun `test conversion unsigned byte to int`() { + // Arrange + val testByte1: Byte = 0x00.toByte() + val testByte2: Byte = 0x80.toByte() + val testByte3: Byte = 0xFF.toByte() + val testByte4: Byte = 0x55.toByte() + + // Act & Assert + assertEquals(0, TypeUtils.convertUnsignedByteToInt(testByte1)) + assertEquals(128, TypeUtils.convertUnsignedByteToInt(testByte2)) + assertEquals(255, TypeUtils.convertUnsignedByteToInt(testByte3)) + assertEquals(85, TypeUtils.convertUnsignedByteToInt(testByte4)) + + } +} diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/sdk/api/model/utils/PolarTimeUtilsTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/sdk/api/model/utils/PolarTimeUtilsTest.kt index 19c0f91f..702fe0de 100644 --- a/sources/Android/android-communications/library/src/test/java/com/polar/sdk/api/model/utils/PolarTimeUtilsTest.kt +++ b/sources/Android/android-communications/library/src/test/java/com/polar/sdk/api/model/utils/PolarTimeUtilsTest.kt @@ -1,6 +1,7 @@ package com.polar.sdk.api.model.utils import com.polar.androidcommunications.testrules.BleLoggerTestRule +import com.polar.sdk.impl.utils.PolarTimeUtils import fi.polar.remote.representation.protobuf.Types import org.junit.Assert import org.junit.Rule From e8367ed4dd9133b455ecb6007df3f515e6ef5b2a Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Mon, 23 Jan 2023 14:59:02 +0100 Subject: [PATCH 02/58] Android Example: - creation of local gradle property to enable local SDK build easily - updates android example according to new SDK refactorings - implements minimal offline recording management (Verity Sense device) --- .../androidBleSdkTestApp/app/build.gradle | 13 +- .../app/src/main/AndroidManifest.xml | 6 +- .../com/polar/androidblesdk/MainActivity.kt | 199 +++++++++++++++--- .../app/src/main/res/layout/activity_main.xml | 39 ++++ .../app/src/main/res/values/strings.xml | 5 + .../androidBleSdkTestApp/build.gradle | 1 + .../androidBleSdkTestApp/gradle.properties | 2 +- .../androidBleSdkTestApp/settings.gradle | 2 +- .../library/build.gradle | 1 - 9 files changed, 229 insertions(+), 39 deletions(-) diff --git a/examples/example-android/androidBleSdkTestApp/app/build.gradle b/examples/example-android/androidBleSdkTestApp/app/build.gradle index 2743c6fa..2ab260f4 100644 --- a/examples/example-android/androidBleSdkTestApp/app/build.gradle +++ b/examples/example-android/androidBleSdkTestApp/app/build.gradle @@ -11,6 +11,7 @@ android { versionCode 1 versionName "1.0" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + multiDexEnabled true } buildTypes { release { @@ -19,10 +20,6 @@ android { } } - defaultConfig { - multiDexEnabled true - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -31,12 +28,12 @@ android { dependencies { implementation 'com.github.polarofficial:polar-ble-sdk:4.0.0' - implementation 'io.reactivex.rxjava3:rxjava:3.1.5' - implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' + implementation 'io.reactivex.rxjava3:rxjava:3.1.6' + implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' implementation 'androidx.appcompat:appcompat:1.6.0' implementation "androidx.core:core-ktx:1.9.0" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20" - implementation 'com.google.android.material:material:1.7.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.21" + implementation 'com.google.android.material:material:1.8.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/examples/example-android/androidBleSdkTestApp/app/src/main/AndroidManifest.xml b/examples/example-android/androidBleSdkTestApp/app/src/main/AndroidManifest.xml index b96efcb7..9dd1c043 100644 --- a/examples/example-android/androidBleSdkTestApp/app/src/main/AndroidManifest.xml +++ b/examples/example-android/androidBleSdkTestApp/app/src/main/AndroidManifest.xml @@ -1,10 +1,12 @@ - + + android:usesPermissionFlags="neverForLocation" + tools:targetApi="s" /> diff --git a/examples/example-android/androidBleSdkTestApp/app/src/main/java/com/polar/androidblesdk/MainActivity.kt b/examples/example-android/androidBleSdkTestApp/app/src/main/java/com/polar/androidblesdk/MainActivity.kt index 2deb1b42..fd9cf225 100644 --- a/examples/example-android/androidBleSdkTestApp/app/src/main/java/com/polar/androidblesdk/MainActivity.kt +++ b/examples/example-android/androidBleSdkTestApp/app/src/main/java/com/polar/androidblesdk/MainActivity.kt @@ -17,6 +17,7 @@ import com.google.android.material.snackbar.Snackbar import com.polar.sdk.api.PolarBleApi import com.polar.sdk.api.PolarBleApiCallback import com.polar.sdk.api.PolarBleApiDefaultImpl +import com.polar.sdk.api.PolarH10OfflineExerciseApi import com.polar.sdk.api.errors.PolarInvalidArgument import com.polar.sdk.api.model.* import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers @@ -25,6 +26,7 @@ import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.functions.Function import java.util.* +import kotlin.time.measureTimedValue class MainActivity : AppCompatActivity() { companion object { @@ -37,8 +39,17 @@ class MainActivity : AppCompatActivity() { private var deviceId = "ACF7222C" private val api: PolarBleApi by lazy { - // Notice PolarBleApi.ALL_FEATURES are enabled - PolarBleApiDefaultImpl.defaultImplementation(applicationContext, PolarBleApi.ALL_FEATURES) + // Notice all features are enabled + PolarBleApiDefaultImpl.defaultImplementation(applicationContext, + setOf(PolarBleApi.PolarBleSdkFeature.FEATURE_HR, + PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_SDK_MODE, + PolarBleApi.PolarBleSdkFeature.FEATURE_BATTERY_INFO, + PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_H10_EXERCISE_RECORDING, + PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_OFFLINE_RECORDING, + PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_ONLINE_STREAMING, + PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP, + PolarBleApi.PolarBleSdkFeature.FEATURE_DEVICE_INFO) + ) } private lateinit var broadcastDisposable: Disposable private var scanDisposable: Disposable? = null @@ -81,6 +92,15 @@ class MainActivity : AppCompatActivity() { private lateinit var getTimeButton: Button private lateinit var toggleSdkModeButton: Button + //Verity Sense offline recroding use + private lateinit var listRecordingsButton: Button + private lateinit var startRecordingButton: Button + private lateinit var stopRecordingButton: Button + private lateinit var downloadRecordingButton: Button + private lateinit var deleteRecordingButton: Button + private val entryCache: MutableMap> = mutableMapOf() + + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -104,6 +124,12 @@ class MainActivity : AppCompatActivity() { setTimeButton = findViewById(R.id.set_time) getTimeButton = findViewById(R.id.get_time) toggleSdkModeButton = findViewById(R.id.toggle_SDK_mode) + //Verity Sense recording buttons + listRecordingsButton = findViewById(R.id.list_recordings) + startRecordingButton = findViewById(R.id.start_recording) + stopRecordingButton = findViewById(R.id.stop_recording) + downloadRecordingButton = findViewById(R.id.download_recording) + deleteRecordingButton = findViewById(R.id.delete_recording) api.setPolarFilter(false) api.setApiLogger { s: String -> Log.d(API_LOGGER_TAG, s) } @@ -140,17 +166,6 @@ class MainActivity : AppCompatActivity() { toggleButtonUp(toggleSdkModeButton, R.string.enable_sdk_mode) } - override fun streamingFeaturesReady(identifier: String, features: Set) { - for (feature in features) { - Log.d(TAG, "Streaming feature $feature is ready") - } - } - - override fun hrFeatureReady(identifier: String) { - Log.d(TAG, "HR READY: $identifier") - // hr notifications are about to start - } - override fun disInformationReceived(identifier: String, uuid: UUID, value: String) { Log.d(TAG, "DIS INFO uuid: $uuid value: $value") } @@ -159,12 +174,8 @@ class MainActivity : AppCompatActivity() { Log.d(TAG, "BATTERY LEVEL: $level") } - override fun hrNotificationReceived(identifier: String, data: PolarHrData) { - Log.d(TAG, "HR value: ${data.hr} rrsMs: ${data.rrsMs} rr: ${data.rrs} contact: ${data.contactStatus} , ${data.contactStatusSupported}") - } - - override fun polarFtpFeatureReady(identifier: String) { - Log.d(TAG, "FTP ready") + override fun hrNotificationReceived(identifier: String, data: PolarHrData.PolarHrSample) { + Log.d(TAG, "HR value: ${data.hr} rrsMs: ${data.rrsMs} contact: ${data.contactStatus} , ${data.contactStatusSupported}") } }) @@ -246,7 +257,7 @@ class MainActivity : AppCompatActivity() { val isDisposed = ecgDisposable?.isDisposed ?: true if (isDisposed) { toggleButtonDown(ecgButton, R.string.stop_ecg_stream) - ecgDisposable = requestStreamSettings(deviceId, PolarBleApi.DeviceStreamingFeature.ECG) + ecgDisposable = requestStreamSettings(deviceId, PolarBleApi.PolarDeviceDataType.ECG) .flatMap { settings: PolarSensorSetting -> api.startEcgStreaming(deviceId, settings) } @@ -273,7 +284,7 @@ class MainActivity : AppCompatActivity() { val isDisposed = accDisposable?.isDisposed ?: true if (isDisposed) { toggleButtonDown(accButton, R.string.stop_acc_stream) - accDisposable = requestStreamSettings(deviceId, PolarBleApi.DeviceStreamingFeature.ACC) + accDisposable = requestStreamSettings(deviceId, PolarBleApi.PolarDeviceDataType.ACC) .flatMap { settings: PolarSensorSetting -> api.startAccStreaming(deviceId, settings) } @@ -305,7 +316,7 @@ class MainActivity : AppCompatActivity() { if (isDisposed) { toggleButtonDown(gyrButton, R.string.stop_gyro_stream) gyrDisposable = - requestStreamSettings(deviceId, PolarBleApi.DeviceStreamingFeature.GYRO) + requestStreamSettings(deviceId, PolarBleApi.PolarDeviceDataType.GYRO) .flatMap { settings: PolarSensorSetting -> api.startGyroStreaming(deviceId, settings) } @@ -334,7 +345,7 @@ class MainActivity : AppCompatActivity() { if (isDisposed) { toggleButtonDown(magButton, R.string.stop_mag_stream) magDisposable = - requestStreamSettings(deviceId, PolarBleApi.DeviceStreamingFeature.MAGNETOMETER) + requestStreamSettings(deviceId, PolarBleApi.PolarDeviceDataType.MAGNETOMETER) .flatMap { settings: PolarSensorSetting -> api.startMagnetometerStreaming(deviceId, settings) } @@ -363,7 +374,7 @@ class MainActivity : AppCompatActivity() { if (isDisposed) { toggleButtonDown(ppgButton, R.string.stop_ppg_stream) ppgDisposable = - requestStreamSettings(deviceId, PolarBleApi.DeviceStreamingFeature.PPG) + requestStreamSettings(deviceId, PolarBleApi.PolarDeviceDataType.PPG) .flatMap { settings: PolarSensorSetting -> api.startOhrStreaming(deviceId, settings) } @@ -511,7 +522,7 @@ class MainActivity : AppCompatActivity() { val isDisposed = recordingStartStopDisposable?.isDisposed ?: true if (isDisposed) { val recordIdentifier = "TEST_APP_ID" - recordingStartStopDisposable = api.startRecording(deviceId, recordIdentifier, PolarBleApi.RecordingInterval.INTERVAL_1S, PolarBleApi.SampleType.HR) + recordingStartStopDisposable = api.startRecording(deviceId, recordIdentifier, PolarH10OfflineExerciseApi.RecordingInterval.INTERVAL_1S, PolarH10OfflineExerciseApi.SampleType.HR) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { @@ -623,6 +634,130 @@ class MainActivity : AppCompatActivity() { ) } + + listRecordingsButton.setOnClickListener { + api.listOfflineRecordings(deviceId) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe { + entryCache[deviceId] = mutableListOf() + } + .map { + entryCache[deviceId]?.add(it) + it + } + .subscribe( + { polarOfflineRecordingEntry: PolarOfflineRecordingEntry -> + Log.d( + TAG, + "next: ${polarOfflineRecordingEntry.date} path: ${polarOfflineRecordingEntry.path} size: ${polarOfflineRecordingEntry.size}" + ) + }, + { error: Throwable -> Log.e(TAG, "Failed to list recordings: $error") }, + { Log.d(TAG, "list recordings complete") } + ) + } + + startRecordingButton.setOnClickListener { + //Example of starting ACC offline recording + Log.d(TAG, "Starts ACC recording") + val settings: MutableMap = mutableMapOf() + settings[PolarSensorSetting.SettingType.SAMPLE_RATE] = 52 + settings[PolarSensorSetting.SettingType.RESOLUTION] = 16 + settings[PolarSensorSetting.SettingType.RANGE] = 8 + settings[PolarSensorSetting.SettingType.CHANNELS] = 3 + //Using a secret key managed by your own. + // You can use a different key to each start recording calls. + // When using key at start recording, it is also needed for the recording download, otherwise could not be decrypted + val yourSecret = PolarRecordingSecret(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)) + api.startOfflineRecording(deviceId, PolarBleApi.PolarDeviceDataType.ACC, PolarSensorSetting(settings.toMap()), yourSecret) + //Without a secret key + //api.startOfflineRecording(deviceId, PolarBleApi.PolarDeviceDataType.ACC, PolarSensorSetting(settings.toMap())) + .subscribe( + { Log.d(TAG, "start offline recording completed") }, + { throwable: Throwable -> Log.e(TAG, "" + throwable.toString()) } + ) + } + + stopRecordingButton.setOnClickListener { + //Example of stopping ACC offline recording + Log.d(TAG, "Stops ACC recording") + api.stopOfflineRecording(deviceId, PolarBleApi.PolarDeviceDataType.ACC) + .subscribe( + { Log.d(TAG, "stop offline recording completed") }, + { throwable: Throwable -> Log.e(TAG, "" + throwable.toString()) } + ) + } + + downloadRecordingButton.setOnClickListener { + //Example of one offline recording download + //NOTE: For this example you need to click on listRecordingsButton to have files entry (entryCache) up to date + Log.d(TAG, "Searching to recording to download... ") + //Get first entry for testing download + val offlineRecEntry = entryCache[deviceId]?.firstOrNull(); + offlineRecEntry?.let { offlineEntry -> + try { + //Using a secret key managed by your own. + // You can use a different key to each start recording calls. + // When using key at start recording, it is also needed for the recording download, otherwise could not be decrypted + val yourSecret = PolarRecordingSecret(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)) + api.getOfflineRecord(deviceId, offlineEntry, yourSecret) + //Not using a secret key + //api.getOfflineRecord(deviceId, offlineEntry) + .subscribe( + { + Log.d(TAG, "Recording ${offlineEntry.path} downloaded. Size: ${offlineEntry.size}") + when (it) { + is PolarOfflineRecordingData.AccOfflineRecording -> { + Log.d(TAG, "ACC Recording started at ${it.startTime}") + for (sample in it.data.samples) { + Log.d(TAG,"ACC data: time: ${sample.timeStamp} X: ${sample.x} Y: ${sample.y} Z: ${sample.z}") + } + } +// is PolarOfflineRecordingData.GyroOfflineRecording -> { } +// is PolarOfflineRecordingData.MagOfflineRecording -> { } +// ... + else -> { + Log.d(TAG, "Recording type is not yet implemented") + } + } + }, + { throwable: Throwable -> Log.e(TAG, "" + throwable.toString()) } + ) + } catch (e: Exception) { + Log.e(TAG, "Get offline recording fetch failed on entry ...", e) + } + } + } + + deleteRecordingButton.setOnClickListener { + //Example of one offline recording deletion + //NOTE: For this example you need to click on listRecordingsButton to have files entry (entryCache) up to date + Log.d(TAG, "Searching to recording to delete... ") + //Get first entry for testing deletion + val offlineRecEntry = entryCache[deviceId]?.firstOrNull(); + offlineRecEntry?.let { offlineEntry -> + try { + api.removeOfflineRecord(deviceId, offlineEntry) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + Log.d(TAG, "Recording file deleted") + }, + { error -> + val errorString = "Recording file deletion failed: $error" + showToast(errorString) + Log.e(TAG, errorString) + } + ) + + } catch (e: Exception) { + Log.e(TAG, "Delete offline recording failed on entry ...", e) + } + } + } + toggleSdkModeButton.setOnClickListener { toggleSdkModeButton.isEnabled = false if (!sdkModeEnabledStatus) { @@ -736,7 +871,7 @@ class MainActivity : AppCompatActivity() { button.background = buttonDrawable } - private fun requestStreamSettings(identifier: String, feature: PolarBleApi.DeviceStreamingFeature): Flowable { + private fun requestStreamSettings(identifier: String, feature: PolarBleApi.PolarDeviceDataType): Flowable { val availableSettings = api.requestStreamSettings(identifier, feature) val allSettings = api.requestFullStreamSettings(identifier, feature) .onErrorReturn { error: Throwable -> @@ -806,6 +941,12 @@ class MainActivity : AppCompatActivity() { setTimeButton.isEnabled = false getTimeButton.isEnabled = false toggleSdkModeButton.isEnabled = false + //Verity Sense recording buttons + listRecordingsButton.isEnabled = false + startRecordingButton.isEnabled = false + stopRecordingButton.isEnabled = false + downloadRecordingButton.isEnabled = false + deleteRecordingButton.isEnabled = false } private fun enableAllButtons() { @@ -828,6 +969,12 @@ class MainActivity : AppCompatActivity() { setTimeButton.isEnabled = true getTimeButton.isEnabled = true toggleSdkModeButton.isEnabled = true + //Verity Sense recording buttons + listRecordingsButton.isEnabled = true + startRecordingButton.isEnabled = true + stopRecordingButton.isEnabled = true + downloadRecordingButton.isEnabled = true + deleteRecordingButton.isEnabled = true } private fun disposeAllStreams() { diff --git a/examples/example-android/androidBleSdkTestApp/app/src/main/res/layout/activity_main.xml b/examples/example-android/androidBleSdkTestApp/app/src/main/res/layout/activity_main.xml index f7dc07b9..40ed3afe 100644 --- a/examples/example-android/androidBleSdkTestApp/app/src/main/res/layout/activity_main.xml +++ b/examples/example-android/androidBleSdkTestApp/app/src/main/res/layout/activity_main.xml @@ -152,5 +152,44 @@ android:layout_height="wrap_content" android:text="@string/get_time" /> + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
Modifier and TypeMethodDescription
final String + getName() +
final Integer + getOrdinal() +
+
+
    + +
  • + + +

    Methods inherited from class java.lang.Object

    + clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, + wait, wait, wait
  • +
+ + + + + + + +
+ +
+ + + + + + diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.DeviceStreamingFeature.html b/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.PolarDeviceDataType.html similarity index 91% rename from polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.DeviceStreamingFeature.html rename to polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.PolarDeviceDataType.html index 99d57a69..5412332f 100644 --- a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.DeviceStreamingFeature.html +++ b/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.PolarDeviceDataType.html @@ -1,7 +1,7 @@ - DeviceStreamingFeature + PolarDeviceDataType @@ -89,7 +89,7 @@
Package 
-

Enum PolarBleApi.DeviceStreamingFeature

+

Enum PolarBleApi.PolarDeviceDataType

@@ -376,7 +382,7 @@

getName

>
  • getOrdinal

    -
     final Integer getOrdinal()
    +
     final Integer getOrdinal()
    diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.html b/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.html index 769e2e62..b5c48818 100644 --- a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.html +++ b/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.html @@ -108,6 +108,24 @@

    Class PolarBleApi

    All Implemented Interfaces:
    + com.polar.sdk.api.PolarH10OfflineExerciseApi + + , + + + com.polar.sdk.api.PolarOfflineRecordingApi + + , + + + com.polar.sdk.api.PolarOnlineStreamingApi + + , + + + com.polar.sdk.api.PolarSdkModeApi + +
    @@ -115,7 +133,7 @@

    Class PolarBleApi

     
     public abstract class PolarBleApi
    -
    + implements PolarOnlineStreamingApi, PolarOfflineRecordingApi, PolarH10OfflineExerciseApi, PolarSdkModeApi
                         

    Polar BLE API.

  • @@ -141,39 +159,25 @@

    Nested Class Summary

    Description - - public interface - PolarBleApi.PolarBleApiLogger - -

    Logger interface for logging events from SDK. Shall be used only for tracing and debugging purposes.

    - - - - public enum - PolarBleApi.DeviceStreamingFeature - -

    Device stream features in Polar devices. The device streaming features requires the .FEATURE_POLAR_SENSOR_STREAMING

    - - public enum - PolarBleApi.RecordingInterval + PolarBleApi.PolarBleSdkFeature -

    Recoding intervals for H10 recording start

    +

    Features available in Polar BLE SDK library

    - public enum - PolarBleApi.SampleType + public interface + PolarBleApi.PolarBleApiLogger -

    Sample types for H10 recording start

    +

    Logger interface for logging events from SDK. Shall be used only for tracing and debugging purposes.

    - public class - PolarBleApi.Companion + public enum + PolarBleApi.PolarDeviceDataType - +

    The data types available in Polar devices for online streaming or offline recording.

    @@ -200,8 +204,8 @@

    Field Summary

    - private final Integer - features + private final Set<PolarBleApi.PolarBleSdkFeature> + features @@ -231,7 +235,7 @@

    Constructor Summary

    PolarBleApi(Integer features) + href="#PolarBleApi(Set)">PolarBleApi(Set<PolarBleApi.PolarBleSdkFeature> features) @@ -285,7 +289,7 @@

    Method Summary

    - final Integer + final Set<PolarBleApi.PolarBleSdkFeature> getFeatures() @@ -297,7 +301,7 @@

    Method Summary

    setMtu(@IntRange(from = 70, to = 512) Integer mtu) - set mtu to lower than default(232 is the default for polar devices, minimum for H10 is 70 and for OH1 is 140) to minimize latency + set mtu to lower than default (232 is the default for polar devices, minimum for H10 is 70 and for OH1 is 140) to minimize latency @@ -321,13 +325,13 @@

    Method Summary

    setPolarFilter(Boolean enable) - When enabled only Polar devices are found by the . + When enabled only Polar devices are found by the searchForDevice, if set to false any BLE devices with HR services are returned by the searchForDevice. abstract Boolean - isFeatureReady(String deviceId, Integer feature) + isFeatureReady(String deviceId, PolarBleApi.PolarBleSdkFeature feature) Check if the feature is ready. @@ -335,33 +339,33 @@

    Method Summary

    abstract Unit - backgroundEntered() + foregroundEntered() - enables scan filter while on background + Optionally call when application enters to the foreground. abstract Unit - foregroundEntered() + setApiCallback(PolarBleApiCallbackProvider callback) - Optionally call when application enters to the foreground. + Sets the API callback abstract Unit - setApiCallback(PolarBleApiCallbackProvider callback) + setApiLogger(PolarBleApi.PolarBleApiLogger logger) - Sets the API callback + Sets the API logger - abstract Unit + abstract Flowable<PolarDeviceInfo> - setApiLogger(PolarBleApi.PolarBleApiLogger logger) + searchForDevice() - Sets the API logger + Starts searching for BLE devices when subscribed. @@ -373,38 +377,6 @@

    Method Summary

    - abstract Completable - - setLocalTime(String identifier, Calendar calendar) - - Set time to device affects on sensor data stream(s) timestamps requires feature . - - - - abstract Single<Calendar> - - getLocalTime(String identifier) - - Get current time in device. - - - - abstract Single<PolarSensorSetting> - - requestStreamSettings(String identifier, PolarBleApi.DeviceStreamingFeature feature) - - Request the stream settings available in current operation mode. - - - - abstract Single<PolarSensorSetting> - - requestFullStreamSettings(String identifier, PolarBleApi.DeviceStreamingFeature feature) - - Request full steam settings capabilities. - - - abstract Completable autoConnectToDevice(Integer rssiLimit, String service, Integer timeout, TimeUnit unit, String polarDeviceType) @@ -412,7 +384,7 @@

    Method Summary

    Start connecting to a nearby Polar device. - + abstract Completable autoConnectToDevice(Integer rssiLimit, String service, String polarDeviceType) @@ -420,7 +392,7 @@

    Method Summary

    - + abstract Unit connectToDevice(String identifier) @@ -428,7 +400,7 @@

    Method Summary

    Request a connection to a BLE device. - + abstract Unit disconnectFromDevice(String identifier) @@ -436,63 +408,23 @@

    Method Summary

    Request disconnecting from a BLE device. - - abstract Completable - - startRecording(String identifier, @Size(min = 1, max = 64) String exerciseId, PolarBleApi.RecordingInterval interval, PolarBleApi.SampleType type) - - Request start recording. - - - - abstract Completable - - stopRecording(String identifier) - - Request to stop recording. - - - - abstract Single<Pair<Boolean, String>> - - requestRecordingStatus(String identifier) - - Request current recording status. - - - - abstract Flowable<PolarExerciseEntry> - - listExercises(String identifier) - - List exercises stored in the device Polar H10 device. - - - - abstract Single<PolarExerciseData> - - fetchExercise(String identifier, PolarExerciseEntry entry) - - Api for fetching a single exercise from Polar H10 device. - - - + abstract Completable - removeExercise(String identifier, PolarExerciseEntry entry) + setLocalTime(String identifier, Calendar calendar) - Api for removing single exercise from Polar H10 device. + Set the device time. - - abstract Flowable<PolarDeviceInfo> + + abstract Single<Calendar> - searchForDevice() + getLocalTime(String identifier) - Starts searching for BLE devices when subscribed. + Get current time in device. - + abstract Flowable<PolarHrBroadcastData> startListenForPolarHrBroadcasts(Set<String> deviceIds) @@ -500,75 +432,17 @@

    Method Summary

    Start listening the heart rate from Polar devices when subscribed. - - abstract Flowable<PolarEcgData> - - startEcgStreaming(String identifier, PolarSensorSetting sensorSetting) - - Start the ECG (Electrocardiography) stream. - - - - abstract Flowable<PolarAccelerometerData> - - startAccStreaming(String identifier, PolarSensorSetting sensorSetting) - - Start ACC (Accelerometer) stream. - - - - abstract Flowable<PolarOhrData> - - startOhrStreaming(String identifier, PolarSensorSetting sensorSetting) - - Start OHR (Optical heart rate) PPG (Photoplethysmography) stream. - - - - abstract Flowable<PolarOhrPPIData> - - startOhrPPIStreaming(String identifier) - - Start OHR (Optical heart rate) PPI (Pulse to Pulse interval) stream. - - - - abstract Flowable<PolarMagnetometerData> - - startMagnetometerStreaming(String identifier, PolarSensorSetting sensorSetting) - - Start magnetometer stream. - - - - abstract Flowable<PolarGyroData> - - startGyroStreaming(String identifier, PolarSensorSetting sensorSetting) - - Start Gyro stream. - - - - abstract Completable - - enableSDKMode(String identifier) - - Enables SDK mode. - - - - abstract Completable - - disableSDKMode(String identifier) - - Disables SDK mode. - -
      +
    • + + +

      Methods inherited from class com.polar.sdk.api.PolarBleApi

      + disableSDKMode, enableSDKMode, fetchExercise, getAvailableOfflineRecordingDataTypes, getAvailableOnlineStreamDataTypes, getOfflineRecord, getOfflineRecordingStatus, getOfflineRecordingTriggerSetup, isSDKModeEnabled, listExercises, listOfflineRecordings, removeExercise, removeOfflineRecord, requestFullOfflineRecordingSettings, requestFullStreamSettings, requestOfflineRecordingSettings, requestRecordingStatus, requestStreamSettings, setOfflineRecordingTrigger, startAccStreaming, startEcgStreaming, startGyroStreaming, startHrStreaming, startMagnetometerStreaming, startOfflineRecording, startOhrPPIStreaming, startOhrStreaming, startPpgStreaming, startRecording, stopOfflineRecording, stopRecording
    • +
    • @@ -595,22 +469,13 @@

      Methods inherited from class java.lang.Object

      Constructor Detail

      - + @@ -635,7 +500,7 @@

      Method Detail

      >
    • getFeatures

      -
       final Integer getFeatures()
      +
       final Set<PolarBleApi.PolarBleSdkFeature> getFeatures()
      @@ -651,7 +516,7 @@

      getFeatures

    • setMtu

       abstract Unit setMtu(@IntRange(from = 70, to = 512) Integer mtu)
      -

      set mtu to lower than default(232 is the default for polar devices, minimum for H10 is 70 and for OH1 is 140) to minimize latency

      +

      set mtu to lower than default (232 is the default for polar devices, minimum for H10 is 70 and for OH1 is 140) to minimize latency

      Parameters:
      @@ -705,7 +570,7 @@

      cleanup

    • setPolarFilter

       abstract Unit setPolarFilter(Boolean enable)
      -

      When enabled only Polar devices are found by the .searchForDevice, if set to false any BLE devices with HR services are returned by the .searchForDevice. The default setting for Polar filter is true.

      +

      When enabled only Polar devices are found by the searchForDevice, if set to false any BLE devices with HR services are returned by the searchForDevice. The default setting for Polar filter is true.

      Parameters:
      @@ -720,7 +585,7 @@

      setPolarFilter

    - + - - - -
      -
    • -

      backgroundEntered

      -
      @Deprecated(message = "in release 3.2.8. Move to the background is not relevant information for SDK starting from release 3.2.8") abstract Unit backgroundEntered()
      -

      enables scan filter while on background

      - - -
    • -
    - @@ -826,6 +676,21 @@

    setApiLogger

    + + + +
      +
    • +

      searchForDevice

      +
       abstract Flowable<PolarDeviceInfo> searchForDevice()
      +

      Starts searching for BLE devices when subscribed. Search continues as long as observable is subscribed or error. Each found device is emitted only once. By default searches only for Polar devices, but can be controlled by .setPolarFilter. If .setPolarFilter is false then searches for any BLE heart rate capable devices

      + + +
    • +
    + @@ -850,26 +715,38 @@

    setAutomaticReconnection

    - +
    • -

      setLocalTime

      -
       abstract Completable setLocalTime(String identifier, Calendar calendar)
      -

      Set time to device affects on sensor data stream(s) timestamps requires feature .FEATURE_POLAR_FILE_TRANSFER

      +

      autoConnectToDevice

      +
       abstract Completable autoConnectToDevice(Integer rssiLimit, String service, Integer timeout, TimeUnit unit, String polarDeviceType)
      +

      Start connecting to a nearby Polar device. PolarBleApiCallback.deviceConnected callback is invoked when connection to a nearby device is established.

      Parameters:
      -
      identifier - polar device id or bt address
      +
      rssiLimit - RSSI (Received Signal Strength Indication) value is typically from -40 to -60 (dBm), depends on the used Bluetooth chipset and/or antenna tuning
      -
      calendar - time to set
      +
      service - in hex string format like "180D" PolarInvalidArgument invoked if not in correct format
      + + + +
      timeout - min time to search nearby device default = 2s
      + + + +
      unit - time unit to be used
      + + + +
      polarDeviceType - like H10, OH1 etc...
      @@ -878,22 +755,37 @@

      setLocalTime

    - + + + + + +
      +
    • +

      connectToDevice

      +
       abstract Unit connectToDevice(String identifier)
      +

      Request a connection to a BLE device. Invokes PolarBleApiCallback.deviceConnected callback.

      Parameters:
      -
      identifier - polar device id or bt address
      +
      identifier - Polar device id found printed on the sensor/device (in format "12345678") or bt address (in format "00:11:22:33:44:55")
      @@ -902,26 +794,22 @@

      getLocalTime

    - +
    • -

      requestStreamSettings

      -
       abstract Single<PolarSensorSetting> requestStreamSettings(String identifier, PolarBleApi.DeviceStreamingFeature feature)
      -

      Request the stream settings available in current operation mode. This request shall be used before the stream is started to decide currently available. The available settings depend on the state of the device. For example, if any stream(s) or optical heart rate measurement is already enabled, then the device may limit the offer of possible settings for other stream feature. Requires feature .FEATURE_POLAR_SENSOR_STREAMING

      +

      disconnectFromDevice

      +
       abstract Unit disconnectFromDevice(String identifier)
      +

      Request disconnecting from a BLE device. Invokes PolarBleApiCallback.deviceDisconnected callback.

      Parameters:
      -
      identifier - polar device id or bt address
      - - - -
      feature - the stream feature of interest
      +
      identifier - Polar device id found printed on the sensor/device or bt address (in format "00:11:22:33:44:55")
      @@ -930,16 +818,16 @@

      requestStreamSettings

    - +
    • -

      requestFullStreamSettings

      -
       abstract Single<PolarSensorSetting> requestFullStreamSettings(String identifier, PolarBleApi.DeviceStreamingFeature feature)
      -

      Request full steam settings capabilities. The request returns the all capabilities of the requested streaming feature not limited by the current operation mode. Requires feature .FEATURE_POLAR_SENSOR_STREAMING. This request is supported only by Polar Verity Sense (starting from firmware 1.1.5)

      +

      setLocalTime

      +
       abstract Completable setLocalTime(String identifier, Calendar calendar)
      +

      Set the device time. Requires feature PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP

      Parameters:
      @@ -949,7 +837,7 @@

      requestFullStreamSettings

      -
      feature - the stream feature of interest
      +
      calendar - time to set
      @@ -958,265 +846,22 @@

      requestFullStreamSettings

    - +
    • -

      autoConnectToDevice

      -
       abstract Completable autoConnectToDevice(Integer rssiLimit, String service, Integer timeout, TimeUnit unit, String polarDeviceType)
      -

      Start connecting to a nearby Polar device. PolarBleApiCallback.deviceConnected callback is invoked when connection to a nearby device is established.

      +

      getLocalTime

      +
       abstract Single<Calendar> getLocalTime(String identifier)
      +

      Get current time in device. Requires feature PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP

      Parameters:
      -
      rssiLimit - RSSI (Received Signal Strength Indication) value is typically from -40 to -60 (dBm), depends on the used Bluetooth chipset and/or antenna tuning
      - - - -
      service - in hex string format like "180D" PolarInvalidArgument invoked if not in correct format
      - - - -
      timeout - min time to search nearby device default = 2s
      - - - -
      unit - time unit to be used
      - - - -
      polarDeviceType - like H10, OH1 etc...
      - - -
      - - -
    • -
    - - - - - - - - - -
      -
    • -

      connectToDevice

      -
       abstract Unit connectToDevice(String identifier)
      -

      Request a connection to a BLE device. Invokes PolarBleApiCallback.deviceConnected callback.

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device (in format "12345678") or bt address (in format "00:11:22:33:44:55")
      - - -
      - - -
    • -
    - - - - - - - - - -
      -
    • -

      startRecording

      -
       abstract Completable startRecording(String identifier, @Size(min = 1, max = 64) String exerciseId, PolarBleApi.RecordingInterval interval, PolarBleApi.SampleType type)
      -

      Request start recording. Supported only by Polar H10. Requires feature .FEATURE_POLAR_FILE_TRANSFER

      - -
      -
      Parameters:
      - - -
      identifier - polar device id or bt address
      - - - -
      exerciseId - unique id for exercise entry
      - - - -
      interval - recording interval to be used, parameter has no effect if the type parameter is SampleType.
      - - - -
      type - sample type to be used
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      stopRecording

      -
       abstract Completable stopRecording(String identifier)
      -

      Request to stop recording. Supported only by Polar H10. Requires feature .FEATURE_POLAR_FILE_TRANSFER

      - -
      -
      Parameters:
      - - -
      identifier - polar device id or bt address
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      requestRecordingStatus

      -
       abstract Single<Pair<Boolean, String>> requestRecordingStatus(String identifier)
      -

      Request current recording status. Supported only by Polar H10. Requires feature .FEATURE_POLAR_FILE_TRANSFER

      - -
      -
      Parameters:
      - - -
      identifier - polar device id or bt address
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      listExercises

      -
       abstract Flowable<PolarExerciseEntry> listExercises(String identifier)
      -

      List exercises stored in the device Polar H10 device. Requires feature .FEATURE_POLAR_FILE_TRANSFER. This API is working for Polar OH1 and Polar Verity Sense devices too, however in those devices recording of exercise requires that sensor is registered to Polar Flow account.

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      fetchExercise

      -
       abstract Single<PolarExerciseData> fetchExercise(String identifier, PolarExerciseEntry entry)
      -

      Api for fetching a single exercise from Polar H10 device. Requires feature .FEATURE_POLAR_FILE_TRANSFER. This API is working for Polar OH1 and Polar Verity Sense devices too, however in those devices recording of exercise requires that sensor is registered to Polar Flow account.

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - - -
      entry - PolarExerciseEntry object
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      removeExercise

      -
       abstract Completable removeExercise(String identifier, PolarExerciseEntry entry)
      -

      Api for removing single exercise from Polar H10 device. Requires feature .FEATURE_POLAR_FILE_TRANSFER. This API is working for Polar OH1 and Polar Verity Sense devices too, however in those devices recording of exercise requires that sensor is registered to Polar Flow account.

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - - -
      entry - entry to be removed
      +
      identifier - polar device id or bt address
      @@ -1225,26 +870,11 @@

      removeExercise

    - - - -
      -
    • -

      searchForDevice

      -
       abstract Flowable<PolarDeviceInfo> searchForDevice()
      -

      Starts searching for BLE devices when subscribed. Search continues as long as observable is subscribed or error. Each found device is emitted only once. By default searches only for Polar devices, but can be controlled by .setPolarFilter. If .setPolarFilter is false then searches for any BLE heart rate capable devices

      - - -
    • -
    -
    • startListenForPolarHrBroadcasts

      @@ -1264,218 +894,6 @@

      startListenForPolarHrBroadcasts

    - - - -
      -
    • -

      startEcgStreaming

      -
       abstract Flowable<PolarEcgData> startEcgStreaming(String identifier, PolarSensorSetting sensorSetting)
      -

      Start the ECG (Electrocardiography) stream. ECG stream is stopped if the connection is closed, error occurs or stream is disposed. Requires feature .FEATURE_POLAR_SENSOR_STREAMING. Before starting the stream it is recommended to query the available settings using .requestStreamSettings

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - - -
      sensorSetting - settings to be used to start streaming
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      startAccStreaming

      -
       abstract Flowable<PolarAccelerometerData> startAccStreaming(String identifier, PolarSensorSetting sensorSetting)
      -

      Start ACC (Accelerometer) stream. ACC stream is stopped if the connection is closed, error occurs or stream is disposed. Requires feature .FEATURE_POLAR_SENSOR_STREAMING. Before starting the stream it is recommended to query the available settings using .requestStreamSettings

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - - -
      sensorSetting - settings to be used to start streaming
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      startOhrStreaming

      -
       abstract Flowable<PolarOhrData> startOhrStreaming(String identifier, PolarSensorSetting sensorSetting)
      -

      Start OHR (Optical heart rate) PPG (Photoplethysmography) stream. PPG stream is stopped if the connection is closed, error occurs or stream is disposed. Requires feature .FEATURE_POLAR_SENSOR_STREAMING. Before starting the stream it is recommended to query the available settings using .requestStreamSettings

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - - -
      sensorSetting - settings to be used to start streaming
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      startOhrPPIStreaming

      -
       abstract Flowable<PolarOhrPPIData> startOhrPPIStreaming(String identifier)
      -

      Start OHR (Optical heart rate) PPI (Pulse to Pulse interval) stream. PPI stream is stopped if the connection is closed, error occurs or stream is disposed. Notice that there is a delay before PPI data stream starts. Requires feature .FEATURE_POLAR_SENSOR_STREAMING.

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      startMagnetometerStreaming

      -
       abstract Flowable<PolarMagnetometerData> startMagnetometerStreaming(String identifier, PolarSensorSetting sensorSetting)
      -

      Start magnetometer stream. Magnetometer stream is stopped if the connection is closed, error occurs or stream is disposed. Requires feature .FEATURE_POLAR_SENSOR_STREAMING. Before starting the stream it is recommended to query the available settings using .requestStreamSettings

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - - -
      sensorSetting - settings to be used to start streaming
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      startGyroStreaming

      -
       abstract Flowable<PolarGyroData> startGyroStreaming(String identifier, PolarSensorSetting sensorSetting)
      -

      Start Gyro stream. Gyro stream is stopped if the connection is closed, error occurs during start or stream is disposed. Requires feature .FEATURE_POLAR_SENSOR_STREAMING. Before starting the stream it is recommended to query the available settings using .requestStreamSettings

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - - -
      sensorSetting - settings to be used to start streaming
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      enableSDKMode

      -
       abstract Completable enableSDKMode(String identifier)
      -

      Enables SDK mode. In SDK mode the wider range of capabilities is available for the stream than in normal operation mode. SDK mode is only supported by Polar Verity Sense (starting from firmware 1.1.5). Requires feature .FEATURE_POLAR_SENSOR_STREAMING.

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - -
      - - -
    • -
    - - - - -
      -
    • -

      disableSDKMode

      -
       abstract Completable disableSDKMode(String identifier)
      -

      Disables SDK mode. SDK mode is only supported by Polar Verity Sense (starting from firmware 1.1.5). Requires feature .FEATURE_POLAR_SENSOR_STREAMING.

      - -
      -
      Parameters:
      - - -
      identifier - Polar device id found printed on the sensor/device or bt address
      - - -
      - - -
    • -
    - diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApiCallback.html b/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApiCallback.html index e4f3cbf3..8c0b613d 100644 --- a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApiCallback.html +++ b/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApiCallback.html @@ -251,7 +251,7 @@

    Method Summary

    blePowerStateChanged(Boolean powered) - + Bluetooth power state of the device where this SDK is running @@ -275,18 +275,26 @@

    Method Summary

    deviceDisconnected(PolarDeviceInfo polarDeviceInfo) - Device is now disconnected, no further action is needed from the application if polar.com.sdk.api.PolarBleApi#disconnectFromPolarDevice is not called. + Device is now disconnected Unit - streamingFeaturesReady(String identifier, Set<PolarBleApi.DeviceStreamingFeature> features) + bleSdkFeatureReady(String identifier, PolarBleApi.PolarBleSdkFeature feature) - Polar device's streaming features ready. + Called when the feature in connected device is available and it is ready. + Unit + + streamingFeaturesReady(String identifier, Set<PolarBleApi.PolarDeviceDataType> features) + + Polar device's streaming features ready. + + + Unit sdkModeFeatureAvailable(String identifier) @@ -294,7 +302,7 @@

    Method Summary

    Polar SDK Mode feature is available in the device. - + Unit hrFeatureReady(String identifier) @@ -302,31 +310,31 @@

    Method Summary

    Polar device HR client is now ready and HR transmission is starting in a moment. - + Unit disInformationReceived(String identifier, UUID uuid, String value) - DIS information received requires feature PolarBleApi#FEATURE_DEVICE_INFO + DIS information received. - + Unit batteryLevelReceived(String identifier, Integer level) - Battery level received requires feature PolarBleApi#FEATURE_BATTERY_INFO + Battery level received. - + Unit - hrNotificationReceived(String identifier, PolarHrData data) + hrNotificationReceived(String identifier, PolarHrData.PolarHrSample data) HR notification data received from device. - + Unit polarFtpFeatureReady(String identifier) @@ -397,7 +405,7 @@

    Method Detail

  • blePowerStateChanged

     Unit blePowerStateChanged(Boolean powered)
    -
    +

    Bluetooth power state of the device where this SDK is running

    Parameters:
    @@ -469,7 +477,7 @@

    deviceConnecting

  • deviceDisconnected

     Unit deviceDisconnected(PolarDeviceInfo polarDeviceInfo)
    -

    Device is now disconnected, no further action is needed from the application if polar.com.sdk.api.PolarBleApi#disconnectFromPolarDevice is not called. Device will be automatically reconnected

    +

    Device is now disconnected

    Parameters:
    @@ -484,6 +492,34 @@

    deviceDisconnected

  • + + + +
      +
    • +

      bleSdkFeatureReady

      +
       Unit bleSdkFeatureReady(String identifier, PolarBleApi.PolarBleSdkFeature feature)
      +

      Called when the feature in connected device is available and it is ready. Called only for the features which are specified by PolarBleApi instantiation.

      + +
      +
      Parameters:
      + + +
      identifier - Polar device id
      + + + +
      feature - feature is ready
      + + +
      + + +
    • +
    + @@ -492,8 +528,8 @@

    deviceDisconnected

    >
  • streamingFeaturesReady

    -
     Unit streamingFeaturesReady(String identifier, Set<PolarBleApi.DeviceStreamingFeature> features)
    -

    Polar device's streaming features ready. Application may start any stream now if desired. Requires feature PolarBleApi.FEATURE_POLAR_SENSOR_STREAMING to be enabled for the Polar SDK instance.

    +
    @Deprecated(message = "The functionality has changed. Please use the bleSdkFeatureReady to know if onlineStreaming is available and the getAvailableOnlineStreamDataTypes function know which data types are supported") Unit streamingFeaturesReady(String identifier, Set<PolarBleApi.PolarDeviceDataType> features)
    +

    Polar device's streaming features ready. Application may start any stream now if desired. Requires feature PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_ONLINE_STREAMING to be enabled for the Polar SDK instance.

    Parameters:
    @@ -520,8 +556,8 @@

    streamingFeaturesReady

    >
  • sdkModeFeatureAvailable

    -
     Unit sdkModeFeatureAvailable(String identifier)
    -

    Polar SDK Mode feature is available in the device. Application may now enter to SDK mode. Requires feature PolarBleApi#FEATURE_POLAR_SENSOR_STREAMING

    +
    @Deprecated(message = "Information whether SDK Mode is available is provided by bleSdkFeatureReady") Unit sdkModeFeatureAvailable(String identifier)
    +

    Polar SDK Mode feature is available in the device. Application may now enter to SDK mode. Requires feature PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_SDK_MODE

    Parameters:
    @@ -544,7 +580,7 @@

    sdkModeFeatureAvailable

    >
  • hrFeatureReady

    -
     Unit hrFeatureReady(String identifier)
    +
    @Deprecated(message = "Information whether HR feature is available is provided by bleSdkFeatureReady") Unit hrFeatureReady(String identifier)

    Polar device HR client is now ready and HR transmission is starting in a moment.

    @@ -569,7 +605,7 @@

    hrFeatureReady

  • disInformationReceived

     Unit disInformationReceived(String identifier, UUID uuid, String value)
    -

    DIS information received requires feature PolarBleApi#FEATURE_DEVICE_INFO

    +

    DIS information received. Requires feature PolarBleApi.PolarBleSdkFeature.FEATURE_DEVICE_INFO

    Parameters:
    @@ -601,7 +637,7 @@

    disInformationReceived

  • batteryLevelReceived

     Unit batteryLevelReceived(String identifier, Integer level)
    -

    Battery level received requires feature PolarBleApi#FEATURE_BATTERY_INFO

    +

    Battery level received. Requires feature PolarBleApi.PolarBleSdkFeature.FEATURE_BATTERY_INFO

    Parameters:
    @@ -620,7 +656,7 @@

    batteryLevelReceived

  • - + + + + +
      +
    • +

      bleSdkFeatureReady

      +
       abstract Unit bleSdkFeatureReady(String identifier, PolarBleApi.PolarBleSdkFeature feature)
      +

      The feature is available in this device and it is ready. Called only for the features which are specified in PolarBleApi construction.

      + +
      +
      Parameters:
      + + +
      identifier - Polar device id
      + + + +
      feature - feature is ready
      + + +
      + + +
    • +
    + @@ -473,8 +509,8 @@

    deviceDisconnected

    >
  • streamingFeaturesReady

    -
     abstract Unit streamingFeaturesReady(String identifier, Set<PolarBleApi.DeviceStreamingFeature> features)
    -

    Polar device's streaming features ready. Application may start any stream now if desired. Requires feature PolarBleApi#FEATURE_POLAR_SENSOR_STREAMING

    +
    @Deprecated(message = "The function is renamed. Please use the getAvailableOnlineStreamDataTypes function") abstract Unit streamingFeaturesReady(String identifier, Set<PolarBleApi.PolarDeviceDataType> features)
    +

    Polar device's streaming features ready. Application may start any stream now if desired. Requires feature PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_ONLINE_STREAMING to be enabled for the Polar SDK instance.

    Parameters:
    @@ -501,8 +537,8 @@

    streamingFeaturesReady

    >
  • sdkModeFeatureAvailable

    -
     abstract Unit sdkModeFeatureAvailable(String identifier)
    -

    Polar SDK Mode feature is available in the device. Application may now enter to SDK mode. Requires feature PolarBleApi#FEATURE_POLAR_SENSOR_STREAMING

    +
    @Deprecated(message = "Information whether SDK Mode is available is provided by bleSdkFeatureReady") abstract Unit sdkModeFeatureAvailable(String identifier)
    +

    Polar SDK Mode feature is available in the device. Application may now enter to SDK mode. Requires feature PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_SDK_MODE

    Parameters:
    @@ -525,7 +561,7 @@

    sdkModeFeatureAvailable

    >
  • hrFeatureReady

    -
     abstract Unit hrFeatureReady(String identifier)
    +
    @Deprecated(message = "Information whether HR feature is available is provided by bleSdkFeatureReady") abstract Unit hrFeatureReady(String identifier)

    Polar device HR client is now ready and HR transmission is starting in a moment.

    @@ -550,7 +586,7 @@

    hrFeatureReady

  • disInformationReceived

     abstract Unit disInformationReceived(String identifier, UUID uuid, String value)
    -

    DIS information received requires feature PolarBleApi#FEATURE_DEVICE_INFO

    +

    DIS information received. Requires feature PolarBleApi.PolarBleSdkFeature.FEATURE_DEVICE_INFO

    Parameters:
    @@ -582,7 +618,7 @@

    disInformationReceived

  • batteryLevelReceived

     abstract Unit batteryLevelReceived(String identifier, Integer level)
    -

    Battery level received requires feature PolarBleApi#FEATURE_BATTERY_INFO

    +

    Battery level received. Requires feature PolarBleApi.PolarBleSdkFeature.FEATURE_BATTERY_INFO

    Parameters:
    @@ -601,7 +637,7 @@

    batteryLevelReceived

  • - +
      batteryLevelReceived >
    • hrNotificationReceived

      -
       abstract Unit hrNotificationReceived(String identifier, PolarHrData data)
      +
      @Deprecated(message = "Please use the startHrStreaming API to get the heart rate data ") abstract Unit hrNotificationReceived(String identifier, PolarHrData.PolarHrSample data)

      HR notification data received from device. Notice when using OH1 and PPI measurement is started hr received from this callback is 0.

      @@ -637,7 +673,7 @@

      hrNotificationReceived

      >
    • polarFtpFeatureReady

      -
       abstract Unit polarFtpFeatureReady(String identifier)
      +
      @Deprecated(message = "Not supported anymore, won't be ever called. Use the bleSdkFeatureReady") abstract Unit polarFtpFeatureReady(String identifier)

      File transfer ready requires feature PolarBleApi#FEATURE_POLAR_FILE_TRANSFER

      diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApiDefaultImpl.html b/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApiDefaultImpl.html index a5f5230e..81474637 100644 --- a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApiDefaultImpl.html +++ b/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApiDefaultImpl.html @@ -244,9 +244,9 @@

      Method Summary

      - final static PolarBleApi + final static BDBleApiImpl - defaultImplementation(Context context, Integer features) + defaultImplementation(Context context, Set<PolarBleApi.PolarBleSdkFeature> features) Default implementation constructor for the API. @@ -303,7 +303,7 @@

      Constructor Detail

      Method Detail

      - +
        Method Detail >
      • defaultImplementation

        -
         final static PolarBleApi defaultImplementation(Context context, Integer features)
        +
         final static BDBleApiImpl defaultImplementation(Context context, Set<PolarBleApi.PolarBleSdkFeature> features)

        Default implementation constructor for the API.

        diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.RecordingInterval.html b/polar-sdk-android/docs/com/polar/sdk/api/PolarH10OfflineExerciseApi.RecordingInterval.html similarity index 93% rename from polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.RecordingInterval.html rename to polar-sdk-android/docs/com/polar/sdk/api/PolarH10OfflineExerciseApi.RecordingInterval.html index 1049bba3..cd508000 100644 --- a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.RecordingInterval.html +++ b/polar-sdk-android/docs/com/polar/sdk/api/PolarH10OfflineExerciseApi.RecordingInterval.html @@ -89,7 +89,7 @@
        Package 
        -

        Enum PolarBleApi.RecordingInterval

        +

        Enum PolarH10OfflineExerciseApi.RecordingInterval

        @@ -366,7 +366,7 @@

        getValue

        >
      • getName

        -
         final String getName()
        +
         final String getName()
        @@ -381,7 +381,7 @@

        getName

        >
      • getOrdinal

        -
         final Integer getOrdinal()
        +
         final Integer getOrdinal()
        diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.SampleType.html b/polar-sdk-android/docs/com/polar/sdk/api/PolarH10OfflineExerciseApi.SampleType.html similarity index 95% rename from polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.SampleType.html rename to polar-sdk-android/docs/com/polar/sdk/api/PolarH10OfflineExerciseApi.SampleType.html index 460f6d01..ffe2ceb9 100644 --- a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.SampleType.html +++ b/polar-sdk-android/docs/com/polar/sdk/api/PolarH10OfflineExerciseApi.SampleType.html @@ -89,7 +89,7 @@
        Package 
        -

        Enum PolarBleApi.SampleType

        +

        Enum PolarH10OfflineExerciseApi.SampleType

        @@ -352,7 +352,7 @@

        getName

        >
      • getOrdinal

        -
         final Integer getOrdinal()
        +
         final Integer getOrdinal()
        diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarH10OfflineExerciseApi.html b/polar-sdk-android/docs/com/polar/sdk/api/PolarH10OfflineExerciseApi.html new file mode 100644 index 00000000..472551e7 --- /dev/null +++ b/polar-sdk-android/docs/com/polar/sdk/api/PolarH10OfflineExerciseApi.html @@ -0,0 +1,573 @@ + + + + PolarH10OfflineExerciseApi + + + + + + + + + + + + + + + + + +
        + +
        + +
        +
        +
        Package 
        +

        Interface PolarH10OfflineExerciseApi

        +
        +
        + +
        +
          +
        • + +
          +
          All Implemented Interfaces:
          +
          + +
          +
          + +
          +
          +
          +public interface PolarH10OfflineExerciseApi
          +
          +                    
          +

          H10 Exercise recording API.

          H10 Exercise recording makes it possible to record Hr or Rr data to H10 device memory. With H10 Exercise recording the H10 and phone don't need to be connected all the time, as H10 exercise recording continues in Polar device even the BLE disconnects.

          Requires features PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_H10_EXERCISE_RECORDING

          Note, API is working only with Polar H10 device

          +
        • +
        +
        +
        + +
        +
        +
          +
        • + + +
          +
            +
          • + + +

            Constructor Detail

            + +
          • +
          +
          + + + +
          +
            +
          • + + +

            Method Detail

            + + + + + + + + + +
              +
            • +

              stopRecording

              +
               abstract Completable stopRecording(String identifier)
              +

              Request to stop recording.

              + +
              +
              Parameters:
              + + +
              identifier - polar device id or bt address
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              requestRecordingStatus

              +
               abstract Single<Pair<Boolean, String>> requestRecordingStatus(String identifier)
              +

              Request current recording status.

              + +
              +
              Parameters:
              + + +
              identifier - polar device id or bt address
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              listExercises

              +
               abstract Flowable<PolarExerciseEntry> listExercises(String identifier)
              +

              List exercises stored in the device Polar H10 device.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + +
              + + +
            • +
            + + + + + + + + + +
              +
            • +

              removeExercise

              +
               abstract Completable removeExercise(String identifier, PolarExerciseEntry entry)
              +

              Api for removing single exercise from Polar H10 device.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + + +
              entry - entry to be removed
              + + +
              + + +
            • +
            + +
          • +
          +
          + +
        • +
        +
        +
        +
        + + + + diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarOfflineRecordingApi.html b/polar-sdk-android/docs/com/polar/sdk/api/PolarOfflineRecordingApi.html new file mode 100644 index 00000000..2661e04f --- /dev/null +++ b/polar-sdk-android/docs/com/polar/sdk/api/PolarOfflineRecordingApi.html @@ -0,0 +1,743 @@ + + + + PolarOfflineRecordingApi + + + + + + + + + + + + + + + + + +
        + +
        + +
        +
        +
        Package 
        +

        Interface PolarOfflineRecordingApi

        +
        +
        + +
        + +
        +
        + +
        +
        +
          +
        • + + +
          +
            +
          • + + +

            Constructor Detail

            + +
          • +
          +
          + + + +
          +
            +
          • + + +

            Method Detail

            + + + + + + + + + +
              +
            • +

              requestOfflineRecordingSettings

              +
               abstract Single<PolarSensorSetting> requestOfflineRecordingSettings(String identifier, PolarBleApi.PolarDeviceDataType feature)
              +

              Request the offline recording settings available in current operation mode. This request shall be used before the offline recording is started to decide currently available settings. The available settings depend on the state of the device.

              + +
              +
              Parameters:
              + + +
              identifier - polar device id or bt address
              + + + +
              feature - the stream feature of interest
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              requestFullOfflineRecordingSettings

              +
               abstract Single<PolarSensorSetting> requestFullOfflineRecordingSettings(String identifier, PolarBleApi.PolarDeviceDataType feature)
              +

              Request all the settings available in the device. The request returns the all capabilities of the requested streaming feature not limited by the current operation mode.

              + +
              +
              Parameters:
              + + +
              identifier - polar device id or bt address
              + + + +
              feature - the stream feature of interest
              + + +
              + + +
            • +
            + + + + + + + + + +
              +
            • +

              listOfflineRecordings

              +
               abstract Flowable<PolarOfflineRecordingEntry> listOfflineRecordings(String identifier)
              +

              List offline recordings stored in the device.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + +
              + + +
            • +
            + + + + + + + + + +
              +
            • +

              removeOfflineRecord

              +
               abstract Completable removeOfflineRecord(String identifier, PolarOfflineRecordingEntry entry)
              +

              Removes offline recording from the device

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + + +
              entry - entry to be removed
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              startOfflineRecording

              +
               abstract Completable startOfflineRecording(String identifier, PolarBleApi.PolarDeviceDataType feature, PolarSensorSetting settings, PolarRecordingSecret secret)
              +

              Start offline recording.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + + +
              feature - the feature to be started
              + + + +
              settings - settings for the started offline recording.
              + + + +
              secret - if the secret is provided the offline recordings are encrypted in device
              + + +
              + + +
            • +
            + + + + + + + + + +
              +
            • +

              setOfflineRecordingTrigger

              +
               abstract Completable setOfflineRecordingTrigger(String identifier, PolarOfflineRecordingTrigger trigger, PolarRecordingSecret secret)
              +

              Set the offline recording triggers. The offline recording can be started automatically by the device if the PolarOfflineRecordingTriggerMode is set enabled. If offline recordings is already recoding the PolarOfflineRecordingTriggerMode won't have affect on running recordings. Change of trigger settings will take effect on next device start up.

              Automatically started offline recording can be stopped by stopOfflineRecording. If user switches off the device power the offline recording is stopped, but starts again once power in device is switched on and the trigger event happens. Trigger functionality can be disabled by PolarOfflineRecordingTriggerMode.TRIGGER_DISABLED, however the already running offline recording is not stopped by disable.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + + +
              trigger - the type of trigger
              + + + +
              secret - if the secret is provided the offline recordings are encrypted in deviceProduces: <BR></BR> - onComplete offline recording trigger set successfully <BR></BR> - onError offline recording trigger set failed.
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              getOfflineRecordingTriggerSetup

              +
               abstract Single<PolarOfflineRecordingTrigger> getOfflineRecordingTriggerSetup(String identifier)
              +

              Get the setup of offline recording triggers in the device.

              The setup is the current offline recording trigger setup in the device. To know which offline recordings are currently recording use the getOfflineRecordingStatus

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt addressProduces: <BR></BR> - onSuccess the offline recording trigger setup in the device <BR></BR> - onError fetch recording request failed
              + + +
              + + +
            • +
            + +
          • +
          +
          + +
        • +
        +
        +
        +
        + + + + diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarOnlineStreamingApi.html b/polar-sdk-android/docs/com/polar/sdk/api/PolarOnlineStreamingApi.html new file mode 100644 index 00000000..2c903056 --- /dev/null +++ b/polar-sdk-android/docs/com/polar/sdk/api/PolarOnlineStreamingApi.html @@ -0,0 +1,731 @@ + + + + PolarOnlineStreamingApi + + + + + + + + + + + + + + + + + +
        + +
        + +
        +
        +
        Package 
        +

        Interface PolarOnlineStreamingApi

        +
        +
        + +
        +
          +
        • + +
          +
          All Implemented Interfaces:
          +
          + +
          +
          + +
          +
          +
          +public interface PolarOnlineStreamingApi
          +
          +                    
          +

          Online steaming API.

          Online streaming makes it possible to stream live online data from Polar device.

          Requires features FEATURE_POLAR_ONLINE_STREAMING

          Note, online streaming is supported by VeritySense, H10 and OH1 devices

          +
        • +
        +
        +
        + +
        +
        +
          +
        • + + +
          +
            +
          • + + +

            Constructor Detail

            + +
          • +
          +
          + + + +
          +
            +
          • + + +

            Method Detail

            + + + + + + + + + +
              +
            • +

              requestStreamSettings

              +
               abstract Single<PolarSensorSetting> requestStreamSettings(String identifier, PolarBleApi.PolarDeviceDataType feature)
              +

              Request the online stream settings available in current operation mode. This request shall be used before the stream is started to decide currently available settings. The available settings depend on the state of the device. For example, if any stream(s) or optical heart rate measurement is already enabled, then the device may limit the offer of possible settings for other stream feature.

              + +
              +
              Parameters:
              + + +
              identifier - polar device id or bt address
              + + + +
              feature - the stream feature of interest
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              requestFullStreamSettings

              +
               abstract Single<PolarSensorSetting> requestFullStreamSettings(String identifier, PolarBleApi.PolarDeviceDataType feature)
              +

              Request the settings available in the device. The request returns the all capabilities of the requested streaming feature not limited by the current operation mode.

              Note, This request is supported only by Polar Verity Sense (starting from firmware 1.1.5)

              + +
              +
              Parameters:
              + + +
              identifier - polar device id or bt address
              + + + +
              feature - the stream feature of interest
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              startHrStreaming

              +
               abstract Flowable<PolarHrData> startHrStreaming(String identifier)
              +

              Start heart rate stream. Heart rate stream is stopped if the connection is closed, error occurs or stream is disposed.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              startEcgStreaming

              +
               abstract Flowable<PolarEcgData> startEcgStreaming(String identifier, PolarSensorSetting sensorSetting)
              +

              Start the ECG (Electrocardiography) stream. ECG stream is stopped if the connection is closed, error occurs or stream is disposed.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + + +
              sensorSetting - settings to be used to start streaming
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              startAccStreaming

              +
               abstract Flowable<PolarAccelerometerData> startAccStreaming(String identifier, PolarSensorSetting sensorSetting)
              +

              Start ACC (Accelerometer) stream. ACC stream is stopped if the connection is closed, error occurs or stream is disposed.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + + +
              sensorSetting - settings to be used to start streaming
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              startOhrStreaming

              +
              @Deprecated(message = "API is renamed, please use startPpgStreaming()", replaceWith = @ReplaceWith(imports = {}, expression = "startPpgStreaming")) abstract Flowable<PolarOhrData> startOhrStreaming(String identifier, PolarSensorSetting sensorSetting)
              +

              Start OHR (Optical heart rate) PPG (Photoplethysmography) stream. PPG stream is stopped if the connection is closed, error occurs or stream is disposed.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + + +
              sensorSetting - settings to be used to start streaming
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              startPpgStreaming

              +
               abstract Flowable<PolarPpgData> startPpgStreaming(String identifier, PolarSensorSetting sensorSetting)
              +

              Start optical sensor PPG (Photoplethysmography) stream. PPG stream is stopped if the connection is closed, error occurs or stream is disposed.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + + +
              sensorSetting - settings to be used to start streaming
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              startOhrPPIStreaming

              +
               abstract Flowable<PolarOhrPPIData> startOhrPPIStreaming(String identifier)
              +

              Start OHR (Optical heart rate) PPI (Pulse to Pulse interval) stream. PPI stream is stopped if the connection is closed, error occurs or stream is disposed. Notice that there is a delay before PPI data stream starts.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              startMagnetometerStreaming

              +
               abstract Flowable<PolarMagnetometerData> startMagnetometerStreaming(String identifier, PolarSensorSetting sensorSetting)
              +

              Start magnetometer stream. Magnetometer stream is stopped if the connection is closed, error occurs or stream is disposed.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + + +
              sensorSetting - settings to be used to start streaming
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              startGyroStreaming

              +
               abstract Flowable<PolarGyroData> startGyroStreaming(String identifier, PolarSensorSetting sensorSetting)
              +

              Start Gyro stream. Gyro stream is stopped if the connection is closed, error occurs during start or stream is disposed.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + + +
              sensorSetting - settings to be used to start streaming
              + + +
              + + +
            • +
            + +
          • +
          +
          + +
        • +
        +
        +
        +
        + + + + diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarSdkModeApi.html b/polar-sdk-android/docs/com/polar/sdk/api/PolarSdkModeApi.html new file mode 100644 index 00000000..19b51901 --- /dev/null +++ b/polar-sdk-android/docs/com/polar/sdk/api/PolarSdkModeApi.html @@ -0,0 +1,443 @@ + + + + PolarSdkModeApi + + + + + + + + + + + + + + + + + +
        + +
        + +
        +
        +
        Package 
        +

        Interface PolarSdkModeApi

        +
        +
        + +
        + +
        +
        +
          +
        • + + +
          +
            +
          • + + +

            Nested Class Summary

            +
            + + + + + + + + +
            Nested Classes 
            Modifier and TypeClassDescription
            +
            +
          • +
          +
          + + + +
          +
            +
          • + + +

            Field Summary

            +
            + + + + + + + + +
            Fields 
            Modifier and TypeFieldDescription
            +
            +
          • +
          +
          + + + +
          +
            +
          • + + +

            Constructor Summary

            +
            + + + + + + + + + + + +
            Constructors 
            ConstructorDescription
            +
            +
          • +
          +
          + + + +
          +
            +
          • + + +

            Enum Constant Summary

            + + + + + + + +
            Enum Constants 
            Enum ConstantDescription
            +
          • +
          +
          + + + +
          +
            +
          • + + +

            Method Summary

            +
            +
            + + +
            +
            + + + + + + + + + + + + + + + + + + + + + + + + + + +
            Modifier and TypeMethodDescription
            abstract Completable + enableSDKMode(String identifier) + Enables SDK mode.
            abstract Completable + disableSDKMode(String identifier) + Disables SDK mode.
            abstract Single<Boolean> + isSDKModeEnabled(String identifier) + Check if SDK mode currently enabled.
            +
            +
              + +
            • + + +

              Methods inherited from class java.lang.Object

              + clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, + wait, wait, wait
            • +
            +
          • +
          +
          + +
        • +
        +
        +
        +
          +
        • + + +
          +
            +
          • + + +

            Constructor Detail

            + +
          • +
          +
          + + + +
          +
            +
          • + + +

            Method Detail

            + + + + +
              +
            • +

              enableSDKMode

              +
               abstract Completable enableSDKMode(String identifier)
              +

              Enables SDK mode.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              disableSDKMode

              +
               abstract Completable disableSDKMode(String identifier)
              +

              Disables SDK mode.

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + +
              + + +
            • +
            + + + + +
              +
            • +

              isSDKModeEnabled

              +
               abstract Single<Boolean> isSDKModeEnabled(String identifier)
              +

              Check if SDK mode currently enabled.

              Note, SDK status check is supported by VeritySense starting from firmware 2.1.0

              + +
              +
              Parameters:
              + + +
              identifier - Polar device id found printed on the sensor/device or bt address
              + + +
              + + +
            • +
            + +
          • +
          +
          + +
        • +
        +
        +
        +
        + + + + diff --git a/polar-sdk-android/docs/com/polar/sdk/api/errors/PolarOfflineRecordingError.html b/polar-sdk-android/docs/com/polar/sdk/api/errors/PolarOfflineRecordingError.html new file mode 100644 index 00000000..c7aaa224 --- /dev/null +++ b/polar-sdk-android/docs/com/polar/sdk/api/errors/PolarOfflineRecordingError.html @@ -0,0 +1,430 @@ + + + + PolarOfflineRecordingError + + + + + + + + + + + + + + + + + +
        + +
        + +
        +
        +
        Package 
        +

        Class PolarOfflineRecordingError

        +
        +
        + +
        +
          +
        • + +
          +
          All Implemented Interfaces:
          +
          + + java.io.Serializable + + +
          +
          + +
          +
          +
          +public final class PolarOfflineRecordingError
          +extends Exception
          +                    
          +

          Offline recording general error

          +
        • +
        +
        +
        +
          +
        • + + +
          +
            +
          • + + +

            Nested Class Summary

            +
            + + + + + + + + +
            Nested Classes 
            Modifier and TypeClassDescription
            +
            +
          • +
          +
          + + + +
          +
            +
          • + + +

            Field Summary

            +
            + + + + + + + + + + + + + + + + + + + + +
            Fields 
            Modifier and TypeFieldDescription
            private final Throwablecause
            private final Stringmessage
            +
            +
          • +
          +
          + + + +
          + +
          + + + +
          +
            +
          • + + +

            Enum Constant Summary

            + + + + + + + +
            Enum Constants 
            Enum ConstantDescription
            +
          • +
          +
          + + + +
          +
            +
          • + + +

            Method Summary

            +
            +
            + + +
            +
            + + + + + + + + + + + + + + + + + + + + +
            Modifier and TypeMethodDescription
            Throwable + getCause() +
            String + getMessage() +
            +
            +
              + +
            • + + +

              Methods inherited from class java.lang.Exception

              + addSuppressed, fillInStackTrace, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace
            • + +
            • + + +

              Methods inherited from class java.lang.Object

              + clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, + wait, wait, wait
            • +
            +
          • +
          +
          + +
        • +
        +
        +
        +
          +
        • + + +
          +
            +
          • + + +

            Constructor Detail

            + + +
              +
            • +

              PolarOfflineRecordingError

              +
              PolarOfflineRecordingError(String detailMessage)
              +
              + +
            • +
            + +
          • +
          +
          + + + +
          + +
          + +
        • +
        +
        +
        +
        + + + + diff --git a/polar-sdk-android/docs/com/polar/sdk/api/errors/package-summary.html b/polar-sdk-android/docs/com/polar/sdk/api/errors/package-summary.html index 23c5c526..006414a3 100644 --- a/polar-sdk-android/docs/com/polar/sdk/api/errors/package-summary.html +++ b/polar-sdk-android/docs/com/polar/sdk/api/errors/package-summary.html @@ -129,9 +129,11 @@

        Package com.polar.sdk.api.errors

        PolarNotificationNotEnabledCharacteristic notification is not enabled - PolarOperationNotSupportedRequested operation is not supported + PolarOfflineRecordingErrorOffline recording general error - PolarServiceNotAvailableGATT (Generic attributes) service not available + PolarOperationNotSupportedRequested operation is not supported + + PolarServiceNotAvailableGATT (Generic attributes) service not available diff --git a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.Companion.html b/polar-sdk-android/docs/com/polar/sdk/api/model/PolarHrData.PolarHrSample.html similarity index 71% rename from polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.Companion.html rename to polar-sdk-android/docs/com/polar/sdk/api/model/PolarHrData.PolarHrSample.html index 3c25ea00..62b02be9 100644 --- a/polar-sdk-android/docs/com/polar/sdk/api/PolarBleApi.Companion.html +++ b/polar-sdk-android/docs/com/polar/sdk/api/model/PolarHrData.PolarHrSample.html @@ -1,25 +1,25 @@ - Companion + PolarHrSample - - - - - - + + + + + + - - - - - - + + + + + +