bluetooth better

pull/8/head
geeksville 2020-01-22 16:45:27 -08:00
rodzic 151c9e59bc
commit 397640151f
3 zmienionych plików z 64 dodań i 46 usunięć

Wyświetl plik

@ -1,7 +1,8 @@
* fix bluetooth update
* add real messaging code/protobufs
* use https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#4 to show service state * use https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#4 to show service state
*fix bluetooth * connect to bluetooth device automatically using minimum power
# Medium priority # Medium priority

Wyświetl plik

@ -27,7 +27,6 @@ import androidx.ui.tooling.preview.Preview
import com.geeksville.android.Logging import com.geeksville.android.Logging
class MainActivity : AppCompatActivity(), Logging { class MainActivity : AppCompatActivity(), Logging {
companion object { companion object {
@ -43,13 +42,20 @@ class MainActivity : AppCompatActivity(), Logging {
fun requestPermission() { fun requestPermission() {
debug("Checking permissions") debug("Checking permissions")
val perms = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, val perms = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION,
Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.WAKE_LOCK) Manifest.permission.WAKE_LOCK
)
val missingPerms = perms.filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED } val missingPerms = perms.filter {
ContextCompat.checkSelfPermission(
this,
it
) != PackageManager.PERMISSION_GRANTED
}
if (missingPerms.isNotEmpty()) { if (missingPerms.isNotEmpty()) {
missingPerms.forEach { missingPerms.forEach {
// Permission is not granted // Permission is not granted
@ -73,25 +79,24 @@ class MainActivity : AppCompatActivity(), Logging {
} }
} }
@Preview
@Composable @Composable
fun composeView() { fun composeView() {
MaterialTheme { MaterialTheme {
Column { Column {
Text(text = "MeshUtil Ugly UI", modifier = Spacing(16.dp)) Text(text = "MeshUtil Ugly UI", modifier = Spacing(8.dp))
Button(text = "Start scan", Button(text = "Start scan",
onClick = { onClick = {
if (bluetoothAdapter != null) { if (bluetoothAdapter != null) {
SoftwareUpdateService.enqueueWork(this@MainActivity, SoftwareUpdateService.scanDevicesIntent) SoftwareUpdateService.enqueueWork(
this@MainActivity,
SoftwareUpdateService.scanDevicesIntent
)
} }
}) })
} }
}} }
@Preview
@Composable
fun defaultPreview() {
composeView()
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -102,13 +107,12 @@ class MainActivity : AppCompatActivity(), Logging {
// Ensures Bluetooth is available on the device and it is enabled. If not, // Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth. // displays a dialog requesting user permission to enable Bluetooth.
if(bluetoothAdapter != null) { if (bluetoothAdapter != null) {
bluetoothAdapter!!.takeIf { !it.isEnabled }?.apply { bluetoothAdapter!!.takeIf { !it.isEnabled }?.apply {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT) startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
} }
} } else {
else {
Toast.makeText(this, "Error - this app requires bluetooth", Toast.LENGTH_LONG).show() Toast.makeText(this, "Error - this app requires bluetooth", Toast.LENGTH_LONG).show()
} }

Wyświetl plik

@ -36,27 +36,18 @@ class SoftwareUpdateService : JobIntentService(), Logging {
bluetoothManager.adapter!! bluetoothManager.adapter!!
} }
lateinit var updateGatt: BluetoothGatt // the gatt api used to talk to our device
lateinit var updateService: BluetoothGattService // The service we are currently talking to to do the update
lateinit var totalSizeDesc: BluetoothGattCharacteristic
lateinit var dataDesc: BluetoothGattCharacteristic
lateinit var firmwareStream: InputStream
fun startUpdate() { fun startUpdate() {
totalSizeDesc = updateService.getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER)!! firmwareStream = assets.open("firmware.bin")
firmwareStream = assets.open("firmware.bin") // Start the update by writing the # of bytes in the image
val numBytes = firmwareStream.available()
// Start the update by writing the # of bytes in the image logAssert(totalSizeDesc.setValue(numBytes, BluetoothGattCharacteristic.FORMAT_UINT32, 0))
val numBytes = firmwareStream.available() logAssert(updateGatt.writeCharacteristic(totalSizeDesc))
logAssert(totalSizeDesc.setValue(numBytes, BluetoothGattCharacteristic.FORMAT_UINT32, 0))
logAssert(updateGatt.writeCharacteristic(totalSizeDesc))
logAssert(updateGatt.readCharacteristic(totalSizeDesc))
} }
// Send the next block of our file to the device // Send the next block of our file to the device
fun sendNextBlock() { fun sendNextBlock() {
if(firmwareStream.available() > 0) { if (firmwareStream.available() > 0) {
var blockSize = 512 var blockSize = 512
if (blockSize > firmwareStream.available()) if (blockSize > firmwareStream.available())
@ -66,12 +57,10 @@ class SoftwareUpdateService : JobIntentService(), Logging {
// slightly expensive to keep reallocing this buffer, but whatever // slightly expensive to keep reallocing this buffer, but whatever
logAssert(firmwareStream.read(buffer) == blockSize) logAssert(firmwareStream.read(buffer) == blockSize)
dataDesc = updateService.getCharacteristic(SW_UPDATE_DATA_CHARACTER)!!
// updateGatt.beginReliableWrite() // updateGatt.beginReliableWrite()
dataDesc.value = buffer dataDesc.value = buffer
logAssert(updateGatt.writeCharacteristic(dataDesc)) logAssert(updateGatt.writeCharacteristic(dataDesc))
} } else {
else {
logAssert(false) // fixme logAssert(false) // fixme
} }
} }
@ -99,11 +88,13 @@ class SoftwareUpdateService : JobIntentService(), Logging {
// Various callback methods defined by the BLE API. // Various callback methods defined by the BLE API.
val gattCallback = object : BluetoothGattCallback() { val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange( override fun onConnectionStateChange(
gatt: BluetoothGatt, gatt: BluetoothGatt,
status: Int, status: Int,
newState: Int newState: Int
) { ) {
info("new bluetooth connection state $newState")
//val intentAction: String //val intentAction: String
when (newState) { when (newState) {
BluetoothProfile.STATE_CONNECTED -> { BluetoothProfile.STATE_CONNECTED -> {
@ -130,6 +121,11 @@ class SoftwareUpdateService : JobIntentService(), Logging {
if (service != null) { if (service != null) {
// FIXME instead of slamming in the target device here, instead make it a param for startUpdate // FIXME instead of slamming in the target device here, instead make it a param for startUpdate
updateService = service updateService = service
totalSizeDesc = service.getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER)
dataDesc = service.getCharacteristic(SW_UPDATE_DATA_CHARACTER)
crc32Desc = service.getCharacteristic(SW_UPDATE_CRC32_CHARACTER)
updateResultDesc = service.getCharacteristic(SW_UPDATE_RESULT_CHARACTER)
// FIXME instead of keeping the connection open, make start update just reconnect (needed once user can choose devices) // FIXME instead of keeping the connection open, make start update just reconnect (needed once user can choose devices)
updateGatt = bluetoothGatt updateGatt = bluetoothGatt
enqueueWork(this@SoftwareUpdateService, startUpdateIntent) enqueueWork(this@SoftwareUpdateService, startUpdateIntent)
@ -162,19 +158,24 @@ class SoftwareUpdateService : JobIntentService(), Logging {
) { ) {
logAssert(status == BluetoothGatt.GATT_SUCCESS) logAssert(status == BluetoothGatt.GATT_SUCCESS)
if (characteristic == dataDesc) { if(characteristic == totalSizeDesc) {
// Our write completed, queue up a readback
logAssert(updateGatt.readCharacteristic(totalSizeDesc))
} else if (characteristic == dataDesc) {
enqueueWork(this@SoftwareUpdateService, sendNextBlockIntent) enqueueWork(this@SoftwareUpdateService, sendNextBlockIntent)
} }
} }
} }
bluetoothGatt = result.device.connectGatt(this@SoftwareUpdateService, false, gattCallback)!! bluetoothGatt =
result.device.connectGatt(this@SoftwareUpdateService.applicationContext, true, gattCallback)!!
toast("FISH " + bluetoothGatt) toast("FISH " + bluetoothGatt)
logAssert(bluetoothGatt.discoverServices())
// too early to do this here
// logAssert(bluetoothGatt.discoverServices())
} }
} }
private fun scanLeDevice(enable: Boolean) { private fun scanLeDevice(enable: Boolean) {
when (enable) { when (enable) {
true -> { true -> {
@ -193,11 +194,11 @@ class SoftwareUpdateService : JobIntentService(), Logging {
/* ScanSettings.CALLBACK_TYPE_FIRST_MATCH seems to trigger a bug returning an error of /* ScanSettings.CALLBACK_TYPE_FIRST_MATCH seems to trigger a bug returning an error of
SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES (error #5) SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES (error #5)
*/ */
val settings = ScanSettings.Builder(). val settings =
setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY). ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).
// setMatchMode(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT). // setMatchMode(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT).
// setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH). // setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH).
build() build()
scanner.startScan(listOf(filter), settings, scanCallback) scanner.startScan(listOf(filter), settings, scanCallback)
} }
else -> { else -> {
@ -216,14 +217,15 @@ class SoftwareUpdateService : JobIntentService(), Logging {
} }
toast("Executing: $label") toast("Executing: $label")
when(intent.action) { when (intent.action) {
scanDevicesIntent.action -> scanLeDevice(true) scanDevicesIntent.action -> scanLeDevice(true)
startUpdateIntent.action -> startUpdate() startUpdateIntent.action -> startUpdate()
sendNextBlockIntent.action -> sendNextBlock() sendNextBlockIntent.action -> sendNextBlock()
else -> logAssert(false) else -> logAssert(false)
} }
info("Completed service @ " + SystemClock.elapsedRealtime() info(
"Completed service @ " + SystemClock.elapsedRealtime()
) )
} }
@ -269,6 +271,17 @@ class SoftwareUpdateService : JobIntentService(), Logging {
private val SW_UPDATE_RESULT_CHARACTER = private val SW_UPDATE_RESULT_CHARACTER =
UUID.fromString("5e134862-7411-4424-ac4a-210937432c77") // read|notify result code, readable but will notify when the OTA operation completes UUID.fromString("5e134862-7411-4424-ac4a-210937432c77") // read|notify result code, readable but will notify when the OTA operation completes
// FIXME - this is state that really more properly goes with the serice instance, but
// it can go away if our work queue gets empty. So we keep it here instead. Not sure
// if there is a better approach?
lateinit var updateGatt: BluetoothGatt // the gatt api used to talk to our device
lateinit var updateService: BluetoothGattService // The service we are currently talking to to do the update
lateinit var totalSizeDesc: BluetoothGattCharacteristic
lateinit var dataDesc: BluetoothGattCharacteristic
lateinit var crc32Desc: BluetoothGattCharacteristic
lateinit var updateResultDesc: BluetoothGattCharacteristic
lateinit var firmwareStream: InputStream
/** /**
* Convenience method for enqueuing work in to this service. * Convenience method for enqueuing work in to this service.
*/ */