move bt code to service

1.2-legacy
geeksville 2020-01-21 10:39:01 -08:00
rodzic 62ee9a43ed
commit 6fd57c8722
3 zmienionych plików z 166 dodań i 144 usunięć

Wyświetl plik

@ -2,6 +2,7 @@
# Medium priority
* add crash reporting
* remove example code boilerplate from the service
* add analytics (make them optional)

Wyświetl plik

@ -19,142 +19,13 @@ class MainActivity : AppCompatActivity() {
companion object {
const val REQUEST_ENABLE_BT = 10
private const val SCAN_PERIOD: Long = 10000
const val ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"
const val ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"
private const val STATE_DISCONNECTED = 0
private const val STATE_CONNECTING = 1
private const val STATE_CONNECTED = 2
private val TAG = MainActivity::class.java.simpleName // FIXME - use my logging class instead
private val SW_UPDATE_UUID = UUID.fromString("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30")
private val SW_UPDATE_TOTALSIZE_CHARACTER = UUID.fromString("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e") // write|read total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted)
private val SW_UPDATE_DATA_CHARACTER = UUID.fromString("e272ebac-d463-4b98-bc84-5cc1a39ee517") // write data, variable sized, recommended 512 bytes, write one for each block of file
private val SW_UPDATE_CRC32_CHARACTER = UUID.fromString("4826129c-c22a-43a3-b066-ce8f0d5bacc6") // write crc32, write last - writing this will complete the OTA operation, now you can read result
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
}
private val BluetoothAdapter.isDisabled: Boolean
get() = !isEnabled
private val bluetoothAdapter: BluetoothAdapter by lazy(LazyThreadSafetyMode.NONE) {
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothManager.adapter!!
}
private var mScanning: Boolean = false
private val handler = Handler()
private val leScanCallback = BluetoothAdapter.LeScanCallback { device, _, _ ->
runOnUiThread {
/*
leDeviceListAdapter.addDevice(device)
leDeviceListAdapter.notifyDataSetChanged()
*/
lateinit var bluetoothGatt: BluetoothGatt
//var connectionState = STATE_DISCONNECTED
lateinit var totalSizeDesc: BluetoothGattCharacteristic
// Send the next block of our file to the device
fun sendNextBlock() {
}
// Various callback methods defined by the BLE API.
val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt,
status: Int,
newState: Int
) {
//val intentAction: String
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
//intentAction = ACTION_GATT_CONNECTED
//connectionState = STATE_CONNECTED
// broadcastUpdate(intentAction)
Log.i(TAG, "Connected to GATT server.")
Log.i(TAG, "Attempting to start service discovery: " +
bluetoothGatt.discoverServices())
}
BluetoothProfile.STATE_DISCONNECTED -> {
//intentAction = ACTION_GATT_DISCONNECTED
//connectionState = STATE_DISCONNECTED
Log.i(TAG, "Disconnected from GATT server.")
// broadcastUpdate(intentAction)
}
}
}
// New services discovered
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
// broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED)
val updateService = gatt.services.find { it.uuid == SW_UPDATE_UUID }
if(updateService != null) {
// Start the update by writing the # of bytes in the image
val numBytes = 45
totalSizeDesc = updateService.getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER)!!
assert(totalSizeDesc.setValue(numBytes, BluetoothGattCharacteristic.FORMAT_UINT32, 0))
assert(bluetoothGatt.writeCharacteristic(totalSizeDesc))
assert(bluetoothGatt.readCharacteristic(totalSizeDesc))
}
}
else -> Log.w(TAG, "onServicesDiscovered received: $status")
}
}
// Result of a characteristic read operation
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
assert(status == BluetoothGatt.GATT_SUCCESS)
if(characteristic == totalSizeDesc) {
// Our read of this has completed, either fail or continue updating
val readvalue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0)
assert(readvalue != 0) // FIXME - handle this case
sendNextBlock() // FIXME, call this in a job queue of the service
}
// broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic)
}
}
bluetoothGatt = device.connectGatt(this, false, gattCallback)!!
}
}
private fun scanLeDevice(enable: Boolean) {
when (enable) {
true -> {
// Stops scanning after a pre-defined scan period.
handler.postDelayed({
mScanning = false
bluetoothAdapter.stopLeScan(leScanCallback)
}, SCAN_PERIOD)
mScanning = true
bluetoothAdapter.startLeScan(leScanCallback)
}
else -> {
mScanning = false
bluetoothAdapter.stopLeScan(leScanCallback)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@ -167,7 +38,7 @@ class MainActivity : AppCompatActivity() {
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
bluetoothAdapter.takeIf { it.isDisabled }?.apply {
bluetoothAdapter.takeIf { !it.isEnabled }?.apply {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}

Wyświetl plik

@ -1,18 +1,154 @@
package com.geeksville.meshutil
import android.bluetooth.*
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.SystemClock
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.JobIntentService
import java.util.*
/**
* Example implementation of a JobIntentService.
* typical flow
*
* startScan
* startUpdate
* sendNextBlock
* finishUpdate
*
* stopScan
*
* FIXME - if we don't find a device stop our scan
* FIXME - broadcast when we found devices, made progress sending blocks or when the update is complete
* FIXME - make the user decide to start an update on a particular device
*/
class SimpleJobIntentService : JobIntentService() {
class SoftwareUpdateService : JobIntentService() {
private val bluetoothAdapter: BluetoothAdapter by lazy(LazyThreadSafetyMode.NONE) {
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
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
fun startUpdate() {
if (updateService != null) {
totalSizeDesc = updateService.getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER)!!
// Start the update by writing the # of bytes in the image
val numBytes = 45
assert(totalSizeDesc.setValue(numBytes, BluetoothGattCharacteristic.FORMAT_UINT32, 0))
assert(updateGatt.writeCharacteristic(totalSizeDesc))
assert(updateGatt.readCharacteristic(totalSizeDesc))
}
}
// Send the next block of our file to the device
fun sendNextBlock() {
}
// For each device that appears in our scan, ask for its GATT, when the gatt arrives,
// check if it is an eligable device and store it in our list of candidates
// if that device later disconnects remove it as a candidate
private val leScanCallback = BluetoothAdapter.LeScanCallback { device, _, _ ->
lateinit var bluetoothGatt: BluetoothGatt // late init so we can declare our callback and use this there
//var connectionState = STATE_DISCONNECTED
// Various callback methods defined by the BLE API.
val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt,
status: Int,
newState: Int
) {
//val intentAction: String
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
//intentAction = ACTION_GATT_CONNECTED
//connectionState = STATE_CONNECTED
// broadcastUpdate(intentAction)
Log.i(AppCompatActivity.TAG, "Connected to GATT server.")
Log.i(
AppCompatActivity.TAG, "Attempting to start service discovery: "
)
assert(bluetoothGatt.discoverServices())
}
BluetoothProfile.STATE_DISCONNECTED -> {
//intentAction = ACTION_GATT_DISCONNECTED
//connectionState = STATE_DISCONNECTED
Log.i(AppCompatActivity.TAG, "Disconnected from GATT server.")
// broadcastUpdate(intentAction)
}
}
}
// New services discovered
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
assert(status == BluetoothGatt.GATT_SUCCESS)
// broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED)
val service = gatt.services.find { it.uuid == SW_UPDATE_UUID }
if (service != null) {
// FIXME instead of slamming in the target device here, instead make it a param for startUpdate
updateService = service
// FIXME instead of keeping the connection open, make start update just reconnect (needed once user can choose devices)
updateGatt = bluetoothGatt
enqueueWork(this@SoftwareUpdateService, startUpdateIntent)
} else {
// drop our connection - we don't care about this device
bluetoothGatt.disconnect()
}
}
// Result of a characteristic read operation
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
assert(status == BluetoothGatt.GATT_SUCCESS)
if (characteristic == totalSizeDesc) {
// Our read of this has completed, either fail or continue updating
val readvalue =
characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0)
assert(readvalue != 0) // FIXME - handle this case
enqueueWork(this@SoftwareUpdateService, sendNextBlockIntent)
}
// broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic)
}
}
bluetoothGatt = device.connectGatt(this, false, gattCallback)!!
}
private fun scanLeDevice(enable: Boolean) {
when (enable) {
true -> {
// Stops scanning after a pre-defined scan period.
/* handler.postDelayed({
mScanning = false
bluetoothAdapter.stopLeScan(leScanCallback)
}, SCAN_PERIOD)
mScanning = true */
assert(bluetoothAdapter.startLeScan(leScanCallback))
}
else -> {
// mScanning = false
bluetoothAdapter.stopLeScan(leScanCallback)
}
}
}
override fun onHandleWork(intent: Intent) { // We have received work to do. The system or framework is already
// holding a wake lock for us at this point, so we can just go.
Log.i("SimpleJobIntentService", "Executing work: $intent")
@ -21,16 +157,7 @@ class SimpleJobIntentService : JobIntentService() {
label = intent.toString()
}
toast("Executing: $label")
for (i in 0..4) {
Log.i(
"SimpleJobIntentService", "Running service " + (i + 1)
+ "/5 @ " + SystemClock.elapsedRealtime()
)
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
}
}
Log.i(
"SimpleJobIntentService",
"Completed service @ " + SystemClock.elapsedRealtime()
@ -46,7 +173,7 @@ class SimpleJobIntentService : JobIntentService() {
// Helper for showing tests
fun toast(text: CharSequence?) {
mHandler.post {
Toast.makeText(this@SimpleJobIntentService, text, Toast.LENGTH_SHORT).show()
Toast.makeText(this@SoftwareUpdateService, text, Toast.LENGTH_SHORT).show()
}
}
@ -56,13 +183,36 @@ class SimpleJobIntentService : JobIntentService() {
*/
const val JOB_ID = 1000
val scanDevicesIntent = Intent("com.geeksville.meshutil.SCAN_DEVICES")
val startUpdateIntent = Intent("com.geeksville.meshutil.START_UPDATE")
private val sendNextBlockIntent = Intent("com.geeksville.meshutil.SEND_NEXT_BLOCK")
private val finishUpdateIntent = Intent("com.geeksville.meshutil.FINISH_UPDATE")
private const val SCAN_PERIOD: Long = 10000
//const val ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"
//const val ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"
private val TAG =
MainActivity::class.java.simpleName // FIXME - use my logging class instead
private val SW_UPDATE_UUID = UUID.fromString("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30")
private val SW_UPDATE_TOTALSIZE_CHARACTER =
UUID.fromString("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e") // write|read total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted)
private val SW_UPDATE_DATA_CHARACTER =
UUID.fromString("e272ebac-d463-4b98-bc84-5cc1a39ee517") // write data, variable sized, recommended 512 bytes, write one for each block of file
private val SW_UPDATE_CRC32_CHARACTER =
UUID.fromString("4826129c-c22a-43a3-b066-ce8f0d5bacc6") // write crc32, write last - writing this will complete the OTA operation, now you can read result
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
/**
* Convenience method for enqueuing work in to this service.
*/
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(
context,
SimpleJobIntentService::class.java, JOB_ID, work
SoftwareUpdateService::class.java, JOB_ID, work
)
}
}