diff --git a/android/.gitignore b/android/.gitignore index a172d9d..20a1870 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,2 +1,3 @@ /build -/.tauri \ No newline at end of file +/.tauri +/.gradle \ No newline at end of file diff --git a/android/src/main/kotlin/app/tauri/serialplugin/SerialPlugin.kt b/android/src/main/kotlin/app/tauri/serialplugin/SerialPlugin.kt index 5a63110..ff1a37c 100644 --- a/android/src/main/kotlin/app/tauri/serialplugin/SerialPlugin.kt +++ b/android/src/main/kotlin/app/tauri/serialplugin/SerialPlugin.kt @@ -1,7 +1,8 @@ +@file:Suppress("unused") + package app.tauri.serialplugin import android.app.Activity -import android.content.Context import app.tauri.annotation.Command import app.tauri.annotation.InvokeArg import app.tauri.annotation.TauriPlugin @@ -13,7 +14,45 @@ import app.tauri.serialplugin.models.* import android.webkit.WebView import android.util.Log import java.util.concurrent.ConcurrentHashMap +import app.tauri.plugin.JSArray +// --- Reused from previous answer (Converts a Map to a JSObject) --- +fun Map.toJSObject(): JSObject { + val jsObject = JSObject() + for ((key, value) in this) { + val convertedValue: Any? = when (value) { + is Map<*, *> -> @Suppress("UNCHECKED_CAST") (value as Map).toJSObject() + is List<*> -> @Suppress("UNCHECKED_CAST") value.toJSArray() // Call the new list utility + else -> value + } + jsObject.put(key, convertedValue) + } + return jsObject +} + +// --- NEW Utility: Converts a List to a JSArray --- +fun List.toJSArray(): JSArray { + val jsArray = JSArray() + + for (item in this) { + val convertedItem: Any? = when (item) { + is Map<*, *> -> { + // If the item is a Map, convert it to a JSObject + @Suppress("UNCHECKED_CAST") + (item as Map).toJSObject() + } + is List<*> -> { + // If the item is a nested List, convert it to a JSArray (for list of lists) + @Suppress("UNCHECKED_CAST") + item.toJSArray() + } + else -> item // Primitives (String, Int, Boolean, etc.) can be put directly + } + // Add the converted item to the JSArray + jsArray.put(convertedItem) + } + return jsArray +} @InvokeArg class PortConfigArgs { lateinit var path: String @@ -32,6 +71,12 @@ class WriteArgs { lateinit var value: String } +@InvokeArg +class WriteBinaryArgs { + lateinit var path: String + lateinit var value: List +} + @InvokeArg class CloseArgs { lateinit var path: String @@ -51,14 +96,14 @@ class SerialPlugin(private val activity: Activity) : Plugin(activity) { Log.d("SerialPlugin", "SerialPlugin loaded successfully") } - override fun onDetach() { + override fun onDestroy() { try { Log.d("SerialPlugin", "SerialPlugin detaching, cleaning up resources") serialPortManager.cleanup() } catch (e: Exception) { Log.e("SerialPlugin", "Failed to cleanup: ${e.message}", e) } - super.onDetach() + super.onDestroy() } @Command @@ -68,7 +113,8 @@ class SerialPlugin(private val activity: Activity) : Plugin(activity) { val ports = serialPortManager.getAvailablePorts() Log.d("SerialPlugin", "Available ports fetched successfully: ${ports.size} ports") val result = JSObject() - result.put("ports", ports) + + result.put("ports", ports.toJSObject()) invoke.resolve(result) } catch (e: Exception) { Log.e("SerialPlugin", "Failed to get available ports: ${e.message}", e) @@ -208,9 +254,10 @@ class SerialPlugin(private val activity: Activity) : Plugin(activity) { @Command fun writeBinary(invoke: Invoke) { try { - val args = invoke.parseArgs(WriteArgs::class.java) + val args = invoke.parseArgs(WriteBinaryArgs::class.java) Log.d("SerialPlugin", "Writing binary to port: ${args.path}") - val bytesWritten = serialPortManager.writeToPort(args.path, args.value.toByteArray()) + val bytesToSend = args.value.map { it.toByte() }.toByteArray() + val bytesWritten = serialPortManager.writeToPort(args.path, bytesToSend) val result = JSObject() result.put("bytesWritten", bytesWritten) Log.d("SerialPlugin", "Binary write successful: $bytesWritten bytes written") @@ -226,7 +273,7 @@ class SerialPlugin(private val activity: Activity) : Plugin(activity) { try { val args = invoke.parseArgs(PortConfigArgs::class.java) Log.d("SerialPlugin", "Reading from port: ${args.path}") - val data = serialPortManager.readFromPort(args.path, args.timeout, 1024) + val data = serialPortManager.readFromPort(args.path, args.timeout, args.size) val result = JSObject() result.put("data", String(data)) Log.d("SerialPlugin", "Read successful: ${data.size} bytes read") @@ -242,9 +289,9 @@ class SerialPlugin(private val activity: Activity) : Plugin(activity) { try { val args = invoke.parseArgs(PortConfigArgs::class.java) Log.d("SerialPlugin", "Reading binary from port: ${args.path}") - val data = serialPortManager.readFullyFromPort(args.path, args.timeout, args.size) + val data = serialPortManager.readFromPort(args.path, args.timeout, args.size) val result = JSObject().apply { - put("data", data.toList()) + put("data", data.toList().map { it.toInt() and 0xFF }.toJSArray()) } Log.d("SerialPlugin", "Binary read successful: ${data.size} bytes read") invoke.resolve(result) @@ -260,7 +307,7 @@ class SerialPlugin(private val activity: Activity) : Plugin(activity) { val args = invoke.parseArgs(CloseArgs::class.java) Log.d("SerialPlugin", "Starting listening on port: ${args.path}") - val listener = { data: ByteArray -> + val listener: (ByteArray) -> Unit = { data: ByteArray -> try { val eventData = JSObject() eventData.put("path", args.path) diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialPortManager.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialPortManager.kt index 047655e..31ea9a6 100644 --- a/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialPortManager.kt +++ b/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialPortManager.kt @@ -12,21 +12,22 @@ import com.hoho.android.usbserial.driver.UsbSerialPort import com.hoho.android.usbserial.driver.UsbSerialProber import com.hoho.android.usbserial.util.SerialInputOutputManager import com.hoho.android.usbserial.driver.ProbeTable -import com.hoho.android.usbserial.driver.FtdiSerialDriver -import com.hoho.android.usbserial.driver.Cp21xxSerialDriver -import com.hoho.android.usbserial.driver.Ch34xSerialDriver -import com.hoho.android.usbserial.driver.ProlificSerialDriver import app.tauri.serialplugin.models.* import java.util.concurrent.Executors import java.io.IOException import android.util.Log -import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit +data class ManagedPort ( + val port: UsbSerialPort, + val config: SerialPortConfig +) + class SerialPortManager(private val context: Context) { private val usbManager: UsbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager - private val portMap = mutableMapOf() + private val portMap = mutableMapOf() private val ioManagerMap = mutableMapOf() private val executor = Executors.newCachedThreadPool() private val permissionFutures = mutableMapOf>() @@ -75,14 +76,19 @@ class SerialPortManager(private val context: Context) { if (Build.VERSION.SDK_INT >= 33) { context.registerReceiver(usbReceiver, filter, Context.RECEIVER_EXPORTED) } else { - context.registerReceiver(usbReceiver, filter) + ContextCompat.registerReceiver( + context, + usbReceiver, + filter, + ContextCompat.RECEIVER_NOT_EXPORTED + ) } } fun unregisterReceiver() { try { context.unregisterReceiver(usbReceiver) - } catch (e: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { Log.w("SerialPortManager", "Receiver not registered") } } @@ -175,12 +181,9 @@ class SerialPortManager(private val context: Context) { val permissionFuture = CompletableFuture() permissionFutures[device.deviceName] = permissionFuture - val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val flags = PendingIntent.FLAG_IMMUTABLE - } else { - 0 - } - + val permissionIntent = PendingIntent.getBroadcast( context, 0, @@ -215,7 +218,7 @@ class SerialPortManager(private val context: Context) { config.parity.value ) Log.d("SerialPortManager", "Port parameters set successfully") - } catch (e: UnsupportedOperationException) { + } catch (_: UnsupportedOperationException) { Log.w("SerialPortManager", "setParameters not supported for this device, using default settings") // Some devices don't support parameter changes, continue with defaults } catch (e: Exception) { @@ -228,10 +231,10 @@ class SerialPortManager(private val context: Context) { FlowControl.HARDWARE -> { Log.d("SerialPortManager", "Enabling hardware flow control") try { - port.setDTR(true) - port.setRTS(true) + port.dtr = true + port.rts = true Log.d("SerialPortManager", "Hardware flow control enabled successfully") - } catch (e: UnsupportedOperationException) { + } catch (_: UnsupportedOperationException) { Log.w("SerialPortManager", "Hardware flow control not supported by this device") } catch (e: Exception) { Log.w("SerialPortManager", "Failed to enable hardware flow control: ${e.message}") @@ -245,7 +248,7 @@ class SerialPortManager(private val context: Context) { } } - portMap[config.path] = port + portMap[config.path] = ManagedPort(port, config) Log.d("SerialPortManager", "Port opened successfully: ${config.path}") return true @@ -294,15 +297,19 @@ class SerialPortManager(private val context: Context) { ioManagerMap[path] = ioManager try { - executor.submit(Runnable { + executor.submit { try { ioManager.start() Log.d("SerialPortManager", "IO Manager started successfully for $path") } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to start IO Manager for $path: ${e.message}", e) + Log.e( + "SerialPortManager", + "Failed to start IO Manager for $path: ${e.message}", + e + ) closePort(path) } - }) + } } catch (e: Exception) { Log.e("SerialPortManager", "Failed to submit IO Manager task for $path: ${e.message}", e) closePort(path) @@ -315,14 +322,9 @@ class SerialPortManager(private val context: Context) { Log.d("SerialPortManager", "Writing to port $path: ${data.size} bytes") - val bytesWritten = port.write(data, 1000) // 1 second timeout - - if (bytesWritten > 0) { - Log.d("SerialPortManager", "Write successful: $bytesWritten bytes written") - } else { - Log.w("SerialPortManager", "Write timeout: no bytes written") - } - + port.port.write(data, 1000) // 1 second timeout + val bytesWritten = data.size + return bytesWritten } catch (e: IOException) { Log.e("SerialPortManager", "Write failed: ${e.message}") @@ -337,7 +339,7 @@ class SerialPortManager(private val context: Context) { try { ioManagerMap[path]?.stop() ioManagerMap.remove(path) - portMap[path]?.close() + portMap[path]?.port?.close() portMap.remove(path) Log.d("SerialPortManager", "Port closed: $path") } catch (e: Exception) { @@ -365,7 +367,7 @@ class SerialPortManager(private val context: Context) { fun setPortParameters(path: String, config: SerialPortConfig): Boolean { return try { portMap[path]?.let { port -> - port.setParameters( + port.port.setParameters( config.baudRate, config.dataBits.value, config.stopBits.value, @@ -384,7 +386,7 @@ class SerialPortManager(private val context: Context) { val port = portMap[path] ?: throw IOException("Port not found") val targetSize = size ?: 1024 - val maxPacketSize = port.getReadEndpoint().getMaxPacketSize() + val maxPacketSize = port.port.readEndpoint.maxPacketSize val bufferSize = minOf(targetSize, maxPacketSize) val buffer = ByteArray(bufferSize) @@ -392,7 +394,7 @@ class SerialPortManager(private val context: Context) { Log.d("SerialPortManager", "Reading from port $path: bufferSize=$bufferSize, timeout=$adjustedTimeout") - val bytesRead = port.read(buffer, adjustedTimeout) + val bytesRead = port.port.read(buffer, adjustedTimeout) if (bytesRead > 0) { Log.d("SerialPortManager", "Read successful: $bytesRead bytes") @@ -416,7 +418,7 @@ class SerialPortManager(private val context: Context) { val startTime = System.currentTimeMillis() val targetSize = size ?: 1024 - val maxPacketSize = port.getReadEndpoint().getMaxPacketSize() + val maxPacketSize = port.port.readEndpoint.maxPacketSize while (buffer.size < targetSize && (System.currentTimeMillis() - startTime) < timeout) { val remainingTime = timeout - (System.currentTimeMillis() - startTime).toInt() @@ -424,7 +426,7 @@ class SerialPortManager(private val context: Context) { val chunkSize = minOf(targetSize - buffer.size, maxPacketSize) val tempBuffer = ByteArray(chunkSize) - val bytesRead = port.read(tempBuffer, remainingTime.coerceAtLeast(200)) + val bytesRead = port.port.read(tempBuffer, remainingTime.coerceAtLeast(200)) if (bytesRead > 0) { buffer.addAll(tempBuffer.copyOf(bytesRead).toList()) @@ -443,7 +445,8 @@ class SerialPortManager(private val context: Context) { fun setBaudRate(path: String, baudRate: Int): Boolean { return try { val port = portMap[path] ?: return false - port.setParameters(baudRate, port.dataBits, port.stopBits, port.parity) + port.config.baudRate = baudRate + port.port.setParameters(port.config.baudRate, port.config.dataBits.value, port.config.stopBits.value, port.config.parity.value) true } catch (e: Exception) { Log.e("SerialPortManager", "Failed to set baud rate: ${e.message}", e) @@ -454,7 +457,8 @@ class SerialPortManager(private val context: Context) { fun setDataBits(path: String, dataBits: DataBits): Boolean { return try { val port = portMap[path] ?: return false - port.setParameters(port.baudRate, dataBits.value, port.stopBits, port.parity) + port.config.dataBits = dataBits + port.port.setParameters(port.config.baudRate, port.config.dataBits.value, port.config.stopBits.value, port.config.parity.value) true } catch (e: Exception) { Log.e("SerialPortManager", "Failed to set data bits: ${e.message}", e) @@ -466,8 +470,8 @@ class SerialPortManager(private val context: Context) { return try { when (flowControl) { FlowControl.HARDWARE -> { - portMap[path]?.setDTR(true) - portMap[path]?.setRTS(true) + portMap[path]?.port?.dtr = true + portMap[path]?.port?.rts = true } FlowControl.SOFTWARE -> { // Software flow control implementation @@ -484,7 +488,8 @@ class SerialPortManager(private val context: Context) { fun setParity(path: String, parity: Parity): Boolean { return try { val port = portMap[path] ?: return false - port.setParameters(port.baudRate, port.dataBits, port.stopBits, parity.value) + port.config.parity = parity + port.port.setParameters(port.config.baudRate, port.config.dataBits.value, port.config.stopBits.value, port.config.parity.value) true } catch (e: Exception) { Log.e("SerialPortManager", "Failed to set parity: ${e.message}", e) @@ -495,7 +500,8 @@ class SerialPortManager(private val context: Context) { fun setStopBits(path: String, stopBits: StopBits): Boolean { return try { val port = portMap[path] ?: return false - port.setParameters(port.baudRate, port.dataBits, stopBits.value, port.parity) + port.config.stopBits = stopBits + port.port.setParameters(port.config.baudRate, port.config.dataBits.value, port.config.stopBits.value, port.config.parity.value) true } catch (e: Exception) { Log.e("SerialPortManager", "Failed to set stop bits: ${e.message}", e) @@ -517,7 +523,7 @@ class SerialPortManager(private val context: Context) { fun writeRequestToSend(path: String, level: Boolean): Boolean { return try { - portMap[path]?.setRTS(level) + portMap[path]?.port?.rts = level true } catch (e: Exception) { Log.e("SerialPortManager", "Failed to set RTS: ${e.message}", e) @@ -527,7 +533,7 @@ class SerialPortManager(private val context: Context) { fun writeDataTerminalReady(path: String, level: Boolean): Boolean { return try { - portMap[path]?.setDTR(level) + portMap[path]?.port?.dtr = level true } catch (e: Exception) { Log.e("SerialPortManager", "Failed to set DTR: ${e.message}", e) @@ -537,7 +543,7 @@ class SerialPortManager(private val context: Context) { fun readClearToSend(path: String): Boolean { return try { - portMap[path]?.getCTS() ?: false + portMap[path]?.port?.cts ?: false } catch (e: Exception) { Log.e("SerialPortManager", "Failed to read CTS: ${e.message}", e) false @@ -546,7 +552,7 @@ class SerialPortManager(private val context: Context) { fun readDataSetReady(path: String): Boolean { return try { - portMap[path]?.getDSR() ?: false + portMap[path]?.port?.dsr ?: false } catch (e: Exception) { Log.e("SerialPortManager", "Failed to read DSR: ${e.message}", e) false @@ -555,7 +561,7 @@ class SerialPortManager(private val context: Context) { fun readRingIndicator(path: String): Boolean { return try { - portMap[path]?.getRI() ?: false + portMap[path]?.port?.ri ?: false } catch (e: Exception) { Log.e("SerialPortManager", "Failed to read RI: ${e.message}", e) false @@ -564,7 +570,7 @@ class SerialPortManager(private val context: Context) { fun readCarrierDetect(path: String): Boolean { return try { - portMap[path]?.getCD() ?: false + portMap[path]?.port?.cd ?: false } catch (e: Exception) { Log.e("SerialPortManager", "Failed to read CD: ${e.message}", e) false @@ -607,7 +613,7 @@ class SerialPortManager(private val context: Context) { fun setBreak(path: String): Boolean { return try { - portMap[path]?.setBreak(true) + portMap[path]?.port?.setBreak(true) true } catch (e: Exception) { Log.e("SerialPortManager", "Failed to set break: ${e.message}", e) @@ -617,7 +623,7 @@ class SerialPortManager(private val context: Context) { fun clearBreak(path: String): Boolean { return try { - portMap[path]?.setBreak(false) + portMap[path]?.port?.setBreak(false) true } catch (e: Exception) { Log.e("SerialPortManager", "Failed to clear break: ${e.message}", e) @@ -627,7 +633,7 @@ class SerialPortManager(private val context: Context) { fun startListening(path: String, onDataReceived: (ByteArray) -> Unit) { val port = portMap[path] ?: throw IOException("Port not found") - startIoManager(path, port, onDataReceived) + startIoManager(path, port.port, onDataReceived) } fun stopListening(path: String) { diff --git a/android/src/main/kotlin/app/tauri/serialplugin/models/SerialPortConfig.kt b/android/src/main/kotlin/app/tauri/serialplugin/models/SerialPortConfig.kt index b4bee7e..b1b8927 100644 --- a/android/src/main/kotlin/app/tauri/serialplugin/models/SerialPortConfig.kt +++ b/android/src/main/kotlin/app/tauri/serialplugin/models/SerialPortConfig.kt @@ -10,7 +10,7 @@ enum class DataBits(val value: Int) { companion object { fun fromValue(value: Int): DataBits { - return values().find { it.value == value } ?: EIGHT + return DataBits.entries.find { it.value == value } ?: EIGHT } } } @@ -38,7 +38,7 @@ enum class Parity(val value: Int) { companion object { fun fromValue(value: Int): Parity { - return values().find { it.value == value } ?: NONE + return Parity.entries.find { it.value == value } ?: NONE } } } @@ -49,7 +49,7 @@ enum class StopBits(val value: Int) { companion object { fun fromValue(value: Int): StopBits { - return values().find { it.value == value } ?: ONE + return StopBits.entries.find { it.value == value } ?: ONE } } } @@ -72,11 +72,11 @@ enum class ClearBuffer { } data class SerialPortConfig( - val path: String, - val baudRate: Int = 9600, - val dataBits: DataBits = DataBits.EIGHT, - val flowControl: FlowControl = FlowControl.NONE, - val parity: Parity = Parity.NONE, - val stopBits: StopBits = StopBits.ONE, - val timeout: Int = 1000 -) \ No newline at end of file + var path: String, + var baudRate: Int = 9600, + var dataBits: DataBits = DataBits.EIGHT, + var flowControl: FlowControl = FlowControl.NONE, + var parity: Parity = Parity.NONE, + var stopBits: StopBits = StopBits.ONE, + var timeout: Int = 1000 +) diff --git a/examples/serialport-test/src-tauri/.gradle/8.3/checksums/checksums.lock b/examples/serialport-test/src-tauri/.gradle/8.3/checksums/checksums.lock new file mode 100644 index 0000000..b0085ac Binary files /dev/null and b/examples/serialport-test/src-tauri/.gradle/8.3/checksums/checksums.lock differ diff --git a/examples/serialport-test/src-tauri/.gradle/8.3/checksums/md5-checksums.bin b/examples/serialport-test/src-tauri/.gradle/8.3/checksums/md5-checksums.bin new file mode 100644 index 0000000..5b26e41 Binary files /dev/null and b/examples/serialport-test/src-tauri/.gradle/8.3/checksums/md5-checksums.bin differ diff --git a/examples/serialport-test/src-tauri/.gradle/8.3/checksums/sha1-checksums.bin b/examples/serialport-test/src-tauri/.gradle/8.3/checksums/sha1-checksums.bin new file mode 100644 index 0000000..8664663 Binary files /dev/null and b/examples/serialport-test/src-tauri/.gradle/8.3/checksums/sha1-checksums.bin differ diff --git a/examples/serialport-test/src-tauri/.gradle/8.3/dependencies-accessors/dependencies-accessors.lock b/examples/serialport-test/src-tauri/.gradle/8.3/dependencies-accessors/dependencies-accessors.lock new file mode 100644 index 0000000..0a073a5 Binary files /dev/null and b/examples/serialport-test/src-tauri/.gradle/8.3/dependencies-accessors/dependencies-accessors.lock differ diff --git a/examples/serialport-test/src-tauri/.gradle/8.3/dependencies-accessors/gc.properties b/examples/serialport-test/src-tauri/.gradle/8.3/dependencies-accessors/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/examples/serialport-test/src-tauri/.gradle/8.3/executionHistory/executionHistory.lock b/examples/serialport-test/src-tauri/.gradle/8.3/executionHistory/executionHistory.lock new file mode 100644 index 0000000..416b633 Binary files /dev/null and b/examples/serialport-test/src-tauri/.gradle/8.3/executionHistory/executionHistory.lock differ diff --git a/examples/serialport-test/src-tauri/.gradle/8.3/fileChanges/last-build.bin b/examples/serialport-test/src-tauri/.gradle/8.3/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/examples/serialport-test/src-tauri/.gradle/8.3/fileChanges/last-build.bin differ diff --git a/examples/serialport-test/src-tauri/.gradle/8.3/fileHashes/fileHashes.lock b/examples/serialport-test/src-tauri/.gradle/8.3/fileHashes/fileHashes.lock new file mode 100644 index 0000000..a96a51e Binary files /dev/null and b/examples/serialport-test/src-tauri/.gradle/8.3/fileHashes/fileHashes.lock differ diff --git a/examples/serialport-test/src-tauri/.gradle/8.3/gc.properties b/examples/serialport-test/src-tauri/.gradle/8.3/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/examples/serialport-test/src-tauri/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/examples/serialport-test/src-tauri/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..a1d5bd8 Binary files /dev/null and b/examples/serialport-test/src-tauri/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/examples/serialport-test/src-tauri/.gradle/buildOutputCleanup/cache.properties b/examples/serialport-test/src-tauri/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..988c881 --- /dev/null +++ b/examples/serialport-test/src-tauri/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Tue Nov 04 12:15:38 IRST 2025 +gradle.version=8.3 diff --git a/examples/serialport-test/src-tauri/.gradle/buildOutputCleanup/outputFiles.bin b/examples/serialport-test/src-tauri/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..4aeeda6 Binary files /dev/null and b/examples/serialport-test/src-tauri/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/examples/serialport-test/src-tauri/.gradle/vcs-1/gc.properties b/examples/serialport-test/src-tauri/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/permissions/schemas/schema.json b/permissions/schemas/schema.json index cd3a456..293a7bc 100644 --- a/permissions/schemas/schema.json +++ b/permissions/schemas/schema.json @@ -775,10 +775,10 @@ "markdownDescription": "Denies the write_rts command without any pre-configured scope." }, { - "description": "# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`", + "description": "# Tauri `serialport` default permissions\r\n\r\nThis configuration file defines the default permissions granted\r\nto the serialport.\r\n\r\n### Granted Permissions\r\n\r\nThis default permission set enables all read-related commands and\r\nallows access to the `$APP` folder and sub directories created in it.\r\nThe location of the `$APP` folder depends on the operating system,\r\nwhere the application is run.\r\n\r\nIn general the `$APP` folder needs to be manually created\r\nby the application at runtime, before accessing files or folders\r\nin it is possible.\r\n\r\n### Denied Permissions\r\n\r\nThis default permission set prevents access to critical components\r\nof the Tauri application by default.\r\nOn Windows the webview data folder access is denied.\r\n\r\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`", "type": "string", "const": "default", - "markdownDescription": "# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`" + "markdownDescription": "# Tauri `serialport` default permissions\r\n\r\nThis configuration file defines the default permissions granted\r\nto the serialport.\r\n\r\n### Granted Permissions\r\n\r\nThis default permission set enables all read-related commands and\r\nallows access to the `$APP` folder and sub directories created in it.\r\nThe location of the `$APP` folder depends on the operating system,\r\nwhere the application is run.\r\n\r\nIn general the `$APP` folder needs to be manually created\r\nby the application at runtime, before accessing files or folders\r\nin it is possible.\r\n\r\n### Denied Permissions\r\n\r\nThis default permission set prevents access to critical components\r\nof the Tauri application by default.\r\nOn Windows the webview data folder access is denied.\r\n\r\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`" } ] } diff --git a/src/mobile_api.rs b/src/mobile_api.rs index 335054a..b7cba9d 100644 --- a/src/mobile_api.rs +++ b/src/mobile_api.rs @@ -33,6 +33,24 @@ struct AvailablePortsResponse { ports: HashMap, } +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct WriteResponse { + bytes_written: usize, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct ReadBinaryResponse { + data: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct ReadResponse { + data: String, +} + impl SerialPort { /// Lists all available serial ports pub fn available_ports(&self) -> Result>, Error> { @@ -166,8 +184,8 @@ impl SerialPort { "value": data, }); - match self.0.run_mobile_plugin("write", params) { - Ok(Value::Number(n)) => Ok(n.as_u64().unwrap_or(0) as usize), + match self.0.run_mobile_plugin::("write", params) { + Ok(WriteResponse { bytes_written }) => Ok(bytes_written), Ok(_) => Err(Error::new("Invalid response format")), Err(e) => Err(Error::new(format!("Plugin error: {}", e))), } @@ -180,8 +198,11 @@ impl SerialPort { "value": data, }); - match self.0.run_mobile_plugin("writeBinary", params) { - Ok(Value::Number(n)) => Ok(n.as_u64().unwrap_or(0) as usize), + match self + .0 + .run_mobile_plugin::("writeBinary", params) + { + Ok(WriteResponse { bytes_written }) => Ok(bytes_written), Ok(_) => Err(Error::new("Invalid response format")), Err(e) => Err(Error::new(format!("Plugin error: {}", e))), } @@ -201,7 +222,7 @@ impl SerialPort { }); match self.0.run_mobile_plugin("read", params) { - Ok(Value::String(data)) => Ok(data), + Ok(ReadResponse { data }) => Ok(data), Ok(_) => Err(Error::new("Invalid response format")), Err(e) => Err(Error::new(format!("Plugin error: {}", e))), } @@ -215,23 +236,13 @@ impl SerialPort { size: Option, ) -> Result, Error> { let params = serde_json::json!({ - "path": path, - "timeout": timeout.unwrap_or(1000), - "size": size.unwrap_or(1024), - }); - - match self.0.run_mobile_plugin("read_binary", params) { - Ok(Value::Array(bytes)) => { - let mut result = Vec::with_capacity(bytes.len()); - for byte in bytes { - if let Some(n) = byte.as_u64() { - result.push(n as u8); - } else { - return Err(Error::new("Invalid byte format")); - } - } - Ok(result) - } + "path": path, + "timeout": timeout.unwrap_or(1000), + "size": size.unwrap_or(1024), + }); + + match self.0.run_mobile_plugin("readBinary", params) { + Ok(ReadBinaryResponse { data }) => Ok(data), Ok(_) => Err(Error::new("Invalid response format")), Err(e) => Err(Error::new(format!("Plugin error: {}", e))), }