kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
bluetooth better
rodzic
151c9e59bc
commit
397640151f
5
TODO.md
5
TODO.md
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
Ładowanie…
Reference in New Issue