From 0c8e2ca1acbc46fd530a661a00c39f98a255aab4 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 8 Jun 2020 18:24:36 -0700 Subject: [PATCH] USB device port support WIP https://github.com/meshtastic/Meshtastic-Android/issues/38 --- .../mesh/service/RadioInterfaceService.kt | 1 + .../mesh/service/SerialInterface.kt | 127 +++++++++++------- .../geeksville/mesh/ui/SettingsFragment.kt | 43 +++--- 3 files changed, 104 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt index 2be3458de..e2c693380 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -85,6 +85,7 @@ class RadioInterfaceService : Service(), Logging { val rest = address.substring(1) val isValid = when (c) { 'x' -> BluetoothInterface.addressValid(context, rest) + 'u' -> SerialInterface.addressValid(context, rest) else -> true } if (!isValid) diff --git a/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt b/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt index 804c2b964..ae303beb8 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt @@ -21,38 +21,62 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S val drivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager) val devices = drivers.map { it.device } devices.forEach { d -> - debug("Found serial port $d") + debug("Found serial port ${d.deviceName}") } return drivers } + + fun addressValid(context: Context, rest: String): Boolean { + findSerial(context, rest)?.let { d -> + val manager = context.getSystemService(Context.USB_SERVICE) as UsbManager + return manager.hasPermission(d.device) + } + return false + } + + fun findSerial(context: Context, rest: String): UsbSerialDriver? { + val drivers = findDrivers(context) + + return if (drivers.isEmpty()) + null + else // Open a connection to the first available driver. + drivers[0] // FIXME, instead we should find by name + } } - private var uart: UsbSerialPort? + private var uart: UsbSerialPort? = null private lateinit var reader: Thread init { val manager = service.getSystemService(Context.USB_SERVICE) as UsbManager - val drivers = findDrivers(this) - // Open a connection to the first available driver. - val device = drivers[0].device + val device = findSerial(service, address) - info("Opening $device") - val connection = manager.openDevice(device) - if (connection == null) { - // FIXME add UsbManager.requestPermission(device, ..) handling to activity - TODO("Need permissions for port") + if (device != null) { + info("Opening $device") + val connection = manager.openDevice(device.device) + if (connection == null) { + // FIXME add UsbManager.requestPermission(device, ..) handling to activity + TODO("Need permissions for port") + } else { + val port = device.ports[0] // Most devices have just one port (port 0) + + port.open(connection) + port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE) + uart = port + + debug("Starting serial reader thread") + // FIXME, start reading thread + reader = + thread( + start = true, + isDaemon = true, + name = "serial reader", + block = ::readerLoop + ) + } } else { - val port = drivers[0].ports[0] // Most devices have just one port (port 0) - - port.open(connection) - port.setParameters(921600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE) - uart = port - - debug("Starting serial reader thread") - // FIXME, start reading thread - reader = - thread(start = true, isDaemon = true, name = "serial reader", block = ::readerLoop) + errormsg("Can't find device") } } @@ -85,40 +109,49 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S var msb = 0 var lsb = 0 + val timeout = (60 * 1000) + while (uart != null) { // we run until our port gets closed uart?.apply { - read(scratch, 0) - val c = scratch[0] + val numRead = read(scratch, timeout) + if (numRead == 0) + errormsg("Read returned zero bytes") + else { + val c = scratch[0] - // Assume we will be advancing our pointer - var nextPtr = ptr + 1 + // Assume we will be advancing our pointer + var nextPtr = ptr + 1 - when (ptr) { - 0 -> // looking for START1 - if (c != START1) { - debugOut(c) - nextPtr = 0 // Restart from scratch + when (ptr) { + 0 -> // looking for START1 + if (c != START1) { + debugOut(c) + nextPtr = 0 // Restart from scratch + } + 1 -> // Looking for START2 + if (c != START2) + nextPtr = 0 // Restart from scratch + 2 -> // Looking for MSB of our 16 bit length + msb = c.toInt() and 0xff + 3 -> // Looking for LSB of our 16 bit length + lsb = c.toInt() and 0xff + else -> { // We've read our header, do one big read for the packet itself + val packetLen = (msb shl 8) or lsb + + // If packet len is too long, the bytes must have been corrupted, start looking for START1 again + if (packetLen <= MAX_TO_FROM_RADIO_SIZE) { + val buf = ByteArray(packetLen) + val numRead = read(buf, timeout) + if (numRead < packetLen) + errormsg("Packet read was too short") + else + service.handleFromRadio(buf) + } + nextPtr = 0 // Start parsing the next packet } - 1 -> // Looking for START2 - if (c != START2) - nextPtr = 0 // Restart from scratch - 2 -> // Looking for MSB of our 16 bit length - msb = c.toInt() and 0xff - 3 -> // Looking for LSB of our 16 bit length - lsb = c.toInt() and 0xff - else -> { // We've read our header, do one big read for the packet itself - val packetLen = (msb shl 8) or lsb - - // If packet len is too long, the bytes must have been corrupted, start looking for START1 again - if (packetLen <= MAX_TO_FROM_RADIO_SIZE) { - val buf = ByteArray(packetLen) - read(buf, 0) - service.handleFromRadio(buf) - } - nextPtr = 0 // Start parsing the next packet } + ptr = nextPtr } - ptr = nextPtr } } } catch (ex: Exception) { diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index 9d1f03d3d..bc14d4433 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -39,6 +39,7 @@ import com.geeksville.mesh.service.SerialInterface import com.geeksville.util.anonymize import com.geeksville.util.exceptionReporter import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.hoho.android.usbserial.driver.UsbSerialDriver import kotlinx.android.synthetic.main.settings_fragment.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -103,7 +104,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { debug("BTScanModel created") } - data class DeviceListEntry(val name: String, val address: String, val bonded: Boolean) { + open class DeviceListEntry(val name: String, val address: String, val bonded: Boolean) { val bluetoothAddress get() = if (address[0] == 'x') @@ -115,11 +116,17 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { override fun toString(): String { return "DeviceListEntry(name=${name.anonymize}, addr=${address.anonymize})" } - - val isBluetooth: Boolean get() = name[0] == 'x' - val isSerial: Boolean get() = name[0] == 's' + + val isBluetooth: Boolean get() = address[0] == 'x' + val isSerial: Boolean get() = address[0] == 's' } + class USBDeviceListEntry(usbManager: UsbManager, val usb: UsbSerialDriver) : DeviceListEntry( + usb.device.deviceName, + "s${usb.device.deviceName}", + usbManager.hasPermission(usb.device) + ) + override fun onCleared() { super.onCleared() debug("BTScanModel cleared") @@ -248,17 +255,12 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { addDevice(DeviceListEntry(context.getString(R.string.none), "n", true)) SerialInterface.findDrivers(context).forEach { d -> - val hasPerms = usbManager.hasPermission(d.device) addDevice( - DeviceListEntry( - d.device.deviceName, - "s${d.device.deviceName}", - hasPerms - ) + USBDeviceListEntry(usbManager, d) ) } - // filter and only accept devices that have a sw update service + // filter and only accept devices that have our service val filter = ScanFilter.Builder() .setServiceUuid(ParcelUuid(BluetoothInterface.BTM_SERVICE_UUID)) @@ -307,6 +309,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { return true } else { // Handle requestng USB or bluetooth permissions for the device + debug("Requesting permissions for the device") if (it.isBluetooth) { // Request bonding for bluetooth @@ -332,6 +335,8 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { } if (it.isSerial) { + it as USBDeviceListEntry + val ACTION_USB_PERMISSION = "com.geeksville.mesh.USB_PERMISSION" val usbReceiver = object : BroadcastReceiver() { @@ -339,21 +344,19 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { override fun onReceive(context: Context, intent: Intent) { if (ACTION_USB_PERMISSION == intent.action) { - val device: UsbDevice? = - intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) + val device: UsbDevice = + intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)!! if (intent.getBooleanExtra( UsbManager.EXTRA_PERMISSION_GRANTED, false ) ) { - device?.apply { - info("User approved USB access") - changeScanSelection(activity, it.address) + info("User approved USB access") + changeScanSelection(activity, it.address) - // Force the GUI to redraw - devices.value = devices.value - } + // Force the GUI to redraw + devices.value = devices.value } else { errormsg("USB permission denied for device $device") } @@ -367,7 +370,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { PendingIntent.getBroadcast(activity, 0, Intent(ACTION_USB_PERMISSION), 0) val filter = IntentFilter(ACTION_USB_PERMISSION) activity.registerReceiver(usbReceiver, filter) - usbManager.requestPermission(device, permissionIntent) + usbManager.requestPermission(it.usb.device, permissionIntent) } return false