sforkowany z mirror/meshtastic-android
USB WIP
rodzic
303fb86aa6
commit
420b718b11
|
|
@ -104,19 +104,6 @@
|
|||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- This is a private service which just does direct communication to the radio -->
|
||||
<!-- <service
|
||||
android:name="com.geeksville.mesh.service.SerialInterfaceService"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/device_filter" />
|
||||
</service> -->
|
||||
|
||||
<activity
|
||||
android:name="com.geeksville.mesh.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
|
|
@ -137,6 +124,8 @@
|
|||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
|
|
@ -145,6 +134,11 @@
|
|||
android:host="www.meshtastic.org"
|
||||
android:pathPrefix="/c/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- The USB devices we want to be informed about -->
|
||||
<meta-data
|
||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/device_filter" />
|
||||
</activity>
|
||||
|
||||
<receiver android:name="com.geeksville.mesh.service.BootCompleteReceiver">
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.usb.UsbDevice
|
||||
import android.hardware.usb.UsbManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
|
@ -427,6 +429,11 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
|
||||
// We now wait for the device to connect, once connected, we ask the user if they want to switch to the new channel
|
||||
}
|
||||
|
||||
if (appLinkAction == UsbManager.ACTION_USB_ACCESSORY_ATTACHED) {
|
||||
val device: UsbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)!!
|
||||
errormsg("Handle USB device attached! $device")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
|||
|
|
@ -6,44 +6,58 @@ import com.geeksville.android.Logging
|
|||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||
import com.hoho.android.usbserial.driver.UsbSerialPort
|
||||
import com.hoho.android.usbserial.driver.UsbSerialProber
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
|
||||
class SerialInterface(private val service: RadioInterfaceService, val address: String) : Logging,
|
||||
IRadioInterface {
|
||||
companion object {
|
||||
companion object : Logging {
|
||||
private const val START1 = 0x94.toByte()
|
||||
private const val START2 = 0xc3.toByte()
|
||||
private const val MAX_TO_FROM_RADIO_SIZE = 512
|
||||
|
||||
fun findDrivers(context: Context): List<UsbSerialDriver> {
|
||||
val manager = context.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
val drivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
|
||||
val devices = drivers.map { it.device }
|
||||
devices.forEach { d ->
|
||||
debug("Found serial port $d")
|
||||
}
|
||||
return drivers
|
||||
}
|
||||
}
|
||||
|
||||
private var uart: UsbSerialPort? = null
|
||||
|
||||
private val manager: UsbManager by lazy {
|
||||
service.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
}
|
||||
private var uart: UsbSerialPort?
|
||||
private lateinit var reader: Thread
|
||||
|
||||
init {
|
||||
val drivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
|
||||
val manager = service.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
val drivers = findDrivers(this)
|
||||
|
||||
// Open a connection to the first available driver.
|
||||
// Open a connection to the first available driver.
|
||||
val driver: UsbSerialDriver = drivers[0]
|
||||
val connection = manager.openDevice(driver.device)
|
||||
val device = drivers[0].device
|
||||
|
||||
info("Opening $device")
|
||||
val connection = manager.openDevice(device)
|
||||
if (connection == null) {
|
||||
// FIXME add UsbManager.requestPermission(driver.getDevice(), ..) handling to activity
|
||||
TODO()
|
||||
// FIXME add UsbManager.requestPermission(device, ..) handling to activity
|
||||
TODO("Need permissions for port")
|
||||
} else {
|
||||
val port = driver.ports[0] // Most devices have just one port (port 0)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleSendToRadio(p: ByteArray) {
|
||||
// This method is called from a continuation and it might show up late, so check for uart being null
|
||||
uart?.apply {
|
||||
val header = ByteArray(4)
|
||||
header[0] = START1
|
||||
|
|
@ -57,7 +71,7 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S
|
|||
|
||||
/** Print device serial debug output somewhere */
|
||||
private fun debugOut(c: Byte) {
|
||||
|
||||
debug("Got c: ${c.toChar()}")
|
||||
}
|
||||
|
||||
private fun readerLoop() {
|
||||
|
|
@ -71,7 +85,7 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S
|
|||
var msb = 0
|
||||
var lsb = 0
|
||||
|
||||
while (true) { // FIXME wait for someone to ask us to exit, and catch continuation exception
|
||||
while (uart != null) { // we run until our port gets closed
|
||||
uart?.apply {
|
||||
read(scratch, 0)
|
||||
val c = scratch[0]
|
||||
|
|
@ -113,7 +127,8 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S
|
|||
}
|
||||
|
||||
override fun close() {
|
||||
uart?.close()
|
||||
debug("Closing serial port")
|
||||
uart?.close() // This will cause the reader thread to exit
|
||||
uart = null
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.geeksville.mesh.ui
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.app.PendingIntent
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothDevice.BOND_BONDED
|
||||
import android.bluetooth.BluetoothDevice.BOND_BONDING
|
||||
|
|
@ -11,6 +12,8 @@ import android.companion.AssociationRequest
|
|||
import android.companion.BluetoothDeviceFilter
|
||||
import android.companion.CompanionDeviceManager
|
||||
import android.content.*
|
||||
import android.hardware.usb.UsbDevice
|
||||
import android.hardware.usb.UsbManager
|
||||
import android.os.Bundle
|
||||
import android.os.ParcelUuid
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -32,6 +35,7 @@ import com.geeksville.mesh.model.UIViewModel
|
|||
import com.geeksville.mesh.service.BluetoothInterface
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.service.RadioInterfaceService
|
||||
import com.geeksville.mesh.service.SerialInterface
|
||||
import com.geeksville.util.anonymize
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
|
@ -105,6 +109,9 @@ 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'
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
|
@ -116,10 +123,11 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
val bluetoothAdapter =
|
||||
(context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter
|
||||
|
||||
private val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
|
||||
var selectedAddress: String? = null
|
||||
val errorText = object : MutableLiveData<String?>(null) {}
|
||||
|
||||
|
||||
private var scanner: BluetoothLeScanner? = null
|
||||
|
||||
/// If this address is for a bluetooth device, return the macaddr portion, else null
|
||||
|
|
@ -231,6 +239,17 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
// Include a placeholder for "None"
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// filter and only accept devices that have a sw update service
|
||||
val filter =
|
||||
ScanFilter.Builder()
|
||||
|
|
@ -279,25 +298,70 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
changeScanSelection(activity, it.address)
|
||||
return true
|
||||
} else {
|
||||
// We ignore missing BT adapters, because it lets us run on the emulator
|
||||
bluetoothAdapter
|
||||
?.getRemoteDevice(it.address)?.let { device ->
|
||||
requestBonding(activity, device) { state ->
|
||||
if (state == BOND_BONDED) {
|
||||
errorText.value = activity.getString(R.string.pairing_completed)
|
||||
changeScanSelection(
|
||||
activity,
|
||||
it.address
|
||||
)
|
||||
} else {
|
||||
errorText.value = activity.getString(R.string.pairing_failed_try_again)
|
||||
}
|
||||
// Handle requestng USB or bluetooth permissions for the device
|
||||
|
||||
// Force the GUI to redraw
|
||||
devices.value = devices.value
|
||||
if (it.isBluetooth) {
|
||||
// Request bonding for bluetooth
|
||||
// We ignore missing BT adapters, because it lets us run on the emulator
|
||||
bluetoothAdapter
|
||||
?.getRemoteDevice(it.address)?.let { device ->
|
||||
requestBonding(activity, device) { state ->
|
||||
if (state == BOND_BONDED) {
|
||||
errorText.value = activity.getString(R.string.pairing_completed)
|
||||
changeScanSelection(
|
||||
activity,
|
||||
it.address
|
||||
)
|
||||
} else {
|
||||
errorText.value =
|
||||
activity.getString(R.string.pairing_failed_try_again)
|
||||
}
|
||||
|
||||
// Force the GUI to redraw
|
||||
devices.value = devices.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (it.isSerial) {
|
||||
val ACTION_USB_PERMISSION = "com.geeksville.mesh.USB_PERMISSION"
|
||||
|
||||
val usbReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (ACTION_USB_PERMISSION == intent.action) {
|
||||
|
||||
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)
|
||||
|
||||
// Force the GUI to redraw
|
||||
devices.value = devices.value
|
||||
}
|
||||
} else {
|
||||
errormsg("USB permission denied for device $device")
|
||||
}
|
||||
}
|
||||
// We don't need to stay registered
|
||||
activity.unregisterReceiver(this)
|
||||
}
|
||||
}
|
||||
|
||||
val permissionIntent =
|
||||
PendingIntent.getBroadcast(activity, 0, Intent(ACTION_USB_PERMISSION), 0)
|
||||
val filter = IntentFilter(ACTION_USB_PERMISSION)
|
||||
activity.registerReceiver(usbReceiver, filter)
|
||||
usbManager.requestPermission(device, permissionIntent)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue