diff --git a/.github/workflows/test-kmp.yaml b/.github/workflows/test-kmp.yaml new file mode 100644 index 0000000..316ef48 --- /dev/null +++ b/.github/workflows/test-kmp.yaml @@ -0,0 +1,33 @@ +name: Test kmp + +on: + workflow_dispatch: + workflow_call: + +permissions: + contents: read + +jobs: + build: + runs-on: macos-13 + steps: + - uses: actions/checkout@v3 + - name: Select Xcode version + run: sudo xcode-select -s '/Applications/Xcode_15.0.1.app/Contents/Developer' + - name: Show Xcode version + run: xcodebuild -version + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: test-kmm + uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + with: + arguments: testDebugUnitTest + - name: Archive code coverage results + if: always() + uses: actions/upload-artifact@v1 + with: + name: test-kmp-report + path: kotlin-multiplatform/build/reports/tests/testDebugUnitTest diff --git a/.github/workflows/test-pull-request.yaml b/.github/workflows/test-pull-request.yaml new file mode 100644 index 0000000..d9a5e54 --- /dev/null +++ b/.github/workflows/test-pull-request.yaml @@ -0,0 +1,14 @@ +name: pull request unit test + +on: + workflow_dispatch: + pull_request: + +permissions: + contents: read + +jobs: + call-test-kmp: + uses: ./.github/workflows/test-kmp.yaml + call-test-rn: + uses: ./.github/workflows/test-rn.yaml diff --git a/.github/workflows/test-rn.yaml b/.github/workflows/test-rn.yaml new file mode 100644 index 0000000..015ff8c --- /dev/null +++ b/.github/workflows/test-rn.yaml @@ -0,0 +1,33 @@ +name: Test react native + +on: + workflow_dispatch: + workflow_call: + +permissions: + contents: read + +jobs: + build: + runs-on: macos-13 + steps: + - uses: actions/checkout@v3 + - name: Select Xcode version + run: sudo xcode-select -s '/Applications/Xcode_15.0.1.app/Contents/Developer' + - name: Show Xcode version + run: xcodebuild -version + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: '18.x' + - name: test-rn + run: | + cd ./react-native + yarn install + yarn test --collectCoverage + - name: Archive code coverage results + if: always() + uses: actions/upload-artifact@v1 + with: + name: test-react-native-report + path: react-native/coverage diff --git a/demos/demo-android/app/build.gradle b/demos/demo-android/app/build.gradle index 4804fd1..6db50de 100755 --- a/demos/demo-android/app/build.gradle +++ b/demos/demo-android/app/build.gradle @@ -81,5 +81,5 @@ dependencies { implementation "com.ricoh360.thetaclient:theta-client:1.5.0" - implementation "com.ricoh360.thetableclient:theta-ble-client-android:1.1.0" + implementation "com.ricoh360.thetableclient:theta-ble-client-android:1.2.0" } diff --git a/demos/demo-ios/Podfile b/demos/demo-ios/Podfile index 6a6d869..ab944ac 100644 --- a/demos/demo-ios/Podfile +++ b/demos/demo-ios/Podfile @@ -7,6 +7,6 @@ target 'demo-ios' do # Pods for demo-ios pod 'THETAClient', '1.4.0' - pod 'THETABleClient', '1.1.0' + pod 'THETABleClient', '1.2.0' end diff --git a/demos/demo-react-native/package.json b/demos/demo-react-native/package.json index 574fb29..d4acc45 100644 --- a/demos/demo-react-native/package.json +++ b/demos/demo-react-native/package.json @@ -20,7 +20,7 @@ "react-native-safe-area-context": "^4.5.1", "react-native-safe-area-view": "^1.1.1", "react-native-screens": "^3.20.0", - "theta-ble-client-react-native": "1.1.0" + "theta-ble-client-react-native": "1.2.0" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/kotlin-multiplatform/THETABleClient.podspec b/kotlin-multiplatform/THETABleClient.podspec index b9c4c7f..fcef2fa 100644 --- a/kotlin-multiplatform/THETABleClient.podspec +++ b/kotlin-multiplatform/THETABleClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'THETABleClient' - spec.version = '1.1.0' + spec.version = '1.2.0' spec.homepage = 'https://github.com/ricohapi/theta-ble-client' spec.source = { :http=> ''} spec.authors = 'Ricoh Co, Ltd.' diff --git a/kotlin-multiplatform/build.gradle.kts b/kotlin-multiplatform/build.gradle.kts index ba97f13..420ccff 100644 --- a/kotlin-multiplatform/build.gradle.kts +++ b/kotlin-multiplatform/build.gradle.kts @@ -10,7 +10,7 @@ plugins { signing } -val theta_ble_version = "1.1.0" +val theta_ble_version = "1.2.0" group = "com.ricoh360.thetableclient" version = theta_ble_version diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/BleCharacteristic.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/BleCharacteristic.kt index 34c94b6..f2c666d 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/BleCharacteristic.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/BleCharacteristic.kt @@ -80,10 +80,18 @@ enum class BleCharacteristic(val bleService: BleService, val uuid: String) { BleService.SHOOTING_CONTROL_COMMAND, "D22B7C92-556E-4038-A5EF-A9AD56899B40", ), + FILE_FORMAT( + BleService.SHOOTING_CONTROL_COMMAND, + "E8F0EDD1-6C0F-494A-95C3-3244AE0B9A01", + ), ISO( BleService.SHOOTING_CONTROL_COMMAND, "ABB94D51-189F-455B-951D-ABE9B0333080", ), + MAX_RECORDABLE_TIME( + BleService.SHOOTING_CONTROL_COMMAND, + "6EABAB73-7F2B-4061-BE7C-1D71D143CB7D", + ), TAKE_PICTURE( BleService.SHOOTING_CONTROL_COMMAND, "FEC1805C-8905-4477-B862-BA5E447528A5", diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/ThetaBle.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/ThetaBle.kt index 7586b6c..11f429d 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/ThetaBle.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/ThetaBle.kt @@ -21,6 +21,7 @@ internal const val MTU_SIZE = 512 internal const val ERROR_MESSAGE_NOT_CONNECTED = "Not connected." internal const val ERROR_MESSAGE_EMPTY_DATA = "Empty data." +internal const val ERROR_MESSAGE_RESERVED_VALUE = "Reserved value." internal const val ERROR_MESSAGE_UNKNOWN_VALUE = "Unknown value." internal const val ERROR_MESSAGE_UNSUPPORTED_VALUE = "Unsupported value." diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/ShootingControlCommand.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/ShootingControlCommand.kt index 7c84782..e3e5147 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/ShootingControlCommand.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/ShootingControlCommand.kt @@ -4,12 +4,16 @@ import com.ricoh360.thetableclient.BleCharacteristic import com.ricoh360.thetableclient.BleService import com.ricoh360.thetableclient.ERROR_MESSAGE_EMPTY_DATA import com.ricoh360.thetableclient.ERROR_MESSAGE_NOT_CONNECTED +import com.ricoh360.thetableclient.ERROR_MESSAGE_RESERVED_VALUE import com.ricoh360.thetableclient.ERROR_MESSAGE_UNKNOWN_VALUE import com.ricoh360.thetableclient.ThetaBle import com.ricoh360.thetableclient.ThetaBle.BluetoothException import com.ricoh360.thetableclient.ThetaBle.ThetaBleApiException import com.ricoh360.thetableclient.service.data.values.CaptureMode +import com.ricoh360.thetableclient.service.data.values.FileFormat +import com.ricoh360.thetableclient.service.data.values.MaxRecordableTime import com.ricoh360.thetableclient.toBytes +import com.ricoh360.thetableclient.toShort import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout @@ -82,6 +86,126 @@ class ShootingControlCommand internal constructor(thetaDevice: ThetaBle.ThetaDev } } + /** + * Acquires the recording size (pixels) of the camera. + * + * Service: 1D0F3602-8DFB-4340-9045-513040DAD991 + * Characteristic: E8F0EDD1-6C0F-494A-95C3-3244AE0B9A01 + * + * @return File format.[FileFormat] + * @exception ThetaBleApiException If an error occurs in library. + * @exception BluetoothException If an error occurs in bluetooth. + */ + @Throws(Throwable::class) + suspend fun getFileFormat(): FileFormat { + val peripheral = + thetaDevice.peripheral ?: throw ThetaBleApiException( + ERROR_MESSAGE_NOT_CONNECTED + ) + try { + val data = peripheral.read(BleCharacteristic.FILE_FORMAT) + if (data.isEmpty()) { + throw ThetaBleApiException(ERROR_MESSAGE_EMPTY_DATA) + } + return FileFormat.getFromBle(data[0]) + ?: throw ThetaBleApiException("$ERROR_MESSAGE_UNKNOWN_VALUE ${data[0]}") + } catch (e: ThetaBleApiException) { + throw e + } catch (e: Throwable) { + throw BluetoothException(e) + } + } + + /** + * Set the recording size (pixels) of the camera. + * + * Service: 1D0F3602-8DFB-4340-9045-513040DAD991 + * Characteristic: E8F0EDD1-6C0F-494A-95C3-3244AE0B9A01 + * + * @param value File format.[FileFormat] + * @exception ThetaBleApiException If an error occurs in library. + * @exception BluetoothException If an error occurs in bluetooth. + */ + @Throws(Throwable::class) + suspend fun setFileFormat(value: FileFormat) { + val peripheral = + thetaDevice.peripheral ?: throw ThetaBleApiException( + ERROR_MESSAGE_NOT_CONNECTED + ) + + if (value == FileFormat.RESERVED) { + throw ThetaBleApiException(ERROR_MESSAGE_RESERVED_VALUE) + } + value.ble ?: throw ThetaBleApiException( + ERROR_MESSAGE_UNKNOWN_VALUE + ) + + try { + val data = value.ble.toBytes() + peripheral.write(BleCharacteristic.FILE_FORMAT, data) + } catch (e: Throwable) { + throw BluetoothException(e) + } + } + + /** + * Acquires the maximum recordable time (in seconds) of the camera. + * + * Service: 1D0F3602-8DFB-4340-9045-513040DAD991 + * Characteristic: 6EABAB73-7F2B-4061-BE7C-1D71D143CB7D + * + * @return Maximum recordable time.[MaxRecordableTime] + * @exception ThetaBleApiException If an error occurs in library. + * @exception BluetoothException If an error occurs in bluetooth. + */ + @Throws(Throwable::class) + suspend fun getMaxRecordableTime(): MaxRecordableTime { + val peripheral = + thetaDevice.peripheral ?: throw ThetaBleApiException( + ERROR_MESSAGE_NOT_CONNECTED + ) + try { + val data = peripheral.read(BleCharacteristic.MAX_RECORDABLE_TIME) + if (data.isEmpty()) { + throw ThetaBleApiException(ERROR_MESSAGE_EMPTY_DATA) + } + val shortValue = data.toShort() + return MaxRecordableTime.getFromBle(shortValue) + ?: throw ThetaBleApiException("$ERROR_MESSAGE_UNKNOWN_VALUE $shortValue") + } catch (e: ThetaBleApiException) { + throw e + } catch (e: Throwable) { + throw BluetoothException(e) + } + } + + /** + * Set the maximum recordable time (in seconds) of the camera. + * + * Service: 1D0F3602-8DFB-4340-9045-513040DAD991 + * Characteristic: 6EABAB73-7F2B-4061-BE7C-1D71D143CB7D + * + * @param value Maximum recordable time.[MaxRecordableTime] + * @exception ThetaBleApiException If an error occurs in library. + * @exception BluetoothException If an error occurs in bluetooth. + */ + @Throws(Throwable::class) + suspend fun setMaxRecordableTime(value: MaxRecordableTime) { + val peripheral = + thetaDevice.peripheral ?: throw ThetaBleApiException( + ERROR_MESSAGE_NOT_CONNECTED + ) + value.ble ?: throw ThetaBleApiException( + ERROR_MESSAGE_UNKNOWN_VALUE + ) + try { + val data = value.ble.toBytes() + peripheral.write(BleCharacteristic.MAX_RECORDABLE_TIME, data) + } catch (e: Throwable) { + throw BluetoothException(e) + } + } + /** * Instructs the camera to start shooting a still image. Also, acquires the shooting status. * diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CameraPower.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CameraPower.kt index d0c84cf..3bf523a 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CameraPower.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CameraPower.kt @@ -27,11 +27,11 @@ enum class CameraPower(internal val ble: Byte?) { /** * Search by bluetooth value. * - * @param ble Return value of bluetooth api. + * @param bleData Return value of bluetooth api. * @return CameraPower */ - internal fun getFromBle(ble: Byte): CameraPower? { - return CameraPower.values().firstOrNull { it.ble == ble } + internal fun getFromBle(bleData: Byte): CameraPower? { + return entries.firstOrNull { it.ble == bleData } } } } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CaptureMode.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CaptureMode.kt index d8cf013..ae6b4df 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CaptureMode.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CaptureMode.kt @@ -27,11 +27,11 @@ enum class CaptureMode(internal val ble: Byte?) { /** * Search by bluetooth value. * - * @param ble Return value of bluetooth api. + * @param bleData Return value of bluetooth api. * @return CaptureMode */ - internal fun getFromBle(ble: Byte): CaptureMode? { - return values().firstOrNull { it.ble == ble } + internal fun getFromBle(bleData: Byte): CaptureMode? { + return entries.firstOrNull { it.ble == bleData } } } } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/ChargingState.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/ChargingState.kt index d696439..146838c 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/ChargingState.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/ChargingState.kt @@ -42,11 +42,11 @@ enum class ChargingState(internal val ble: Byte?) : SerialNameEnum { /** * Search by bluetooth value. * - * @param ble Return value of bluetooth api. + * @param bleData Return value of bluetooth api. * @return ChargingState */ - internal fun getFromBle(ble: Byte): ChargingState? { - return ChargingState.values().firstOrNull { it.ble == ble } + internal fun getFromBle(bleData: Byte): ChargingState? { + return entries.firstOrNull { it.ble == bleData } } } } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CommandErrorDescription.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CommandErrorDescription.kt index 93f197c..e7c2ee2 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CommandErrorDescription.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/CommandErrorDescription.kt @@ -59,11 +59,11 @@ enum class CommandErrorDescription(internal val ble: Byte?) { /** * Search by bluetooth value. * - * @param ble Return value of bluetooth api. + * @param bleData Return value of bluetooth api. * @return CommandErrorDescription */ - internal fun getFromBle(ble: Byte): CommandErrorDescription? { - return CommandErrorDescription.values().firstOrNull { it.ble == ble } + internal fun getFromBle(bleData: Byte): CommandErrorDescription? { + return entries.firstOrNull { it.ble == bleData } } } } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/FileFormat.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/FileFormat.kt new file mode 100644 index 0000000..cbed9d8 --- /dev/null +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/FileFormat.kt @@ -0,0 +1,88 @@ +package com.ricoh360.thetableclient.service.data.values + +/** + * File format. + */ +enum class FileFormat(internal val ble: Byte?) { + /** + * Reserved value + */ + RESERVED(null), + + /** + * Still image. 5376x2688 + * + * For RICOH THETA V + */ + IMAGE_5K(0), + + /** + * Movie. 3840x1920. H.264/MPEG-4 AVC + * + * For RICOH THETA V, RICOH THETA Z1 or later + */ + VIDEO_4K(1), + + /** + * Movie. 1920x960. H.264/MPEG-4 AVC + * + * For RICOH THETA V, RICOH THETA Z1 or later + */ + VIDEO_2K(3), + + /** + * Still image JPEG format. 6720x3360 (Equirectangular) or 7296x3648 (Dual-Fisheye) + * + * For RICOH THETA Z1 or later + */ + IMAGE_6_7K(6), + + /** + * Still image RAW+ format. 7296x3648 + * + * For RICOH THETA Z1 or later + */ + RAW_P_6_7K(7), + + /** + * Movie. 2688x2688. H.264/MPEG-4 AVC + * + * RICOH THETA Z1 firmware v3.01.1 or later. This mode outputs two fisheye video for each lens. + * The MP4 file name ending with _0 is the video file on the front lens, + * and _1 is back lens. This mode does not record audio track to MP4 file. + */ + VIDEO_2_7K(9), + + /** + * Movie. 3648x3648. H.264/MPEG-4 AVC + * + * RICOH THETA Z1 firmware v3.01.1 or later. This mode outputs two fisheye video for each lens. + * The MP4 file name ending with _0 is the video file on the front lens, + * and _1 is back lens. This mode does not record audio track to MP4 file. + */ + VIDEO_3_6K(11), + ; + + companion object { + internal val MAX_RESERVED = 10 + + val keyName: String + get() = "fileFormat" + + /** + * Search by bluetooth value. + * + * @param bleData Return value of bluetooth api. + * @return FileFormat + */ + internal fun getFromBle(bleData: Byte): FileFormat? { + entries.firstOrNull { it.ble == bleData }?.let { + return it + } + return when { + bleData in 0..MAX_RESERVED -> RESERVED + else -> null + } + } + } +} diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/MaxRecordableTime.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/MaxRecordableTime.kt new file mode 100644 index 0000000..e13b9f8 --- /dev/null +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/MaxRecordableTime.kt @@ -0,0 +1,40 @@ +package com.ricoh360.thetableclient.service.data.values + +/** + * Maximum recordable time (in seconds) of the camera + */ +enum class MaxRecordableTime(internal val ble: Short?) { + /** + * Maximum recordable time. 300sec for other than SC2. + */ + RECORDABLE_TIME_300(300), + + /** + * Maximum recordable time. 1500sec for other than SC2. + */ + RECORDABLE_TIME_1500(1500), + + /** + * Maximum recordable time. 3000sec for THETA Z1 Version 3.01.1 or later + * only for 3.6K 1/2fps and 2.7K 1/2fps. + * If you set 3000 seconds in 3.6K 2fps mode and then set back to 4K 30fps mode, + * the max recordable time will be overwritten to 300 seconds automatically. + */ + RECORDABLE_TIME_3000(3000), + ; + + companion object { + val keyName: String + get() = "maxRecordableTime" + + /** + * Search by bluetooth value. + * + * @param bleData Return value of bluetooth api. + * @return MaxRecordableTime + */ + internal fun getFromBle(bleData: Short): MaxRecordableTime? { + return entries.firstOrNull { it.ble == bleData } + } + } +} diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/PluginPowerStatus.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/PluginPowerStatus.kt index fa8ffdf..750dc29 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/PluginPowerStatus.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetableclient/service/data/values/PluginPowerStatus.kt @@ -24,11 +24,11 @@ enum class PluginPowerStatus(internal val ble: Byte?) { /** * Search by bluetooth value. * - * @param ble Return value of bluetooth api. + * @param bleData Return value of bluetooth api. * @return PluginPowerStatus */ - internal fun getFromBle(ble: Byte): PluginPowerStatus? { - return values().firstOrNull { it.ble == ble } + internal fun getFromBle(bleData: Byte): PluginPowerStatus? { + return entries.firstOrNull { it.ble == bleData } } } } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/ThetaDeviceConnectTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/ThetaDeviceConnectTest.kt index b63c043..69956d3 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/ThetaDeviceConnectTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/ThetaDeviceConnectTest.kt @@ -6,7 +6,11 @@ import com.ricoh360.thetableclient.ble.newAdvertisement import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest -import kotlin.test.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue class ThetaDeviceConnectTest { @@ -102,11 +106,14 @@ class ThetaDeviceConnectTest { */ @Test fun connectTimeoutTest() = runBlocking { - val timeoutConnect = TIMEOUT_CONNECT + val timeoutConnect = 1000 + val timeout = ThetaBle.Timeout( + timeoutConnect = timeoutConnect + ) MockBlePeripheral.onConnect = { - delay(timeoutConnect.toLong() + 100) + delay(timeoutConnect.toLong() + 1000) } - val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + val device = ThetaBle.ThetaDevice(newAdvertisement(devName), timeout) try { device.connect() assertTrue(false, "exception connect timeout") diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/ThetaDeviceDisconnectTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/ThetaDeviceDisconnectTest.kt index 076786e..a287961 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/ThetaDeviceDisconnectTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/ThetaDeviceDisconnectTest.kt @@ -86,11 +86,14 @@ class ThetaDeviceDisconnectTest { */ @Test fun timeoutTest() = runBlocking { - val timeoutConnect = TIMEOUT_CONNECT - val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + val timeoutConnect = 1000 + val timeout = ThetaBle.Timeout( + timeoutConnect = timeoutConnect + ) + val device = ThetaBle.ThetaDevice(newAdvertisement(devName), timeout) MockBlePeripheral.onDisconnect = { - delay(timeoutConnect.toLong() + 100) + delay(timeoutConnect.toLong() + 1000) } try { diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/cameracontrolv2/SetStateNotifyTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/cameracontrolv2/SetStateNotifyTest.kt index 1a15d12..aeea2f2 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/cameracontrolv2/SetStateNotifyTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/cameracontrolv2/SetStateNotifyTest.kt @@ -1,19 +1,26 @@ package com.ricoh360.thetableclient.cameracontrolv2 import com.goncalossilva.resources.Resource -import com.ricoh360.thetableclient.* +import com.ricoh360.thetableclient.BleCharacteristic +import com.ricoh360.thetableclient.ThetaBle import com.ricoh360.thetableclient.ble.MockBlePeripheral import com.ricoh360.thetableclient.ble.newAdvertisement +import com.ricoh360.thetableclient.initMock import com.ricoh360.thetableclient.service.data.values.CameraError import com.ricoh360.thetableclient.service.data.values.CaptureStatus import com.ricoh360.thetableclient.service.data.values.ChargingState import com.ricoh360.thetableclient.service.data.values.ShootingFunction import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout -import kotlin.test.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue class SetStateNotifyTest { private val devName = "99999999" @@ -33,14 +40,20 @@ class SetStateNotifyTest { @Test fun normalTest() = runBlocking { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic == BleCharacteristic.NOTIFY_STATE) { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } + val service = device.cameraControlCommandV2 assertNotNull(service) @@ -80,14 +93,20 @@ class SetStateNotifyTest { @Test fun setEmptyTest() = runBlocking { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic == BleCharacteristic.NOTIFY_STATE) { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } + val service = device.cameraControlCommandV2 assertNotNull(service) @@ -183,14 +202,20 @@ class SetStateNotifyTest { @Test fun emptyDataTest() = runBlocking { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic == BleCharacteristic.NOTIFY_STATE) { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } + val service = device.cameraControlCommandV2 assertNotNull(service) @@ -215,14 +240,20 @@ class SetStateNotifyTest { @Test fun notJsonTest() = runBlocking { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic == BleCharacteristic.NOTIFY_STATE) { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } + val service = device.cameraControlCommandV2 assertNotNull(service) diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetBatteryLevelNotifyTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetBatteryLevelNotifyTest.kt index a419561..e8a88e2 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetBatteryLevelNotifyTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetBatteryLevelNotifyTest.kt @@ -1,14 +1,21 @@ package com.ricoh360.thetableclient.camerastatus -import com.ricoh360.thetableclient.* +import com.ricoh360.thetableclient.BleCharacteristic +import com.ricoh360.thetableclient.ThetaBle import com.ricoh360.thetableclient.ble.MockBlePeripheral import com.ricoh360.thetableclient.ble.newAdvertisement +import com.ricoh360.thetableclient.initMock +import com.ricoh360.thetableclient.toBytes import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout -import kotlin.test.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue class SetBatteryLevelNotifyTest { private val devName = "99999999" @@ -32,6 +39,7 @@ class SetBatteryLevelNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() val testValue = 99 @@ -39,11 +47,15 @@ class SetBatteryLevelNotifyTest { MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "BATTERY_LEVEL") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setBatteryLevelNotify { value, error -> assertEquals(value, testValue) @@ -67,6 +79,7 @@ class SetBatteryLevelNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) var deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() val testValue = 99 @@ -74,11 +87,15 @@ class SetBatteryLevelNotifyTest { MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "BATTERY_LEVEL") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setBatteryLevelNotify { value, error -> assertEquals(value, testValue) @@ -166,16 +183,21 @@ class SetBatteryLevelNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "BATTERY_LEVEL") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setBatteryLevelNotify { value, error -> assertTrue(error?.message!!.indexOf("Empty data", 0, true) >= 0, "exception empty") diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetBatteryStatusNotifyTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetBatteryStatusNotifyTest.kt index 7aac2fa..8059f80 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetBatteryStatusNotifyTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetBatteryStatusNotifyTest.kt @@ -1,15 +1,23 @@ package com.ricoh360.thetableclient.camerastatus -import com.ricoh360.thetableclient.* +import com.ricoh360.thetableclient.BleCharacteristic +import com.ricoh360.thetableclient.ThetaBle import com.ricoh360.thetableclient.ble.MockBlePeripheral import com.ricoh360.thetableclient.ble.newAdvertisement +import com.ricoh360.thetableclient.initMock import com.ricoh360.thetableclient.service.data.values.ChargingState +import com.ricoh360.thetableclient.toBytes import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout -import kotlin.test.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue class SetBatteryStatusNotifyTest { private val devName = "99999999" @@ -33,6 +41,7 @@ class SetBatteryStatusNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() val testValue = ChargingState.CHARGED assertNotNull(testValue.ble) @@ -41,18 +50,22 @@ class SetBatteryStatusNotifyTest { MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "BATTERY_STATUS") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setBatteryStatusNotify { value, error -> assertEquals(value, testValue) assertNull(error) deferred.complete(Unit) } - val data = testValue.ble?.toBytes() ?: 255.toByte().toBytes() + val data = testValue.ble.toBytes() observer(data) withTimeout(100) { deferred.await() @@ -69,6 +82,7 @@ class SetBatteryStatusNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) var deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() val testValue = ChargingState.CHARGING @@ -76,11 +90,15 @@ class SetBatteryStatusNotifyTest { MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "BATTERY_STATUS") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setBatteryStatusNotify { value, error -> assertEquals(value, testValue) @@ -168,16 +186,21 @@ class SetBatteryStatusNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "BATTERY_STATUS") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setBatteryStatusNotify { value, error -> assertTrue(error?.message!!.indexOf("Empty data", 0, true) >= 0, "exception empty") @@ -200,16 +223,21 @@ class SetBatteryStatusNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "BATTERY_STATUS") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setBatteryStatusNotify { value, error -> assertTrue(error?.message!!.indexOf("Unknown value", 0, true) >= 0, "exception empty") diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetCameraPowerNotifyTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetCameraPowerNotifyTest.kt index 90064ca..c372acd 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetCameraPowerNotifyTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetCameraPowerNotifyTest.kt @@ -1,15 +1,23 @@ package com.ricoh360.thetableclient.camerastatus -import com.ricoh360.thetableclient.* +import com.ricoh360.thetableclient.BleCharacteristic +import com.ricoh360.thetableclient.ThetaBle import com.ricoh360.thetableclient.ble.MockBlePeripheral import com.ricoh360.thetableclient.ble.newAdvertisement +import com.ricoh360.thetableclient.initMock import com.ricoh360.thetableclient.service.data.values.CameraPower +import com.ricoh360.thetableclient.toBytes import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout -import kotlin.test.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue class SetCameraPowerNotifyTest { private val devName = "99999999" @@ -33,6 +41,7 @@ class SetCameraPowerNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() val testValue = CameraPower.ON assertNotNull(testValue.ble) @@ -41,11 +50,15 @@ class SetCameraPowerNotifyTest { MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "CAMERA_POWER") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setCameraPowerNotify { value, error -> assertEquals(value, testValue) @@ -69,6 +82,7 @@ class SetCameraPowerNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) var deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() val testValue = CameraPower.OFF assertNotNull(testValue.ble) @@ -77,11 +91,15 @@ class SetCameraPowerNotifyTest { MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "CAMERA_POWER") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setCameraPowerNotify { value, error -> assertEquals(value, testValue) @@ -169,16 +187,21 @@ class SetCameraPowerNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "CAMERA_POWER") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setCameraPowerNotify { value, error -> assertTrue(error?.message!!.indexOf("Empty data", 0, true) >= 0, "exception empty") @@ -201,16 +224,21 @@ class SetCameraPowerNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "CAMERA_POWER") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setCameraPowerNotify { value, error -> assertTrue(error?.message!!.indexOf("Unknown value", 0, true) >= 0, "exception empty") diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetCommandErrorDescriptionNotifyTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetCommandErrorDescriptionNotifyTest.kt index 4b83fdc..06a6772 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetCommandErrorDescriptionNotifyTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetCommandErrorDescriptionNotifyTest.kt @@ -1,15 +1,22 @@ package com.ricoh360.thetableclient.camerastatus -import com.ricoh360.thetableclient.* +import com.ricoh360.thetableclient.BleCharacteristic +import com.ricoh360.thetableclient.ThetaBle import com.ricoh360.thetableclient.ble.MockBlePeripheral import com.ricoh360.thetableclient.ble.newAdvertisement +import com.ricoh360.thetableclient.initMock import com.ricoh360.thetableclient.service.data.values.CommandErrorDescription +import com.ricoh360.thetableclient.toBytes import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout -import kotlin.test.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue class SetCommandErrorDescriptionNotifyTest { private val devName = "99999999" @@ -33,6 +40,7 @@ class SetCommandErrorDescriptionNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() val testValue = CommandErrorDescription.INVALID_PARAMETER_VALUE @@ -40,11 +48,15 @@ class SetCommandErrorDescriptionNotifyTest { MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "COMMAND_ERROR_DESCRIPTION") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setCommandErrorDescriptionNotify { value, error -> assertEquals(value, testValue) @@ -68,6 +80,7 @@ class SetCommandErrorDescriptionNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) var deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() val testValue = CommandErrorDescription.INVALID_FILE_FORMAT @@ -75,11 +88,15 @@ class SetCommandErrorDescriptionNotifyTest { MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "COMMAND_ERROR_DESCRIPTION") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setCommandErrorDescriptionNotify { value, error -> assertEquals(value, testValue) @@ -167,16 +184,21 @@ class SetCommandErrorDescriptionNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "COMMAND_ERROR_DESCRIPTION") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setCommandErrorDescriptionNotify { value, error -> assertTrue(error?.message!!.indexOf("Empty data", 0, true) >= 0, "exception empty") @@ -199,16 +221,21 @@ class SetCommandErrorDescriptionNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "COMMAND_ERROR_DESCRIPTION") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setCommandErrorDescriptionNotify { value, error -> assertTrue(error?.message!!.indexOf("Unknown value", 0, true) >= 0, "exception empty") diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetPluginControlNotifyTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetPluginControlNotifyTest.kt index ca3481b..36d80f2 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetPluginControlNotifyTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/camerastatus/SetPluginControlNotifyTest.kt @@ -1,16 +1,23 @@ package com.ricoh360.thetableclient.camerastatus -import com.ricoh360.thetableclient.* +import com.ricoh360.thetableclient.BleCharacteristic +import com.ricoh360.thetableclient.ThetaBle import com.ricoh360.thetableclient.ble.MockBlePeripheral import com.ricoh360.thetableclient.ble.newAdvertisement +import com.ricoh360.thetableclient.initMock import com.ricoh360.thetableclient.service.data.ble.PluginControl import com.ricoh360.thetableclient.service.data.values.PluginPowerStatus +import com.ricoh360.thetableclient.toBytes import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout -import kotlin.test.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue class SetPluginControlNotifyTest { private val devName = "99999999" @@ -34,6 +41,7 @@ class SetPluginControlNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() val testValue = PluginControl(PluginPowerStatus.RUNNING, 1) @@ -41,11 +49,15 @@ class SetPluginControlNotifyTest { MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "PLUGIN_CONTROL") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setPluginControlNotify { value, error -> assertEquals(value?.pluginControl, testValue.pluginControl) @@ -70,6 +82,7 @@ class SetPluginControlNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) var deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() val testValue = PluginControl(PluginPowerStatus.STOP, 2) @@ -77,11 +90,15 @@ class SetPluginControlNotifyTest { MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "PLUGIN_CONTROL") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setPluginControlNotify { value, error -> assertEquals(value?.pluginControl, testValue.pluginControl) @@ -170,16 +187,21 @@ class SetPluginControlNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "PLUGIN_CONTROL") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setPluginControlNotify { value, error -> assertTrue(error?.message!!.indexOf("Empty data", 0, true) >= 0, "exception empty") @@ -202,16 +224,21 @@ class SetPluginControlNotifyTest { val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) val deferred = CompletableDeferred() + val deferredObserve = CompletableDeferred() lateinit var observer: (ByteArray) -> Unit MockBlePeripheral.onObserve = { characteristic, collect: (ByteArray) -> Unit -> if (characteristic.name == "PLUGIN_CONTROL") { observer = collect + deferredObserve.complete(Unit) } } device.connect() - delay(100) + + withTimeout(1000) { + deferredObserve.await() + } device.cameraStatusCommand?.setPluginControlNotify { value, error -> assertTrue(error?.message!!.indexOf("Unknown value", 0, true) >= 0, "exception empty") diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/GetFileFormatTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/GetFileFormatTest.kt new file mode 100644 index 0000000..38e64c4 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/GetFileFormatTest.kt @@ -0,0 +1,159 @@ +package com.ricoh360.thetableclient.shootingcontrol + +import com.ricoh360.thetableclient.ThetaBle +import com.ricoh360.thetableclient.ble.MockBlePeripheral +import com.ricoh360.thetableclient.ble.newAdvertisement +import com.ricoh360.thetableclient.initMock +import com.ricoh360.thetableclient.service.data.values.FileFormat +import com.ricoh360.thetableclient.toBytes +import kotlinx.coroutines.runBlocking +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class GetFileFormatTest { + private val devName = "99999999" + + @BeforeTest + fun setup() { + println("setup") + initMock() + } + + @AfterTest + fun teardown() { + } + + /** + * call getFileFormat. + */ + @Test + fun normalTest() = runBlocking { + val enumList = listOf( + Pair(0, FileFormat.IMAGE_5K), + Pair(1, FileFormat.VIDEO_4K), + Pair(2, FileFormat.RESERVED), + Pair(3, FileFormat.VIDEO_2K), + Pair(4, FileFormat.RESERVED), + Pair(5, FileFormat.RESERVED), + Pair(6, FileFormat.IMAGE_6_7K), + Pair(7, FileFormat.RAW_P_6_7K), + Pair(8, FileFormat.RESERVED), + Pair(9, FileFormat.VIDEO_2_7K), + Pair(10, FileFormat.RESERVED), + Pair(11, FileFormat.VIDEO_3_6K), + ) + enumList.forEach { + getFileFormatTest(it.first.toByte(), it.second) + } + } + + private fun getFileFormatTest(bleData: Byte, fileFormat: FileFormat) = runBlocking { + initMock() + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + device.connect() + + println("getFileFormatTest: $fileFormat") + MockBlePeripheral.onRead = { + assertEquals(it.name, "FILE_FORMAT") + bleData.toBytes() + } + val readValue = device.shootingControlCommand?.getFileFormat() + assertEquals(readValue, fileFormat, "getFileFormat") + } + + /** + * Not connected exception for getFileFormat call. + */ + @Test + fun exceptionApiTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + try { + device.connect() + val shootingControlCommand = device.shootingControlCommand + device.disconnect() + shootingControlCommand?.getFileFormat() + assertTrue(false, "exception Not connected") + } catch (e: ThetaBle.ThetaBleApiException) { + assertTrue( + e.message!!.indexOf("Not connected", 0, true) >= 0, + "exception Not connected" + ) + } catch (e: Throwable) { + assertTrue(false, "exception Not connected. ${e.message}") + } + } + + /** + * Read exception for getFileFormat call. + */ + @Test + fun exceptionBleTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + MockBlePeripheral.onRead = { + throw Exception("read") + } + + try { + device.connect() + device.shootingControlCommand?.getFileFormat() + assertTrue(false, "exception read") + } catch (e: ThetaBle.BluetoothException) { + assertTrue(e.message!!.indexOf("read", 0, true) >= 0, "exception read") + } catch (e: Throwable) { + assertTrue(false, "exception read. ${e.message}") + } + } + + /** + * Read empty for getFileFormat call. + */ + @Test + fun emptyBleTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + MockBlePeripheral.onRead = { + ByteArray(0) + } + + try { + device.connect() + device.shootingControlCommand?.getFileFormat() + assertTrue(false, "exception empty") + } catch (e: ThetaBle.ThetaBleApiException) { + assertTrue(e.message!!.indexOf("Empty data", 0, true) >= 0, "exception empty") + } catch (e: ThetaBle.BluetoothException) { + assertTrue(false, "exception empty") + } catch (e: Throwable) { + assertTrue(false, "exception empty. ${e.message}") + } + } + + /** + * Read unknown for getFileFormat call. + */ + @Test + fun unknownBleTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + MockBlePeripheral.onRead = { + 20.toByte().toBytes() + } + + try { + device.connect() + device.shootingControlCommand?.getFileFormat() + assertTrue(false, "exception unknown") + } catch (e: ThetaBle.ThetaBleApiException) { + assertTrue(e.message!!.indexOf("Unknown value", 0, true) >= 0, "exception unknown") + } catch (e: ThetaBle.BluetoothException) { + assertTrue(false, "exception unknown") + } catch (e: Throwable) { + assertTrue(false, "exception unknown. ${e.message}") + } + } +} diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/GetMaxRecordableTimeTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/GetMaxRecordableTimeTest.kt new file mode 100644 index 0000000..8add815 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/GetMaxRecordableTimeTest.kt @@ -0,0 +1,152 @@ +package com.ricoh360.thetableclient.shootingcontrol + +import com.ricoh360.thetableclient.ThetaBle +import com.ricoh360.thetableclient.ble.MockBlePeripheral +import com.ricoh360.thetableclient.ble.newAdvertisement +import com.ricoh360.thetableclient.initMock +import com.ricoh360.thetableclient.service.data.values.MaxRecordableTime +import com.ricoh360.thetableclient.toBytes +import kotlinx.coroutines.runBlocking +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class GetMaxRecordableTimeTest { + private val devName = "99999999" + + @BeforeTest + fun setup() { + println("setup") + initMock() + } + + @AfterTest + fun teardown() { + } + + /** + * call getMaxRecordableTime. + */ + @Test + fun normalTest() = runBlocking { + val enumList = listOf( + MaxRecordableTime.RECORDABLE_TIME_300, + MaxRecordableTime.RECORDABLE_TIME_1500, + MaxRecordableTime.RECORDABLE_TIME_3000, + ) + enumList.forEach { + getMaxRecordableTimeTest(it) + } + } + + private fun getMaxRecordableTimeTest(value: MaxRecordableTime) = runBlocking { + initMock() + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + device.connect() + + println("getMaxRecordableTimeTest: $value") + MockBlePeripheral.onRead = { + assertEquals(it.name, "MAX_RECORDABLE_TIME") + assertNotNull(value.ble) + value.ble.toBytes() + } + val readValue = device.shootingControlCommand?.getMaxRecordableTime() + assertEquals(readValue, value, "getMaxRecordableTime") + } + + /** + * Not connected exception for getMaxRecordableTime call. + */ + @Test + fun exceptionApiTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + try { + device.connect() + val shootingControlCommand = device.shootingControlCommand + device.disconnect() + shootingControlCommand?.getMaxRecordableTime() + assertTrue(false, "exception Not connected") + } catch (e: ThetaBle.ThetaBleApiException) { + assertTrue( + e.message!!.indexOf("Not connected", 0, true) >= 0, + "exception Not connected" + ) + } catch (e: Throwable) { + assertTrue(false, "exception Not connected. ${e.message}") + } + } + + /** + * Read exception for getMaxRecordableTime call. + */ + @Test + fun exceptionBleTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + MockBlePeripheral.onRead = { + throw Exception("read") + } + + try { + device.connect() + device.shootingControlCommand?.getMaxRecordableTime() + assertTrue(false, "exception read") + } catch (e: ThetaBle.BluetoothException) { + assertTrue(e.message!!.indexOf("read", 0, true) >= 0, "exception read") + } catch (e: Throwable) { + assertTrue(false, "exception read. ${e.message}") + } + } + + /** + * Read empty for getMaxRecordableTime call. + */ + @Test + fun emptyBleTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + MockBlePeripheral.onRead = { + ByteArray(0) + } + + try { + device.connect() + device.shootingControlCommand?.getMaxRecordableTime() + assertTrue(false, "exception empty") + } catch (e: ThetaBle.ThetaBleApiException) { + assertTrue(e.message!!.indexOf("Empty data", 0, true) >= 0, "exception empty") + } catch (e: ThetaBle.BluetoothException) { + assertTrue(false, "exception empty") + } catch (e: Throwable) { + assertTrue(false, "exception empty. ${e.message}") + } + } + + /** + * Read unknown for getMaxRecordableTime call. + */ + @Test + fun unknownBleTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + MockBlePeripheral.onRead = { + 255.toByte().toBytes() + } + + try { + device.connect() + device.shootingControlCommand?.getMaxRecordableTime() + assertTrue(false, "exception unknown") + } catch (e: ThetaBle.ThetaBleApiException) { + assertTrue(e.message!!.indexOf("Unknown value", 0, true) >= 0, "exception unknown") + } catch (e: ThetaBle.BluetoothException) { + assertTrue(false, "exception unknown") + } catch (e: Throwable) { + assertTrue(false, "exception unknown. ${e.message}") + } + } +} diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/SetFileFormatTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/SetFileFormatTest.kt new file mode 100644 index 0000000..66e8ba2 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/SetFileFormatTest.kt @@ -0,0 +1,141 @@ +package com.ricoh360.thetableclient.shootingcontrol + +import com.ricoh360.thetableclient.ThetaBle +import com.ricoh360.thetableclient.ble.MockBlePeripheral +import com.ricoh360.thetableclient.ble.newAdvertisement +import com.ricoh360.thetableclient.initMock +import com.ricoh360.thetableclient.service.data.values.FileFormat +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SetFileFormatTest { + private val devName = "99999999" + + @BeforeTest + fun setup() { + println("setup") + initMock() + } + + @AfterTest + fun teardown() { + } + + /** + * call setFileFormat. + */ + @Test + fun normalTest() = runBlocking { + val enumList = listOf( + Pair(0, FileFormat.IMAGE_5K), + Pair(1, FileFormat.VIDEO_4K), + Pair(3, FileFormat.VIDEO_2K), + Pair(6, FileFormat.IMAGE_6_7K), + Pair(7, FileFormat.RAW_P_6_7K), + Pair(9, FileFormat.VIDEO_2_7K), + Pair(11, FileFormat.VIDEO_3_6K), + ) + enumList.forEach { + setFileFormatTest(it.first.toByte(), it.second) + } + } + + private fun setFileFormatTest(bleData: Byte, fileFormat: FileFormat) = runBlocking { + initMock() + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + device.connect() + println("setFileFormatTest: $fileFormat") + + val deferred = CompletableDeferred() + + MockBlePeripheral.onWrite = { characteristic, data -> + if (characteristic.name == "FILE_FORMAT") { + assertEquals(data[0], bleData, "setFileFormat") + deferred.complete(Unit) + } + } + device.shootingControlCommand?.setFileFormat(fileFormat) + withTimeout(100) { + deferred.await() + } + assertTrue(true, "setFileFormat") + } + + /** + * Not connected exception for setFileFormat call. + */ + @Test + fun exceptionApiTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + try { + device.connect() + val shootingControlCommand = device.shootingControlCommand + device.disconnect() + shootingControlCommand?.setFileFormat(FileFormat.IMAGE_5K) + assertTrue(false, "exception Not connected") + } catch (e: ThetaBle.ThetaBleApiException) { + assertTrue( + e.message!!.indexOf("Not connected", 0, true) >= 0, + "exception Not connected" + ) + } catch (e: Throwable) { + assertTrue(false, "exception Not connected. ${e.message}") + } + } + + /** + * Read exception for setFileFormat call. + */ + @Test + fun exceptionBleTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + val fileFormat = FileFormat.IMAGE_5K + MockBlePeripheral.onWrite = { characteristic, data -> + if (characteristic.name == "FILE_FORMAT") { + assertEquals(data[0], fileFormat.ble, "setFileFormat") + throw Exception("write") + } + } + + try { + device.connect() + device.shootingControlCommand?.setFileFormat(fileFormat) + assertTrue(false, "exception write") + } catch (e: ThetaBle.BluetoothException) { + assertTrue(e.message!!.indexOf("write", 0, true) >= 0, "exception write") + } catch (e: Throwable) { + assertTrue(false, "exception write. ${e.message}") + } + } + + /** + * Exception set reserved for setFileFormat call. + */ + @Test + fun setReservedTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + val fileFormat = FileFormat.RESERVED + MockBlePeripheral.onWrite = { _, _ -> + assertTrue(false, "onWrite") + } + + try { + device.connect() + device.shootingControlCommand?.setFileFormat(fileFormat) + assertTrue(false, "set reserved") + } catch (e: ThetaBle.ThetaBleApiException) { + assertTrue(e.message!!.indexOf("Reserved value", 0, true) >= 0, "set reserved") + } catch (e: Throwable) { + assertTrue(false, "exception write. ${e.message}") + } + } +} diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/SetMaxRecordableTimeTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/SetMaxRecordableTimeTest.kt new file mode 100644 index 0000000..5644892 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/SetMaxRecordableTimeTest.kt @@ -0,0 +1,115 @@ +package com.ricoh360.thetableclient.shootingcontrol + +import com.ricoh360.thetableclient.ThetaBle +import com.ricoh360.thetableclient.ble.MockBlePeripheral +import com.ricoh360.thetableclient.ble.newAdvertisement +import com.ricoh360.thetableclient.initMock +import com.ricoh360.thetableclient.service.data.values.MaxRecordableTime +import com.ricoh360.thetableclient.toShort +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SetMaxRecordableTimeTest { + private val devName = "99999999" + + @BeforeTest + fun setup() { + println("setup") + initMock() + } + + @AfterTest + fun teardown() { + } + + /** + * call setMaxRecordableTime. + */ + @Test + fun normalTest() = runBlocking { + val enumList = listOf( + MaxRecordableTime.RECORDABLE_TIME_300, + MaxRecordableTime.RECORDABLE_TIME_1500, + MaxRecordableTime.RECORDABLE_TIME_3000, + ) + enumList.forEach { + setMaxRecordableTimeTest(it) + } + } + + private fun setMaxRecordableTimeTest(value: MaxRecordableTime) = runBlocking { + initMock() + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + device.connect() + println("setMaxRecordableTimeTest: $value") + + val deferred = CompletableDeferred() + + MockBlePeripheral.onWrite = { characteristic, data -> + if (characteristic.name == "MAX_RECORDABLE_TIME") { + assertEquals(data.toShort(), value.ble, "setMaxRecordableTime") + deferred.complete(Unit) + } + } + device.shootingControlCommand?.setMaxRecordableTime(value) + withTimeout(100) { + deferred.await() + } + assertTrue(true, "setMaxRecordableTime") + } + + /** + * Not connected exception for setMaxRecordableTime call. + */ + @Test + fun exceptionApiTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + try { + device.connect() + val shootingControlCommand = device.shootingControlCommand + device.disconnect() + shootingControlCommand?.setMaxRecordableTime(MaxRecordableTime.RECORDABLE_TIME_300) + assertTrue(false, "exception Not connected") + } catch (e: ThetaBle.ThetaBleApiException) { + assertTrue( + e.message!!.indexOf("Not connected", 0, true) >= 0, + "exception Not connected" + ) + } catch (e: Throwable) { + assertTrue(false, "exception Not connected. ${e.message}") + } + } + + /** + * Read exception for setMaxRecordableTime call. + */ + @Test + fun exceptionBleTest() = runBlocking { + val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + + val value = MaxRecordableTime.RECORDABLE_TIME_300 + MockBlePeripheral.onWrite = { characteristic, data -> + if (characteristic.name == "MAX_RECORDABLE_TIME") { + assertEquals(data.toShort(), value.ble, "setMaxRecordableTime") + throw Exception("write") + } + } + + try { + device.connect() + device.shootingControlCommand?.setMaxRecordableTime(value) + assertTrue(false, "exception write") + } catch (e: ThetaBle.BluetoothException) { + assertTrue(e.message!!.indexOf("write", 0, true) >= 0, "exception write") + } catch (e: Throwable) { + assertTrue(false, "exception write. ${e.message}") + } + } +} diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/TakePictureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/TakePictureTest.kt index ac9526c..c77767b 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/TakePictureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetableclient/shootingcontrol/TakePictureTest.kt @@ -165,7 +165,10 @@ class TakePictureTest { } } - val device = ThetaBle.ThetaDevice(newAdvertisement(devName)) + val timeout = ThetaBle.Timeout( + timeoutTakePicture = 1000 + ) + val device = ThetaBle.ThetaDevice(newAdvertisement(devName), timeout) device.connect() device.shootingControlCommand?.takePicture { @@ -177,7 +180,7 @@ class TakePictureTest { deferredWrite.await() } - withTimeout(device.timeout.timeoutTakePicture.toLong() + 100) { + withTimeout(timeout.timeoutTakePicture.toLong() + 2000) { deferredComplete.await() } assertTrue(true, "takePicture") diff --git a/react-native/android/build.gradle b/react-native/android/build.gradle index 4a7b96c..39f0657 100644 --- a/react-native/android/build.gradle +++ b/react-native/android/build.gradle @@ -74,7 +74,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" - implementation "com.ricoh360.thetableclient:theta-ble-client-android:1.1.0" + implementation "com.ricoh360.thetableclient:theta-ble-client-android:1.2.0" } if (isNewArchitectureEnabled()) { diff --git a/react-native/android/src/main/java/com/ricoh360/thetableclientreactnative/ShootingControlCommandService.kt b/react-native/android/src/main/java/com/ricoh360/thetableclientreactnative/ShootingControlCommandService.kt index 1efd642..1c7649e 100644 --- a/react-native/android/src/main/java/com/ricoh360/thetableclientreactnative/ShootingControlCommandService.kt +++ b/react-native/android/src/main/java/com/ricoh360/thetableclientreactnative/ShootingControlCommandService.kt @@ -2,6 +2,8 @@ package com.ricoh360.thetableclientreactnative import com.facebook.react.bridge.Promise import com.ricoh360.thetableclient.service.data.values.CaptureMode +import com.ricoh360.thetableclient.service.data.values.FileFormat +import com.ricoh360.thetableclient.service.data.values.MaxRecordableTime object ShootingControlCommandService { suspend fun getCaptureMode(id: Int, promise: Promise) { @@ -35,7 +37,7 @@ object ShootingControlCommandService { promise.reject(Exception(ERROR_MESSAGE_UNSUPPORTED_SERVICE)) return } - val enumValue = CaptureMode.values().find { + val enumValue = CaptureMode.entries.find { it.name == value } if (enumValue == null) { @@ -49,6 +51,96 @@ object ShootingControlCommandService { } } + suspend fun getFileFormat(id: Int, promise: Promise) { + try { + val device = ThetaBleClientReactNativeModule.deviceList[id] + device ?: let { + promise.reject(Exception(ERROR_MESSAGE_DEVICE_NOT_FOUND)) + return + } + val service = device.shootingControlCommand + service ?: let { + promise.reject(Exception(ERROR_MESSAGE_UNSUPPORTED_SERVICE)) + return + } + val value = service.getFileFormat() + promise.resolve(value.name) + } catch (e: Throwable) { + promise.reject(e) + } + } + + suspend fun setFileFormat(id: Int, value: String, promise: Promise) { + try { + val device = ThetaBleClientReactNativeModule.deviceList[id] + device ?: let { + promise.reject(Exception(ERROR_MESSAGE_DEVICE_NOT_FOUND)) + return + } + val service = device.shootingControlCommand + service ?: let { + promise.reject(Exception(ERROR_MESSAGE_UNSUPPORTED_SERVICE)) + return + } + val enumValue = FileFormat.entries.find { + it.name == value + } + if (enumValue == null) { + promise.reject(Exception("File format not found. $value")) + } else { + service.setFileFormat(enumValue) + promise.resolve(null) + } + } catch (e: Throwable) { + promise.reject(e) + } + } + + suspend fun getMaxRecordableTime(id: Int, promise: Promise) { + try { + val device = ThetaBleClientReactNativeModule.deviceList[id] + device ?: let { + promise.reject(Exception(ERROR_MESSAGE_DEVICE_NOT_FOUND)) + return + } + val service = device.shootingControlCommand + service ?: let { + promise.reject(Exception(ERROR_MESSAGE_UNSUPPORTED_SERVICE)) + return + } + val value = service.getMaxRecordableTime() + promise.resolve(value.name) + } catch (e: Throwable) { + promise.reject(e) + } + } + + suspend fun setMaxRecordableTime(id: Int, value: String, promise: Promise) { + try { + val device = ThetaBleClientReactNativeModule.deviceList[id] + device ?: let { + promise.reject(Exception(ERROR_MESSAGE_DEVICE_NOT_FOUND)) + return + } + val service = device.shootingControlCommand + service ?: let { + promise.reject(Exception(ERROR_MESSAGE_UNSUPPORTED_SERVICE)) + return + } + val enumValue = MaxRecordableTime.entries.find { + it.name == value + } + if (enumValue == null) { + promise.reject(Exception("Max recordable time not found. $value")) + } else { + service.setMaxRecordableTime(enumValue) + promise.resolve(null) + } + } catch (e: Throwable) { + promise.reject(e) + } + } + fun takePicture(id: Int, promise: Promise) { try { val device = ThetaBleClientReactNativeModule.deviceList[id] diff --git a/react-native/android/src/main/java/com/ricoh360/thetableclientreactnative/ThetaBleClientReactNativeModule.kt b/react-native/android/src/main/java/com/ricoh360/thetableclientreactnative/ThetaBleClientReactNativeModule.kt index f0e0f05..7492189 100644 --- a/react-native/android/src/main/java/com/ricoh360/thetableclientreactnative/ThetaBleClientReactNativeModule.kt +++ b/react-native/android/src/main/java/com/ricoh360/thetableclientreactnative/ThetaBleClientReactNativeModule.kt @@ -4,7 +4,6 @@ import com.facebook.react.bridge.* import com.facebook.react.modules.core.DeviceEventManagerModule import com.ricoh360.thetableclient.BleService import com.ricoh360.thetableclient.ThetaBle -import com.ricoh360.thetableclient.service.data.values.ThetaModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -320,6 +319,34 @@ class ThetaBleClientReactNativeModule(reactContext: ReactApplicationContext) : } } + @ReactMethod + fun nativeGetFileFormat(id: Int, promise: Promise) { + launch { + ShootingControlCommandService.getFileFormat(id, promise) + } + } + + @ReactMethod + fun nativeSetFileFormat(id: Int, value: String, promise: Promise) { + launch { + ShootingControlCommandService.setFileFormat(id, value, promise) + } + } + + @ReactMethod + fun nativeGetMaxRecordableTime(id: Int, promise: Promise) { + launch { + ShootingControlCommandService.getMaxRecordableTime(id, promise) + } + } + + @ReactMethod + fun nativeSetMaxRecordableTime(id: Int, value: String, promise: Promise) { + launch { + ShootingControlCommandService.setMaxRecordableTime(id, value, promise) + } + } + @ReactMethod fun nativeTakePicture(id: Int, promise: Promise) { ShootingControlCommandService.takePicture(id, promise) diff --git a/react-native/ios/CameraControlCommandsService.swift b/react-native/ios/CameraControlCommandsService.swift index 2ebd112..c4b6f1f 100644 --- a/react-native/ios/CameraControlCommandsService.swift +++ b/react-native/ios/CameraControlCommandsService.swift @@ -72,7 +72,7 @@ class CameraControlCommandsService { do { let objects = value as? Dictionary ?? [: ] let thetaValue = toTheta(pluginOrders: objects) - guard let thetaValue = thetaValue else { + guard let thetaValue else { reject(ERROR_TITLE, "Plugin orders not found. \(value)", nil) return } diff --git a/react-native/ios/CameraStatusCommandService.swift b/react-native/ios/CameraStatusCommandService.swift index 7fb4cb7..230c7ac 100644 --- a/react-native/ios/CameraStatusCommandService.swift +++ b/react-native/ios/CameraStatusCommandService.swift @@ -164,7 +164,7 @@ class CameraStatusCommandService { Task { do { let enumValue = getEnumValue(values: CameraPower.values(), name: value) - guard let enumValue = enumValue else { + guard let enumValue else { reject(ERROR_TITLE, "Camera power not found. \(value)", nil) return } @@ -287,7 +287,7 @@ class CameraStatusCommandService { do { let objects = value as? Dictionary ?? [: ] let thetaValue = toTheta(pluginControl: objects) - guard let thetaValue = thetaValue else { + guard let thetaValue else { reject(ERROR_TITLE, "Plugin control not found. \(value)", nil) return } diff --git a/react-native/ios/ShootingControlCommandService.swift b/react-native/ios/ShootingControlCommandService.swift index 8c33a3b..390b3ef 100644 --- a/react-native/ios/ShootingControlCommandService.swift +++ b/react-native/ios/ShootingControlCommandService.swift @@ -48,7 +48,7 @@ class ShootingControlCommandService { Task { do { let enumValue = getEnumValue(values: CaptureMode.values(), name: value) - guard let enumValue = enumValue else { + guard let enumValue else { reject(ERROR_TITLE, "Capture mode not found. \(value)", nil) return } @@ -60,6 +60,110 @@ class ShootingControlCommandService { } } + static func getFileFormat(id: Int, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) -> Void + { + guard let device = ThetaBleClientReactNative.deviceList[id] else { + reject(ERROR_TITLE, ERROR_MESSAGE_DEVICE_NOT_FOUND, nil) + return + } + guard let service = device.shootingControlCommand else { + reject(ERROR_TITLE, ERROR_MESSAGE_UNSUPPORTED_SERVICE, nil) + return + } + + Task { + do { + let value = try await service.getFileFormat() + resolve(value.name) + } catch { + reject(ERROR_TITLE, error.localizedDescription, error) + } + } + } + + static func setFileFormat(id: Int, + value: String, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) -> Void + { + guard let device = ThetaBleClientReactNative.deviceList[id] else { + reject(ERROR_TITLE, ERROR_MESSAGE_DEVICE_NOT_FOUND, nil) + return + } + guard let service = device.shootingControlCommand else { + reject(ERROR_TITLE, ERROR_MESSAGE_UNSUPPORTED_SERVICE, nil) + return + } + + Task { + do { + let enumValue = getEnumValue(values: FileFormat.values(), name: value) + guard let enumValue else { + reject(ERROR_TITLE, "File format not found. \(value)", nil) + return + } + try await service.setFileFormat(value: enumValue) + resolve(nil) + } catch { + reject(ERROR_TITLE, error.localizedDescription, error) + } + } + } + + static func getMaxRecordableTime(id: Int, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) -> Void + { + guard let device = ThetaBleClientReactNative.deviceList[id] else { + reject(ERROR_TITLE, ERROR_MESSAGE_DEVICE_NOT_FOUND, nil) + return + } + guard let service = device.shootingControlCommand else { + reject(ERROR_TITLE, ERROR_MESSAGE_UNSUPPORTED_SERVICE, nil) + return + } + + Task { + do { + let value = try await service.getMaxRecordableTime() + resolve(value.name) + } catch { + reject(ERROR_TITLE, error.localizedDescription, error) + } + } + } + + static func setMaxRecordableTime(id: Int, + value: String, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) -> Void + { + guard let device = ThetaBleClientReactNative.deviceList[id] else { + reject(ERROR_TITLE, ERROR_MESSAGE_DEVICE_NOT_FOUND, nil) + return + } + guard let service = device.shootingControlCommand else { + reject(ERROR_TITLE, ERROR_MESSAGE_UNSUPPORTED_SERVICE, nil) + return + } + + Task { + do { + let enumValue = getEnumValue(values: MaxRecordableTime.values(), name: value) + guard let enumValue else { + reject(ERROR_TITLE, "File format not found. \(value)", nil) + return + } + try await service.setMaxRecordableTime(value: enumValue) + resolve(nil) + } catch { + reject(ERROR_TITLE, error.localizedDescription, error) + } + } + } + static func takePicture(id: Int, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void diff --git a/react-native/ios/ThetaBleClientConvertObject.swift b/react-native/ios/ThetaBleClientConvertObject.swift index d3bb4a4..e63e4a2 100644 --- a/react-native/ios/ThetaBleClientConvertObject.swift +++ b/react-native/ios/ThetaBleClientConvertObject.swift @@ -149,7 +149,7 @@ func toNotify( } func toNotifyError(error: KotlinThrowable?) -> [String: Any]? { - guard let error = error else { + guard let error else { return nil } return [ @@ -225,7 +225,7 @@ func toTheta(pluginControl: [String: Any?]) -> PluginControl? { values: PluginPowerStatus.values(), name: pluginControl[PluginPowerStatus.companion.keyName] as? String ?? "" ) - guard let pluginControlEnum = pluginControlEnum else { + guard let pluginControlEnum else { return nil } if let plugin = pluginControl[KEY_PLUGIN] as? Int { diff --git a/react-native/ios/ThetaBleClientReactNative.m b/react-native/ios/ThetaBleClientReactNative.m index 1502203..ff8620e 100644 --- a/react-native/ios/ThetaBleClientReactNative.m +++ b/react-native/ios/ThetaBleClientReactNative.m @@ -120,6 +120,22 @@ @interface RCT_EXTERN_MODULE(ThetaBleClientReactNative, NSObject) withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(nativeGetFileFormat:(int)id + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(nativeSetFileFormat:(int)id withValue:(NSString*)value + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(nativeGetMaxRecordableTime:(int)id + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(nativeSetMaxRecordableTime:(int)id withValue:(NSString*)value + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + RCT_EXTERN_METHOD(nativeTakePicture:(int)id withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) diff --git a/react-native/ios/ThetaBleClientReactNative.swift b/react-native/ios/ThetaBleClientReactNative.swift index 1875f62..d6bc547 100644 --- a/react-native/ios/ThetaBleClientReactNative.swift +++ b/react-native/ios/ThetaBleClientReactNative.swift @@ -438,6 +438,54 @@ class ThetaBleClientReactNative: RCTEventEmitter { } } + @objc(nativeGetFileFormat:withResolver:withRejecter:) + func nativeGetFileFormat(id: Int, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) -> Void + { + ShootingControlCommandService.getFileFormat(id: id) {value in + resolve(value) + } reject: { code, message, error in + reject(code, message, error) + } + } + + @objc(nativeSetFileFormat:withValue:withResolver:withRejecter:) + func nativeSetFileFormat(id: Int, value: String, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) -> Void + { + ShootingControlCommandService.setFileFormat(id: id, value: value) {_ in + resolve(nil) + } reject: { code, message, error in + reject(code, message, error) + } + } + + @objc(nativeGetMaxRecordableTime:withResolver:withRejecter:) + func nativeGetMaxRecordableTime(id: Int, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) -> Void + { + ShootingControlCommandService.getMaxRecordableTime(id: id) {value in + resolve(value) + } reject: { code, message, error in + reject(code, message, error) + } + } + + @objc(nativeSetMaxRecordableTime:withValue:withResolver:withRejecter:) + func nativeSetMaxRecordableTime(id: Int, value: String, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) -> Void + { + ShootingControlCommandService.setMaxRecordableTime(id: id, value: value) {_ in + resolve(nil) + } reject: { code, message, error in + reject(code, message, error) + } + } + @objc(nativeTakePicture:withResolver:withRejecter:) func nativeTakePicture(id: Int, resolve: @escaping RCTPromiseResolveBlock, diff --git a/react-native/package.json b/react-native/package.json index 51fc3da..b34249d 100644 --- a/react-native/package.json +++ b/react-native/package.json @@ -1,6 +1,6 @@ { "name": "theta-ble-client-react-native", - "version": "1.1.0", + "version": "1.2.0", "description": "This library provides a way to control RICOH THETA using", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/react-native/src/__mocks__/react-native.ts b/react-native/src/__mocks__/react-native.ts index 78d0210..bd32050 100644 --- a/react-native/src/__mocks__/react-native.ts +++ b/react-native/src/__mocks__/react-native.ts @@ -33,6 +33,10 @@ export const NativeModules = { nativeGetCaptureMode: jest.fn(), nativeSetCaptureMode: jest.fn(), + nativeGetFileFormat: jest.fn(), + nativeSetFileFormat: jest.fn(), + nativeGetMaxRecordableTime: jest.fn(), + nativeSetMaxRecordableTime: jest.fn(), nativeTakePicture: jest.fn(), nativeCameraControlCommandV2GetInfo: jest.fn(), diff --git a/react-native/src/__tests__/shooting-control/file-format.test.tsx b/react-native/src/__tests__/shooting-control/file-format.test.tsx new file mode 100644 index 0000000..8cab93b --- /dev/null +++ b/react-native/src/__tests__/shooting-control/file-format.test.tsx @@ -0,0 +1,116 @@ +import { FileFormatEnum, ShootingControlCommand, ThetaDevice } from '../..'; +import { NativeModules } from 'react-native'; + +const thetaBle = NativeModules.ThetaBleClientReactNative; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +afterEach(() => { + thetaBle.nativeGetFileFormat = jest.fn(); + thetaBle.nativeSetFileFormat = jest.fn(); +}); + +const devId = 1; +const devName = '0123456789'; + +async function getFileFormatTest(fileFormat: FileFormatEnum) { + jest.mocked(thetaBle.nativeGetFileFormat).mockImplementation( + jest.fn(async (id) => { + expect(id).toBe(devId); + return fileFormat as string; + }), + ); + + const device = new ThetaDevice(devId, devName); + const service = new ShootingControlCommand(device); + const response = await service.getFileFormat(); + + expect(response).toBe(fileFormat); + expect(thetaBle.nativeGetFileFormat).toHaveBeenCalledWith(devId); +} + +test('Call getFileFormat normal', () => { + const valueList = [ + FileFormatEnum.IMAGE_5K, + FileFormatEnum.VIDEO_4K, + FileFormatEnum.VIDEO_2K, + FileFormatEnum.IMAGE_6_7K, + FileFormatEnum.RAW_P_6_7K, + FileFormatEnum.VIDEO_2_7K, + FileFormatEnum.VIDEO_3_6K, + FileFormatEnum.RESERVED, + ]; + valueList.forEach(function(element){ + getFileFormatTest(element); + }); +}); + +test('Exception for Call getFileFormat', async () => { + jest.mocked(thetaBle.nativeGetFileFormat).mockImplementation( + jest.fn(async () => { + throw 'error'; + }), + ); + + const device = new ThetaDevice(devId, devName); + const service = new ShootingControlCommand(device); + try { + await service.getFileFormat(); + throw new Error('failed'); + } catch (error) { + expect(error).toBe('error'); + } + + expect(thetaBle.nativeGetFileFormat).toHaveBeenCalledWith(devId); +}); + +async function setFileFormatTest(fileFormat: FileFormatEnum) { + jest.mocked(thetaBle.nativeSetFileFormat).mockImplementation( + jest.fn(async (id, value: string) => { + expect(id).toBe(devId); + expect(value).toBe(fileFormat as string); + }), + ); + + const device = new ThetaDevice(devId, devName); + const service = new ShootingControlCommand(device); + await service.setFileFormat(fileFormat); + + expect(thetaBle.nativeSetFileFormat).toHaveBeenCalledWith(devId, fileFormat as string); +} + +test('Call setFileFormat normal', () => { + const valueList = [ + FileFormatEnum.IMAGE_5K, + FileFormatEnum.VIDEO_4K, + FileFormatEnum.VIDEO_2K, + FileFormatEnum.IMAGE_6_7K, + FileFormatEnum.RAW_P_6_7K, + FileFormatEnum.VIDEO_2_7K, + FileFormatEnum.VIDEO_3_6K, + ]; + valueList.forEach(function(element){ + setFileFormatTest(element); + }); +}); + +test('Exception for Call setFileFormat', async () => { + jest.mocked(thetaBle.nativeSetFileFormat).mockImplementation( + jest.fn(async () => { + throw 'error'; + }), + ); + + const device = new ThetaDevice(devId, devName); + const service = new ShootingControlCommand(device); + try { + await service.setFileFormat(FileFormatEnum.IMAGE_5K); + throw new Error('failed'); + } catch (error) { + expect(error).toBe('error'); + } + + expect(thetaBle.nativeSetFileFormat).toHaveBeenCalledWith(devId, FileFormatEnum.IMAGE_5K as string); +}); diff --git a/react-native/src/__tests__/shooting-control/max-recordable-time.test.tsx b/react-native/src/__tests__/shooting-control/max-recordable-time.test.tsx new file mode 100644 index 0000000..ff4b8c4 --- /dev/null +++ b/react-native/src/__tests__/shooting-control/max-recordable-time.test.tsx @@ -0,0 +1,108 @@ +import { MaxRecordableTimeEnum, ShootingControlCommand, ThetaDevice } from '../..'; +import { NativeModules } from 'react-native'; + +const thetaBle = NativeModules.ThetaBleClientReactNative; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +afterEach(() => { + thetaBle.nativeGetMaxRecordableTime = jest.fn(); + thetaBle.nativeSetMaxRecordableTime = jest.fn(); +}); + +const devId = 1; +const devName = '0123456789'; + +async function getMaxRecordableTimeTest(value: MaxRecordableTimeEnum) { + jest.mocked(thetaBle.nativeGetMaxRecordableTime).mockImplementation( + jest.fn(async (id) => { + expect(id).toBe(devId); + return value as string; + }), + ); + + const device = new ThetaDevice(devId, devName); + const service = new ShootingControlCommand(device); + const response = await service.getMaxRecordableTime(); + + expect(response).toBe(value); + expect(thetaBle.nativeGetMaxRecordableTime).toHaveBeenCalledWith(devId); +} + +test('Call getMaxRecordableTime normal', () => { + const valueList = [ + MaxRecordableTimeEnum.RECORDABLE_TIME_300, + MaxRecordableTimeEnum.RECORDABLE_TIME_1500, + MaxRecordableTimeEnum.RECORDABLE_TIME_3000, + ]; + valueList.forEach(function(element){ + getMaxRecordableTimeTest(element); + }); +}); + +test('Exception for Call getMaxRecordableTime', async () => { + jest.mocked(thetaBle.nativeGetMaxRecordableTime).mockImplementation( + jest.fn(async () => { + throw 'error'; + }), + ); + + const device = new ThetaDevice(devId, devName); + const service = new ShootingControlCommand(device); + try { + await service.getMaxRecordableTime(); + throw new Error('failed'); + } catch (error) { + expect(error).toBe('error'); + } + + expect(thetaBle.nativeGetMaxRecordableTime).toHaveBeenCalledWith(devId); +}); + +async function setMaxRecordableTimeTest(value: MaxRecordableTimeEnum) { + jest.mocked(thetaBle.nativeSetMaxRecordableTime).mockImplementation( + jest.fn(async (id, mode: string) => { + expect(id).toBe(devId); + expect(mode).toBe(value as string); + }), + ); + + const device = new ThetaDevice(devId, devName); + const service = new ShootingControlCommand(device); + await service.setMaxRecordableTime(value); + + expect(thetaBle.nativeSetMaxRecordableTime).toHaveBeenCalledWith(devId, value as string); +} + +test('Call setMaxRecordableTime normal', () => { + const valueList = [ + MaxRecordableTimeEnum.RECORDABLE_TIME_300, + MaxRecordableTimeEnum.RECORDABLE_TIME_1500, + MaxRecordableTimeEnum.RECORDABLE_TIME_3000, + ]; + valueList.forEach(function(element){ + setMaxRecordableTimeTest(element); + }); +}); + +test('Exception for Call setMaxRecordableTime', async () => { + jest.mocked(thetaBle.nativeSetMaxRecordableTime).mockImplementation( + jest.fn(async () => { + throw 'error'; + }), + ); + + const device = new ThetaDevice(devId, devName); + const service = new ShootingControlCommand(device); + try { + await service.setMaxRecordableTime(MaxRecordableTimeEnum.RECORDABLE_TIME_1500); + throw new Error('failed'); + } catch (error) { + expect(error).toBe('error'); + } + + expect(thetaBle.nativeSetMaxRecordableTime) + .toHaveBeenCalledWith(devId, MaxRecordableTimeEnum.RECORDABLE_TIME_1500 as string); +}); diff --git a/react-native/src/__tests__/values/file-format.test.tsx b/react-native/src/__tests__/values/file-format.test.tsx new file mode 100644 index 0000000..80d68c3 --- /dev/null +++ b/react-native/src/__tests__/values/file-format.test.tsx @@ -0,0 +1,24 @@ +import { FileFormatEnum } from '../../service'; + +describe('FileFormatEnum', () => { + const data: [FileFormatEnum, string][] = [ + [FileFormatEnum.RESERVED, 'RESERVED'], + [FileFormatEnum.IMAGE_5K, 'IMAGE_5K'], + [FileFormatEnum.VIDEO_4K, 'VIDEO_4K'], + [FileFormatEnum.VIDEO_2K, 'VIDEO_2K'], + [FileFormatEnum.IMAGE_6_7K, 'IMAGE_6_7K'], + [FileFormatEnum.RAW_P_6_7K, 'RAW_P_6_7K'], + [FileFormatEnum.VIDEO_2_7K, 'VIDEO_2_7K'], + [FileFormatEnum.VIDEO_3_6K, 'VIDEO_3_6K'], + ]; + + test('length', () => { + expect(data.length).toBe(Object.keys(FileFormatEnum).length); + }); + + test('data', () => { + data.forEach((item) => { + expect(item[0]).toBe(item[1]); + }); + }); +}); diff --git a/react-native/src/__tests__/values/max-recordable-time.test.tsx b/react-native/src/__tests__/values/max-recordable-time.test.tsx new file mode 100644 index 0000000..e376791 --- /dev/null +++ b/react-native/src/__tests__/values/max-recordable-time.test.tsx @@ -0,0 +1,19 @@ +import { MaxRecordableTimeEnum } from '../../service'; + +describe('MaxRecordableTimeEnum', () => { + const data: [MaxRecordableTimeEnum, string][] = [ + [MaxRecordableTimeEnum.RECORDABLE_TIME_300, 'RECORDABLE_TIME_300'], + [MaxRecordableTimeEnum.RECORDABLE_TIME_1500, 'RECORDABLE_TIME_1500'], + [MaxRecordableTimeEnum.RECORDABLE_TIME_3000, 'RECORDABLE_TIME_3000'], + ]; + + test('length', () => { + expect(data.length).toBe(Object.keys(MaxRecordableTimeEnum).length); + }); + + test('data', () => { + data.forEach((item) => { + expect(item[0]).toBe(item[1]); + }); + }); +}); diff --git a/react-native/src/native/native-functions.ts b/react-native/src/native/native-functions.ts index 62c0eba..af174d4 100644 --- a/react-native/src/native/native-functions.ts +++ b/react-native/src/native/native-functions.ts @@ -12,6 +12,8 @@ import type { CameraPowerEnum, CaptureModeEnum, ChargingStateEnum, + FileFormatEnum, + MaxRecordableTimeEnum, PluginControl, PluginList, PluginOrders, @@ -156,6 +158,22 @@ export async function nativeSetCaptureMode(id: number, value: CaptureModeEnum) { return ThetaBleClientReactNative.nativeSetCaptureMode(id, value); } +export async function nativeGetFileFormat(id: number): Promise { + return ThetaBleClientReactNative.nativeGetFileFormat(id); +} + +export async function nativeSetFileFormat(id: number, value: FileFormatEnum) { + return ThetaBleClientReactNative.nativeSetFileFormat(id, value); +} + +export async function nativeGetMaxRecordableTime(id: number): Promise { + return ThetaBleClientReactNative.nativeGetMaxRecordableTime(id); +} + +export async function nativeSetMaxRecordableTime(id: number, value: MaxRecordableTimeEnum) { + return ThetaBleClientReactNative.nativeSetMaxRecordableTime(id, value); +} + export async function nativeTakePicture(id: number) { return ThetaBleClientReactNative.nativeTakePicture(id); } diff --git a/react-native/src/service/shooting-control-command.ts b/react-native/src/service/shooting-control-command.ts index e8e7b9e..eb6ec86 100644 --- a/react-native/src/service/shooting-control-command.ts +++ b/react-native/src/service/shooting-control-command.ts @@ -2,7 +2,7 @@ import type { ThetaDevice } from '../theta-device'; import { ThetaService } from './theta-service'; -import { BleServiceEnum, CaptureModeEnum } from './values'; +import { BleServiceEnum, CaptureModeEnum, FileFormatEnum, MaxRecordableTimeEnum } from './values'; import * as ThetaBleClient from '../native'; /** @@ -56,6 +56,78 @@ export class ShootingControlCommand extends ThetaService { } } + /** + * Acquires the recording size (pixels) of the camera. + * + * Service: 1D0F3602-8DFB-4340-9045-513040DAD991 + * + * Characteristic: E8F0EDD1-6C0F-494A-95C3-3244AE0B9A01 + * + * @returns File format. + */ + async getFileFormat(): Promise { + return new Promise((resolve, reject) => { + ThetaBleClient.nativeGetFileFormat(this.device.id) + .then((value) => { + resolve(value as FileFormatEnum); + }) + .catch((error) => reject(error)); + }); + } + + /** + * Set the recording size (pixels) of the camera. + * + * Service: 1D0F3602-8DFB-4340-9045-513040DAD991 + * + * Characteristic: E8F0EDD1-6C0F-494A-95C3-3244AE0B9A01 + * + * @param value File format. + */ + async setFileFormat(value: FileFormatEnum) { + try { + return await ThetaBleClient.nativeSetFileFormat(this.device.id, value); + } catch(error) { + throw error; + } + } + + /** + * Acquires the maximum recordable time (in seconds) of the camera. + * + * Service: 1D0F3602-8DFB-4340-9045-513040DAD991 + * + * Characteristic: 6EABAB73-7F2B-4061-BE7C-1D71D143CB7D + * + * @returns Maximum recordable time. + */ + async getMaxRecordableTime(): Promise { + return new Promise((resolve, reject) => { + ThetaBleClient.nativeGetMaxRecordableTime(this.device.id) + .then((value) => { + resolve(value as MaxRecordableTimeEnum); + }) + .catch((error) => reject(error)); + }); + } + + /** + * Set the maximum recordable time (in seconds) of the camera. + * + * Service: 1D0F3602-8DFB-4340-9045-513040DAD991 + * + * Characteristic: 6EABAB73-7F2B-4061-BE7C-1D71D143CB7D + * + * @param value Maximum recordable time. + */ + async setMaxRecordableTime(value: MaxRecordableTimeEnum) { + try { + return await ThetaBleClient.nativeSetMaxRecordableTime(this.device.id, value); + } catch(error) { + throw error; + } + } + /** * Instructs the camera to start shooting a still image. Also, acquires the shooting status. * diff --git a/react-native/src/service/values/file-format.ts b/react-native/src/service/values/file-format.ts new file mode 100644 index 0000000..3c13bf4 --- /dev/null +++ b/react-native/src/service/values/file-format.ts @@ -0,0 +1,66 @@ +/** + * File format. + */ +export const FileFormatEnum = { + /** + * Reserved value + */ + RESERVED: 'RESERVED', + + /** + * Still image. 5376x2688 + * + * For RICOH THETA V + */ + IMAGE_5K: 'IMAGE_5K', + + /** + * Movie. 3840x1920. H.264/MPEG-4 AVC + * + * For RICOH THETA V, RICOH THETA Z1 or later + */ + VIDEO_4K: 'VIDEO_4K', + + /** + * Movie. 1920x960. H.264/MPEG-4 AVC + * + * For RICOH THETA V, RICOH THETA Z1 or later + */ + VIDEO_2K: 'VIDEO_2K', + + /** + * Still image JPEG format. 6720x3360 (Equirectangular) or 7296x3648 (Dual-Fisheye) + * + * For RICOH THETA Z1 or later + */ + IMAGE_6_7K: 'IMAGE_6_7K', + + /** + * Still image RAW+ format. 7296x3648 + * + * For RICOH THETA Z1 or later + */ + RAW_P_6_7K: 'RAW_P_6_7K', + + /** + * Movie. 2688x2688. H.264/MPEG-4 AVC + * + * RICOH THETA Z1 firmware v3.01.1 or later. This mode outputs two fisheye video for each lens. + * The MP4 file name ending with _0 is the video file on the front lens, + * and _1 is back lens. This mode does not record audio track to MP4 file. + */ + VIDEO_2_7K: 'VIDEO_2_7K', + + /** + * Movie. 3648x3648. H.264/MPEG-4 AVC + * + * RICOH THETA Z1 firmware v3.01.1 or later. This mode outputs two fisheye video for each lens. + * The MP4 file name ending with _0 is the video file on the front lens, + * and _1 is back lens. This mode does not record audio track to MP4 file. + */ + VIDEO_3_6K: 'VIDEO_3_6K', +} as const; + +/** type definition of FileFormatEnum */ +export type FileFormatEnum = + typeof FileFormatEnum[keyof typeof FileFormatEnum]; diff --git a/react-native/src/service/values/index.ts b/react-native/src/service/values/index.ts index e388752..ea8d940 100644 --- a/react-native/src/service/values/index.ts +++ b/react-native/src/service/values/index.ts @@ -5,6 +5,8 @@ export * from './capture-mode'; export * from './capture-status'; export * from './charging-state'; export * from './command-error-description'; +export * from './file-format'; +export * from './max-recordable-time'; export * from './plugin-power-status'; export * from './shooting-function'; export * from './theta-model'; diff --git a/react-native/src/service/values/max-recordable-time.ts b/react-native/src/service/values/max-recordable-time.ts new file mode 100644 index 0000000..978c7f2 --- /dev/null +++ b/react-native/src/service/values/max-recordable-time.ts @@ -0,0 +1,26 @@ +/** + * Maximum recordable time (in seconds) of the camera. + */ +export const MaxRecordableTimeEnum = { + /** + * Maximum recordable time. 300sec for other than SC2. + */ + RECORDABLE_TIME_300: 'RECORDABLE_TIME_300', + + /** + * Maximum recordable time. 1500sec for other than SC2. + */ + RECORDABLE_TIME_1500: 'RECORDABLE_TIME_1500', + + /** + * Maximum recordable time. 3000sec for THETA Z1 Version 3.01.1 or later + * only for 3.6K 1/2fps and 2.7K 1/2fps. + * If you set 3000 seconds in 3.6K 2fps mode and then set back to 4K 30fps mode, + * the max recordable time will be overwritten to 300 seconds automatically. + */ + RECORDABLE_TIME_3000: 'RECORDABLE_TIME_3000', +} as const; + +/** type definition of MaxRecordableTimeEnum */ +export type MaxRecordableTimeEnum = + typeof MaxRecordableTimeEnum[keyof typeof MaxRecordableTimeEnum]; diff --git a/react-native/theta-ble-client-react-native.podspec b/react-native/theta-ble-client-react-native.podspec index af98db3..ff1b72b 100644 --- a/react-native/theta-ble-client-react-native.podspec +++ b/react-native/theta-ble-client-react-native.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.dependency "React-Core" - s.dependency 'THETABleClient', '1.1.0' + s.dependency 'THETABleClient', '1.2.0' # Don't install the dependencies when we run `pod install` in the old architecture. if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then