From 3f31daa7bc321afbcce29c1c870f980798fa5ca1 Mon Sep 17 00:00:00 2001 From: Mark Haslinghuis Date: Mon, 27 Oct 2025 23:48:24 +0100 Subject: [PATCH 1/3] Tauri: unify serial implementation --- src-tauri/Cargo.lock | 5 +- src-tauri/Cargo.toml | 11 ++- src-tauri/src/main.rs | 22 +----- src-tauri/src/serial_android.rs | 120 -------------------------------- 4 files changed, 9 insertions(+), 149 deletions(-) delete mode 100644 src-tauri/src/serial_android.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ffc2c16029..408936795f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -100,7 +100,6 @@ version = "2025.12.0" dependencies = [ "serde", "serde_json", - "serialport", "tauri", "tauri-build", "tauri-plugin-serialplugin", @@ -3497,9 +3496,9 @@ dependencies = [ [[package]] name = "tauri-plugin-serialplugin" -version = "2.21.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a23ab6d07a643246533ac7ada2921a8d7a858c3245913019ef56d3f4093761" +checksum = "df85d754452000858016ef70a4209880292a588464f3dae585138442a7ab366a" dependencies = [ "serde", "serde_json", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9a57e3c3c2..c44880ef84 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -18,15 +18,12 @@ tauri-build = { version = "2.5", features = [] } [dependencies] tauri = { version = "2.9", features = [] } tauri-plugin-shell = "2.3" -# Allow newer serial plugin releases (2.21+) which may include Android fixes serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" - -[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] -tauri-plugin-serialplugin = "2.21" - -[target.'cfg(target_os = "android")'.dependencies] -serialport = "4.8" +# Pin exact version to avoid pulling 2.21.x which is incompatible with current Android/Kotlin setup +# Try an older plugin version that matches tauri-android 2.9 API surface (avoid onDetach override) +# Further downgrade to avoid onDetach override incompatibility +tauri-plugin-serialplugin = "=2.15.0" [features] default = ["custom-protocol"] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index fbecbd774f..fc5a1bb2d6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,25 +4,9 @@ )] fn main() { - let mut builder = tauri::Builder::default() - .plugin(tauri_plugin_shell::init()); - - #[cfg(target_os = "android")] - { - // Local Android serial plugin that mirrors the commands used by the frontend - builder = builder.plugin(crate::serial_android::init_android()); - } - - #[cfg(not(target_os = "android"))] - { - // Desktop: use official plugin for now - builder = builder.plugin(tauri_plugin_serialplugin::init()); - } - - builder + tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_serialplugin::init()) .run(tauri::generate_context!()) .expect("error while running tauri application"); } - -#[cfg(target_os = "android")] -mod serial_android; diff --git a/src-tauri/src/serial_android.rs b/src-tauri/src/serial_android.rs deleted file mode 100644 index 629748c607..0000000000 --- a/src-tauri/src/serial_android.rs +++ /dev/null @@ -1,120 +0,0 @@ -#[cfg(target_os = "android")] -use std::{collections::HashMap, io::{Read, Write}, sync::Mutex, time::Duration}; - -#[cfg(target_os = "android")] -use tauri::{plugin::Builder as PluginBuilder, Manager, Runtime, State}; - -#[cfg(target_os = "android")] -use serialport::{SerialPort, SerialPortType}; - -#[cfg(target_os = "android")] -type PortMap = Mutex>; - -#[cfg(target_os = "android")] -struct PortEntry { - port: Box, -} - -#[cfg(target_os = "android")] -#[derive(Debug, serde::Serialize, serde::Deserialize)] -struct OpenOptions { - path: String, - #[serde(default = "default_baud")] - baudRate: u32, -} - -#[cfg(target_os = "android")] -fn default_baud() -> u32 { 115_200 } - -#[cfg(target_os = "android")] -#[tauri::command] -fn available_ports_android() -> Result { - // Android: serialport enumeration is limited. Probe common device nodes. - let candidates = ["/dev/ttyACM0", "/dev/ttyUSB0"]; - let mut map = serde_json::Map::new(); - for path in candidates { - if std::fs::metadata(path).is_ok() { - // Provide fake but plausible VID/PID so UI filter accepts it. - // STM32 VCP: VID 1155 (0x0483), PID 22336 (0x57C0) - map.insert(path.to_string(), serde_json::json!({ - "vid": 1155, - "pid": 22336, - "serial_number": serde_json::Value::Null - })); - } - } - Ok(serde_json::Value::Object(map)) -} - -#[cfg(target_os = "android")] -#[tauri::command] -fn open_android(state: State<'_, PortMap>, opts: OpenOptions) -> Result { - let port = serialport::new(&opts.path, opts.baudRate) - .timeout(Duration::from_millis(100)) - .open() - .map_err(|e| format!("failed to open: {e}"))?; - let mut map = state.lock().unwrap(); - map.insert(opts.path, PortEntry { port }); - Ok(true) -} - -#[cfg(target_os = "android")] -#[tauri::command] -fn set_timeout_android(state: State<'_, PortMap>, path: String, timeout: u64) -> Result { - let mut map = state.lock().unwrap(); - let entry = map.get_mut(&path).ok_or_else(|| "port not open".to_string())?; - entry.port.set_timeout(Duration::from_millis(timeout)).map_err(|e| e.to_string())?; - Ok(true) -} - -#[cfg(target_os = "android")] -#[tauri::command] -fn read_binary_android(state: State<'_, PortMap>, path: String, size: usize, timeout: Option) -> Result, String> { - let mut map = state.lock().unwrap(); - let entry = map.get_mut(&path).ok_or_else(|| "port not open".to_string())?; - if let Some(ms) = timeout { let _ = entry.port.set_timeout(Duration::from_millis(ms)); } - let mut buf = vec![0u8; size.max(1)]; - match entry.port.read(buf.as_mut_slice()) { - Ok(n) if n > 0 => { buf.truncate(n); Ok(buf) }, - Ok(_n) => Err("no data received".to_string()), - Err(e) => { - let msg = e.to_string(); - if msg.to_lowercase().contains("timed out") { Err("no data received".to_string()) } else { Err(msg) } - } - } -} - -#[cfg(target_os = "android")] -#[tauri::command] -fn write_binary_android(state: State<'_, PortMap>, path: String, value: Vec) -> Result { - let mut map = state.lock().unwrap(); - let entry = map.get_mut(&path).ok_or_else(|| "port not open".to_string())?; - entry.port.write_all(&value).map_err(|e| e.to_string())?; - Ok(value.len()) -} - -#[cfg(target_os = "android")] -#[tauri::command] -fn close_android(state: State<'_, PortMap>, path: String) -> Result { - let mut map = state.lock().unwrap(); - map.remove(&path); - Ok(true) -} - -#[cfg(target_os = "android")] -pub fn init_android() -> tauri::plugin::TauriPlugin { - PluginBuilder::new("serialplugin") - .setup(|app, _api| { - app.manage(Mutex::new(HashMap::::new())); - Ok(()) - }) - .invoke_handler(tauri::generate_handler![ - available_ports_android, - open_android, - set_timeout_android, - read_binary_android, - write_binary_android, - close_android - ]) - .build() -} From 550e5a1849abcd43597d4c68d26b96d1978ffbd4 Mon Sep 17 00:00:00 2001 From: Mark Haslinghuis Date: Tue, 28 Oct 2025 01:33:34 +0100 Subject: [PATCH 2/3] Update release.yml --- .github/workflows/release.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 20aa735289..8686dcbcb7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,7 @@ jobs: retention-days: 30 android: - name: Android APK (Capacitor) + name: Android APK (Tauri) runs-on: ubuntu-latest steps: - name: Checkout @@ -79,11 +79,21 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v3 - - name: Setup Java 11 + - name: Setup Java 21 uses: actions/setup-java@v5 with: distribution: temurin - java-version: '11' + java-version: '21' + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + with: + workspaces: src-tauri - name: Cache Gradle uses: actions/cache@v4 @@ -97,7 +107,7 @@ jobs: - name: Build Android release (unsigned) run: | - yarn android:release + yarn tauri:build:android - name: Setup release keystore (if available) if: ${{ secrets.ANDROID_KEYSTORE_BASE64 != '' }} From ddbe6a58c8dd86c9d78558031e2c75fb83c425ea Mon Sep 17 00:00:00 2001 From: Mark Haslinghuis Date: Tue, 28 Oct 2025 01:54:37 +0100 Subject: [PATCH 3/3] Update yml --- .github/workflows/ci.yml | 16 ++++++++++++++-- .github/workflows/release.yml | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90eabfbc31..c17eaae962 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,11 +126,11 @@ jobs: - name: Install dependencies run: yarn install --frozen-lockfile - - name: Setup Java 17 + - name: Setup Java 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' + java-version: '21' - name: Setup Android SDK uses: android-actions/setup-android@v3 @@ -151,6 +151,18 @@ jobs: - name: Initialize Tauri Android project run: yarn tauri android init --ci + - name: Ensure JitPack repository (for usb-serial-for-android) + shell: bash + run: | + set -euo pipefail + FILE="src-tauri/gen/android/build.gradle.kts" + echo "Ensuring JitPack repository is present in $FILE" + if ! grep -q 'jitpack.io' "$FILE"; then + printf '\nallprojects {\n repositories {\n maven(url = "https://jitpack.io")\n }\n}\n' >> "$FILE" + fi + echo "Repositories block in $FILE now contains:" + grep -n "jitpack.io" "$FILE" || true + - name: Build Tauri Android APK uses: tauri-apps/tauri-action@v0 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8686dcbcb7..5ffca3ac85 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -105,6 +105,21 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- + - name: Ensure JitPack repository (for usb-serial-for-android) + shell: bash + run: | + set -euo pipefail + FILE="src-tauri/gen/android/build.gradle.kts" + if [ -f "$FILE" ]; then + echo "Ensuring JitPack repository is present in $FILE" + if ! grep -q 'jitpack.io' "$FILE"; then + printf '\nallprojects {\n repositories {\n maven(url = "https://jitpack.io")\n }\n}\n' >> "$FILE" + fi + grep -n "jitpack.io" "$FILE" || true + else + echo "Warning: $FILE not found (will be generated by Tauri on first build)." + fi + - name: Build Android release (unsigned) run: | yarn tauri:build:android