~hokiegeek/kiddoecomm

35c2327ffd67865e71b806e8ecd4adb901266ad2 — HokieGeek 1 year, 30 days ago a45a618 master
Kinda getting notified of button presses
M app/src/main/java/net/hokiegeek/kiddoecomm/IMicroBitButtonStateListener.kt => app/src/main/java/net/hokiegeek/kiddoecomm/IMicroBitButtonStateListener.kt +3 -4
@@ 1,7 1,6 @@
package net.hokiegeek.kiddoecomm

interface IMicroBitNotificationListener {
    fun OnButton1StateReceived()
    fun OnButton2StateReceived()
    fun OnTemperatureReceived(temperature: Int)
interface IMicroBitButtonStateListener {
    fun OnMicroBitButton1StateReceived(state: Int)
    fun OnMicroBitButton2StateReceived(state: Int)
}
\ No newline at end of file

M app/src/main/java/net/hokiegeek/kiddoecomm/IMicroBitTemperatureListener.kt => app/src/main/java/net/hokiegeek/kiddoecomm/IMicroBitTemperatureListener.kt +2 -4
@@ 1,7 1,5 @@
package net.hokiegeek.kiddoecomm

interface IMicroBitNotificationListener {
    fun OnButton1StateReceived()
    fun OnButton2StateReceived()
    fun OnTemperatureReceived(temperature: Int)
interface IMicroBitTemperatureListener {
    fun OnMicroBitTemperatureReceived(temperature: Int)
}
\ No newline at end of file

M app/src/main/java/net/hokiegeek/kiddoecomm/MainActivity.kt => app/src/main/java/net/hokiegeek/kiddoecomm/MainActivity.kt +35 -5
@@ 4,13 4,18 @@ import android.app.Activity
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.content.*
import android.content.pm.PackageManager
import android.location.LocationManager
import android.location.LocationProvider
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.forEach
import androidx.navigation.NavGraph
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController


@@ 24,6 29,7 @@ class MainActivity : IMicroBitHolder, INewBondedDeviceDealer, AppCompatActivity(
    private val logTag = "KiddoeComm::MainActivity"

    private val requestEnableBT = 3
    private val requestEnableLocation = 4

    // private var mBTAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter() // get a handle on the bluetooth radio
    private var mBTLEService: BTLEService? = null


@@ 71,7 77,7 @@ class MainActivity : IMicroBitHolder, INewBondedDeviceDealer, AppCompatActivity(
        } else {
            Log.d(logTag, "Starting with Messaging fragment")
            graph.startDestination = R.id.navigation_messaging
            // mBottomNav?.menu?.forEach { it.isEnabled = true }
            mBottomNav?.menu?.forEach { it.isEnabled = true }
        }
        navController.graph = graph
    }


@@ 107,6 113,16 @@ class MainActivity : IMicroBitHolder, INewBondedDeviceDealer, AppCompatActivity(
                }
            }.start()
        }

        /*
        if (ContextCompat.checkSelfPermission(applicationContext,
                Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_DENIED)
        {
            requestPermissions(
                arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION),
                requestEnableLocation)
        }
        */
    }

    override fun onPause() {


@@ 132,7 148,7 @@ class MainActivity : IMicroBitHolder, INewBondedDeviceDealer, AppCompatActivity(
                    bindBTLEService()
                } else {
                    // User did not enable Bluetooth or an error occurred
                    Log.d(logTag, "BT not enabled")
                    Log.e(logTag, "BT not enabled")
                    /*
                    val activity: FragmentActivity = getActivity()
                    if (activity != null) {


@@ 144,6 160,13 @@ class MainActivity : IMicroBitHolder, INewBondedDeviceDealer, AppCompatActivity(
                    }
                    */
                }
            requestEnableLocation ->
                if (resultCode == Activity.RESULT_OK) {
                    Log.d(logTag, "yup")
                } else {
                    // User did not enable location perms or an error occurred
                    Log.e(logTag, "Coarse and Fine location not enabled")
                }
        }
    }



@@ 294,13 317,16 @@ class MainActivity : IMicroBitHolder, INewBondedDeviceDealer, AppCompatActivity(
                BTLEService.ACTION_GATT_CONNECTED -> {
                    Log.d(logTag, "ACTION_GATT_CONNECTED")
                    microbit?.connected = true
                    // microbit?.registerBroadcastReceiver(applicationContext)
                    mBottomNav?.menu?.forEach { it.isEnabled = true }
                    fireOnMicroBitConnected()
                }
                BTLEService.ACTION_GATT_DISCONNECTED -> {
                    Log.d(logTag, "ACTION_GATT_DISCONNECTED")
                    // microbit?.unregisterBroadcastReceiver(applicationContext)
                    microbit?.connected = false
                    mBottomNav?.menu?.forEach { it.isEnabled = false }
                    mBottomNav?.selectedItemId = R.id.navigation_settings
                    fireOnMicroBitDisconnected()
                }
                BTLEService.ACTION_GATT_SERVICES_DISCOVERED -> {


@@ 309,9 335,6 @@ class MainActivity : IMicroBitHolder, INewBondedDeviceDealer, AppCompatActivity(
                        microbit?.addService(gattService)
                    }
                }
                BTLEService.ACTION_DATA_AVAILABLE -> {
                    Log.d(logTag, "ACTION_DATA_AVAILABLE: " + intent.getStringExtra(BTLEService.EXTRA_DATA))
                }
                BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
                    val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
                    Log.d(logTag, "ACTION_BOND_STATE_CHANGED: $device")


@@ 320,6 343,13 @@ class MainActivity : IMicroBitHolder, INewBondedDeviceDealer, AppCompatActivity(
                        fireOnNewBondedDevice(device)
                    }
                }
                BTLEService.ACTION_DATA_AVAILABLE -> {
                    Log.d(logTag, "ACTION_DATA_AVAILABLE: " + intent.extras!!.getString(BTLEService.PARCEL_CHARACTERISTIC_UUID))
                    if (microbit != null && microbit!!.connected) {
                        intent.extras?.let { microbit?.handleDataReceived(it) }
                        // Log.d(logTag, "ACTION_DATA_AVAILABLE: " + bundle!!.getString(BTLEService.PARCEL_CHARACTERISTIC_UUID))
                    }
                }
            }
        }
    }

M app/src/main/java/net/hokiegeek/kiddoecomm/MicroBit.kt => app/src/main/java/net/hokiegeek/kiddoecomm/MicroBit.kt +220 -16
@@ 2,14 2,23 @@ package net.hokiegeek.kiddoecomm

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import net.hokiegeek.kiddoecomm.bt.BTLEService
import java.util.*
import kotlin.experimental.and
import kotlin.experimental.or

class MicroBit(val alias: String, private val device: BluetoothDevice) {
class MicroBit
        (val alias: String, private val device: BluetoothDevice)
    // : BroadcastReceiver()
{
    private val logTag = "KiddoeComm::MicroBit"

    // UUIDs (Copied from  https://github.com/microbit-foundation/microbit-blue/blob/master/app/src/main/java/com/bluetooth/mwoolley/microbitbledemo/bluetooth/BleAdapterService.java#L93)


@@ 149,19 158,14 @@ class MicroBit(val alias: String, private val device: BluetoothDevice) {
                uuid.toString()
            }
        }

        // message parms
        private const val PARCEL_DESCRIPTOR_UUID = "DESCRIPTOR_UUID"
        private const val PARCEL_CHARACTERISTIC_UUID = "CHARACTERISTIC_UUID"
        private const val PARCEL_SERVICE_UUID = "SERVICE_UUID"
        private const val PARCEL_VALUE = "VALUE"
        private const val PARCEL_RSSI = "RSSI"
        private const val PARCEL_TEXT = "TEXT"
    }

    private val services: MutableMap<UUID, BluetoothGattService>? = mutableMapOf()
    private val serviceCharacteristics: MutableMap<UUID, BluetoothGattCharacteristic>? = mutableMapOf()

    private var buttonStateListeners: MutableList<IMicroBitButtonStateListener> = mutableListOf()
    private var temperatureListeners: MutableList<IMicroBitTemperatureListener> = mutableListOf()

    val name: String
        get() = this.device.name



@@ 175,6 179,9 @@ class MicroBit(val alias: String, private val device: BluetoothDevice) {
            if (service != null) field = service
        }

    val string: String
        get() = "$alias ($name/$address)"

    fun addService(service: BluetoothGattService) {
        // Log.d(logTag, "adding service: ${service.uuid}")
        Log.d(logTag, "adding service: ${nameOrUUID(service.uuid)}")


@@ 194,6 201,17 @@ class MicroBit(val alias: String, private val device: BluetoothDevice) {
        return serviceCharacteristics?.get(uuid)
    }

    private fun setNotification(uuid: UUID, enabled: Boolean) {
        Log.d(logTag, "setNotification(${nameOrUUID(uuid)}, $enabled)")
        getCharacteristic(uuid)?.let {
            btleService?.setCharacteristicNotification(it, enabled)
            val descriptor = it.getDescriptor(UUID_CLIENT_CHARACTERISTIC_CONFIG)
            descriptor.value = if (enabled) BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
                               else BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
            btleService?.writeDescriptor(descriptor)
        }
    }

    val hasLedsService: Boolean
        get() {
            if (this.services == null) {


@@ 202,8 220,51 @@ class MicroBit(val alias: String, private val device: BluetoothDevice) {
            return this.services.containsKey(UUID_LEDSERVICE_SERVICE)
        }

    fun readLeds() {
       // TODO
    private fun setButtonStateNotification(enabled: Boolean) {
        setNotification(UUID_BUTTON1STATE_CHARACTERISTIC, enabled)
        setNotification(UUID_BUTTON2STATE_CHARACTERISTIC, enabled)
    }

    fun addButtonStateListener(listener: IMicroBitButtonStateListener) {
        Log.d(logTag, "addButtonStateListener(${listener::class.simpleName})")
        if (buttonStateListeners.isEmpty()) {
            setButtonStateNotification(true)
        }
        buttonStateListeners.add(listener)
    }

    fun removeButtonStateListener(listener: IMicroBitButtonStateListener) {
        Log.d(logTag, "removeButtonStateListener(${listener::class.simpleName})")
        buttonStateListeners.removeIf { it == listener }
        if (buttonStateListeners.isEmpty()) {
            setButtonStateNotification(false)
        }
    }

    private fun fireButton1StateReceived(state: Int) {
        buttonStateListeners.forEach { it.OnMicroBitButton1StateReceived(state) }
    }

    private fun fireButton2StateReceived(state: Int) {
        buttonStateListeners.forEach { it.OnMicroBitButton2StateReceived(state) }
    }

    fun addTemperatureListener(listener: IMicroBitTemperatureListener) {
        if (temperatureListeners.isEmpty()) {
            setNotification(UUID_TEMPERATURESERVICE_SERVICE, true)
        }
        temperatureListeners.add(listener)
    }

    fun removeTemperatureListener(listener: IMicroBitTemperatureListener) {
        temperatureListeners.removeIf { it == listener }
        if (temperatureListeners.isEmpty()) {
            setNotification(UUID_TEMPERATURESERVICE_SERVICE, false)
        }
    }

    private fun fireTemperatureChanged(temp: Int) {
        temperatureListeners.forEach { it.OnMicroBitTemperatureReceived(temp) }
    }

    fun setLeds(leds: List<List<Boolean>>) {


@@ 226,14 287,14 @@ class MicroBit(val alias: String, private val device: BluetoothDevice) {
                }
            }

            Log.e(logTag, "LED matrix")
            Log.e(logTag, "  01234")
            Log.i(logTag, "LED matrix")
            Log.i(logTag, "  01234")
            ledMatrixState.forEachIndexed { rownum, value ->
                var row = ""
                for (ledInRow in 4 downTo 0) {
                    row += if (value and ((1 shl ledInRow).toByte()) != 0.toByte()) "o" else "."
                }
                Log.e(logTag, "$rownum $row")
                Log.i(logTag, "$rownum $row")
            }

            characteristic.value = ledMatrixState


@@ 266,7 327,150 @@ class MicroBit(val alias: String, private val device: BluetoothDevice) {
        }
    }

    /*
    fun registerBroadcastReceiver(context: Context) {
        Log.d(logTag, "Register micro:bit broadcast receiver")
        val intentFilter = IntentFilter()
        intentFilter.addAction(BTLEService.ACTION_GATT_SERVICES_DISCOVERED)
        intentFilter.addAction(BTLEService.ACTION_DATA_AVAILABLE)
        context.registerReceiver(blReceiver, intentFilter)
    }

    val string: String
        get() = "$alias ($name/$address)"
    fun unregisterBroadcastReceiver(context: Context) {
        Log.d(logTag, "Unregister micro:bit broadcast receiver")
        context.unregisterReceiver(blReceiver)
    }
    */

    fun handleDataReceived(bundle: Bundle) {
        // Log.d(logTag, "ACTION_DATA_AVAILABLE: " + bundle!!.getString(BTLEService.PARCEL_CHARACTERISTIC_UUID))
        val charUuid = UUID.fromString(bundle!!.getString(BTLEService.PARCEL_CHARACTERISTIC_UUID))
        val value = bundle.getByteArray(BTLEService.PARCEL_VALUE)
        Log.d(logTag, "ACTION_DATA_AVAILABLE: CHAR: ${nameOrUUID(charUuid)}")
        Log.d(logTag, "ACTION_DATA_AVAILABLE: VAL: $value")
        Log.d(logTag, "ACTION_DATA_AVAILABLE: VAL[0]: ${value?.get(0)}")
        Log.d(logTag, "ACTION_DATA_AVAILABLE: VAL[0].str: " + value?.get(0))
        Log.d(logTag, "ACTION_DATA_AVAILABLE: VAL[0].int: ${value?.get(0)?.toInt()}")
        val num = value?.get(0)?.toInt()
        if (num == null) {
            return
        }
        when(charUuid) {
            UUID_TEMPERATURESERVICE_SERVICE -> {
                Log.d(logTag, "temperature $num")
                fireTemperatureChanged(num)
            }
            UUID_BUTTON1STATE_CHARACTERISTIC -> {
                Log.d(logTag, "button1state $num")
                fireButton1StateReceived(num)
            }
            UUID_BUTTON2STATE_CHARACTERISTIC -> {
                Log.d(logTag, "button2state $value")
                fireButton2StateReceived(num)
            }
            UUID_LEDMATRIXSTATE_CHARACTERISTIC-> {
                Log.d(logTag, "ledmatrix $value")
            }
            else ->
                Log.w(logTag, "did not recognize characteristic")
        }
    }

    /*
    private val blReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent) {
            when(intent.action) {
                BTLEService.ACTION_GATT_SERVICES_DISCOVERED -> {
                    Log.d(logTag, "ACTION_GATT_SERVICES_DISCOVERED")
                    for (gattService in btleService?.supportedGattServices!!) {
                        addService(gattService)
                    }
                    if (buttonStateListeners.isNotEmpty()) {
                        setButtonStateNotification(true)
                    }
                    if (temperatureListeners.isNotEmpty()) {
                        setNotification(UUID_TEMPERATURESERVICE_SERVICE, true)
                    }
                }
                BTLEService.ACTION_DATA_AVAILABLE -> {
                    val bundle = intent.extras
                    // Log.d(logTag, "ACTION_DATA_AVAILABLE: " + bundle!!.getString(BTLEService.PARCEL_CHARACTERISTIC_UUID))
                    val charUuid = UUID.fromString(bundle!!.getString(BTLEService.PARCEL_CHARACTERISTIC_UUID))
                    val value = bundle.getByteArray(BTLEService.PARCEL_VALUE)
                    Log.d(logTag, "ACTION_DATA_AVAILABLE: CHAR: ${nameOrUUID(charUuid)}")
                    Log.d(logTag, "ACTION_DATA_AVAILABLE: VAL: $value")
                    Log.d(logTag, "ACTION_DATA_AVAILABLE: VAL[0]: ${value?.get(0)}")
                    Log.d(logTag, "ACTION_DATA_AVAILABLE: VAL[0].str: " + value?.get(0))
                    when(charUuid) {
                        UUID_TEMPERATURESERVICE_SERVICE -> {
                            Log.d(logTag, "temperature $value")
                            fireTemperatureChanged(0)
                        }
                        UUID_BUTTON1STATE_CHARACTERISTIC -> {
                            Log.d(logTag, "button1state $value")
                            fireButton1StateReceived()
                        }
                        UUID_BUTTON2STATE_CHARACTERISTIC -> {
                            Log.d(logTag, "button2state $value")
                            fireButton2StateReceived()
                        }
                        UUID_LEDMATRIXSTATE_CHARACTERISTIC-> {
                            Log.d(logTag, "ledmatrix $value")
                        }
                        else ->
                            Log.w(logTag, "did not recognize characteristic")
                    }
                }
            }
        }
    }
     */

    /*
    override fun onReceive(context: Context?, intent: Intent) {
        when(intent.action) {
            BTLEService.ACTION_GATT_SERVICES_DISCOVERED -> {
                Log.d(logTag, "ACTION_GATT_SERVICES_DISCOVERED")
                for (gattService in btleService?.supportedGattServices!!) {
                    addService(gattService)
                }
                if (buttonStateListeners.isNotEmpty()) {
                    setButtonStateNotification(true)
                }
                if (temperatureListeners.isNotEmpty()) {
                    setNotification(UUID_TEMPERATURESERVICE_SERVICE, true)
                }
            }
            BTLEService.ACTION_DATA_AVAILABLE -> {
                val bundle = intent.extras
                // Log.d(logTag, "ACTION_DATA_AVAILABLE: " + bundle!!.getString(BTLEService.PARCEL_CHARACTERISTIC_UUID))
                val charUuid = UUID.fromString(bundle!!.getString(BTLEService.PARCEL_CHARACTERISTIC_UUID))
                val value = bundle.getByteArray(BTLEService.PARCEL_VALUE)
                Log.d(logTag, "ACTION_DATA_AVAILABLE: CHAR: ${nameOrUUID(charUuid)}")
                Log.d(logTag, "ACTION_DATA_AVAILABLE: VAL: $value")
                Log.d(logTag, "ACTION_DATA_AVAILABLE: VAL[0]: ${value?.get(0)}")
                Log.d(logTag, "ACTION_DATA_AVAILABLE: VAL[0].str: " + value?.get(0))
                when(charUuid) {
                    UUID_TEMPERATURESERVICE_SERVICE -> {
                        Log.d(logTag, "temperature $value")
                        fireTemperatureChanged(0)
                    }
                    UUID_BUTTON1STATE_CHARACTERISTIC -> {
                        Log.d(logTag, "button1state $value")
                        fireButton1StateReceived()
                    }
                    UUID_BUTTON2STATE_CHARACTERISTIC -> {
                        Log.d(logTag, "button2state $value")
                        fireButton2StateReceived()
                    }
                    UUID_LEDMATRIXSTATE_CHARACTERISTIC-> {
                        Log.d(logTag, "ledmatrix $value")
                    }
                    else ->
                        Log.w(logTag, "did not recognize characteristic")
                }
            }
        }
    }
     */
}

M app/src/main/java/net/hokiegeek/kiddoecomm/bt/BTLEService.kt => app/src/main/java/net/hokiegeek/kiddoecomm/bt/BTLEService.kt +70 -32
@@ 5,6 5,7 @@ import android.bluetooth.*
import android.content.Context
import android.content.Intent
import android.os.Binder
import android.os.Bundle
import android.os.IBinder
import android.util.Log



@@ 21,6 22,13 @@ class BTLEService : Service() {
        const val ACTION_CHARACTERISTIC_WRITE = "net.hokiegeek.bt.le.ACTION_CHARACTERISTIC_WRITE"
        const val ACTION_DATA_AVAILABLE = "net.hokiegeek.bt.le.ACTION_DATA_AVAILABLE"
        const val EXTRA_DATA = "net.hokiegeek.bt.le.EXTRA_DATA"

        const val PARCEL_DESCRIPTOR_UUID = "DESCRIPTOR_UUID"
        const val PARCEL_CHARACTERISTIC_UUID = "CHARACTERISTIC_UUID"
        const val PARCEL_SERVICE_UUID = "SERVICE_UUID"
        const val PARCEL_VALUE = "VALUE"
        // const val PARCEL_RSSI = "RSSI"
        // const val PARCEL_TEXT = "TEXT"
    }

    private var mBluetoothManager: BluetoothManager? = null


@@ 37,12 45,12 @@ class BTLEService : Service() {
            status: Int,
            newState: Int
        ) {
            val intentAction: String
            // val intentAction: String
            when(newState) {
                BluetoothProfile.STATE_CONNECTED -> {
                    intentAction = ACTION_GATT_CONNECTED
                    // intentAction = ACTION_GATT_CONNECTED
                    mConnectionState = STATE_CONNECTED
                    broadcastUpdate(intentAction)
                    broadcastUpdate(ACTION_GATT_CONNECTED)
                    Log.i(logTag, "Connected to GATT server.")
                    // Attempts to discover services after successful connection.
                    Log.i(


@@ 51,10 59,10 @@ class BTLEService : Service() {
                    )
                }
                BluetoothProfile.STATE_DISCONNECTED -> {
                    intentAction = ACTION_GATT_DISCONNECTED
                    // intentAction = ACTION_GATT_DISCONNECTED
                    mConnectionState = STATE_DISCONNECTED
                    Log.i(logTag, "Disconnected from GATT server.")
                    broadcastUpdate(intentAction)
                    broadcastUpdate(ACTION_GATT_DISCONNECTED)
                }
            }
        }


@@ 83,7 91,7 @@ class BTLEService : Service() {
            status: Int
        ) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(logTag, "Wrote data successfully!")
                Log.d(logTag, "Wrote characteristic successfully!")
                if (characteristic != null) {
                    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic)
                }


@@ 96,8 104,24 @@ class BTLEService : Service() {
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic
        ) {
            Log.d(logTag, "Changed characteristic: ${characteristic.uuid.toString()}")
            broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic)
        }


        override fun onDescriptorWrite(
            gatt: BluetoothGatt,
            descriptor: BluetoothGattDescriptor,
            status: Int
        ) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(logTag, "Wrote descriptor successfully!")
                broadcastUpdate(ACTION_DATA_AVAILABLE, descriptor)
            } else {
                Log.w(logTag, "Unable to write descriptor")
            }

        }
    }

    private fun broadcastUpdate(action: String) {


@@ 111,24 135,28 @@ class BTLEService : Service() {
    ) {
        val intent = Intent(action)

        // For all other profiles, writes the data formatted in HEX.
        val data = characteristic.value
        if (data != null && data.isNotEmpty()) {
            val stringBuilder = StringBuilder(data.size)
            for (byteChar in data) stringBuilder.append(
                String.format(
                    "%02X ",
                    byteChar
                )
            )
            intent.putExtra(
                EXTRA_DATA,
                """
                    ${String(data)}
                    $stringBuilder
                    """.trimIndent()
            )
        }
        val bundle = Bundle()
        bundle.putString(PARCEL_SERVICE_UUID, characteristic.service.uuid.toString())
        bundle.putString(PARCEL_CHARACTERISTIC_UUID, characteristic.uuid.toString())
        bundle.putByteArray(PARCEL_VALUE, characteristic.value)

        intent.putExtras(bundle)
        sendBroadcast(intent)
    }

    private fun broadcastUpdate(
        action: String,
        descriptor: BluetoothGattDescriptor
    ) {
        val intent = Intent(action)

        val bundle = Bundle()
        bundle.putString(PARCEL_SERVICE_UUID, descriptor.characteristic.service.uuid.toString())
        bundle.putString(PARCEL_CHARACTERISTIC_UUID, descriptor.characteristic.uuid.toString())
        bundle.putString(PARCEL_DESCRIPTOR_UUID, descriptor.uuid.toString())
        bundle.putByteArray(PARCEL_VALUE, descriptor.value)

        intent.putExtras(bundle)
        sendBroadcast(intent)
    }



@@ 238,14 266,6 @@ class BTLEService : Service() {
        mBluetoothGatt!!.disconnect()
    }

    fun writeCharacteristic(characteristic: BluetoothGattCharacteristic) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(logTag, "BluetoothAdapter not initialized")
            return
        }
        mBluetoothGatt!!.writeCharacteristic(characteristic)
    }

    /**
     * After using a given BLE device, the app must call this method to ensure resources are
     * released properly.


@@ 258,6 278,14 @@ class BTLEService : Service() {
        mBluetoothGatt = null
    }

    fun writeCharacteristic(characteristic: BluetoothGattCharacteristic) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(logTag, "BluetoothAdapter not initialized")
            return
        }
        mBluetoothGatt!!.writeCharacteristic(characteristic)
    }

    /**
     * Request a read on a given `BluetoothGattCharacteristic`. The read result is reported
     * asynchronously through the `BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)`


@@ 287,9 315,19 @@ class BTLEService : Service() {
            Log.w(logTag, "BluetoothAdapter not initialized")
            return
        }
        Log.d(logTag, "setCharacteristicNotification(${characteristic.uuid.toString()}, $enabled)")
        mBluetoothGatt!!.setCharacteristicNotification(characteristic, enabled)
    }

    fun writeDescriptor(descriptor: BluetoothGattDescriptor) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(logTag, "BluetoothAdapter not initialized")
            return
        }
        Log.d(logTag, "writeDescriptor(${descriptor.uuid.toString()})")
        mBluetoothGatt!!.writeDescriptor(descriptor)
    }

    /**
     * Retrieves a list of supported GATT services on the connected device. This should be
     * invoked only after `BluetoothGatt#discoverServices()` completes successfully.

M app/src/main/java/net/hokiegeek/kiddoecomm/ui/audio/AudioFragment.kt => app/src/main/java/net/hokiegeek/kiddoecomm/ui/audio/AudioFragment.kt +12 -3
@@ 49,7 49,7 @@ class AudioFragment : IMicroBitConnectionListener, Fragment() {
            object : Thread() {
                override fun run() {
                    Log.d(logTag, "Sending alert sound")
                    mMicroBit?.sendUART("s;a#")
                    mMicroBit?.let { it1 -> playAlert(it1) }
                }
            }.start()
        }


@@ 58,7 58,7 @@ class AudioFragment : IMicroBitConnectionListener, Fragment() {
            object : Thread() {
                override fun run() {
                    Log.d(logTag, "Sending alarm sound")
                    mMicroBit?.sendUART("s;m#")
                    mMicroBit?.let { it1 -> playAlarm(it1) }
                }
            }.start()
        }


@@ 79,11 79,20 @@ class AudioFragment : IMicroBitConnectionListener, Fragment() {
        if (snackbar != null) {
            snackbar!!.dismiss()
        }
        // viewModel.text.value = "Connected to ${microbit.string}"
    }

    override fun onMicroBitDisconnected() {
        Log.d(logTag, "Micro:Bit disconnected")
        mMicroBit = null
    }

    companion object {
        fun playAlert(microbit: MicroBit) {
            microbit.sendUART("s;a#")
        }

        fun playAlarm(microbit: MicroBit) {
            microbit.sendUART("s;m#")
        }
    }
}

M app/src/main/java/net/hokiegeek/kiddoecomm/ui/messaging/MessagingFragment.kt => app/src/main/java/net/hokiegeek/kiddoecomm/ui/messaging/MessagingFragment.kt +37 -10
@@ 1,5 1,7 @@
package net.hokiegeek.kiddoecomm.ui.messaging

import android.bluetooth.BluetoothDevice
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater


@@ 12,12 14,14 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputEditText
import net.hokiegeek.kiddoecomm.MicroBit
import net.hokiegeek.kiddoecomm.R
import net.hokiegeek.kiddoecomm.IMicroBitConnectionListener
import net.hokiegeek.kiddoecomm.IMicroBitHolder

class MessagingFragment : IMicroBitConnectionListener, Fragment() {
import net.hokiegeek.kiddoecomm.*
import net.hokiegeek.kiddoecomm.bt.BTLEService
import net.hokiegeek.kiddoecomm.ui.audio.AudioFragment

class MessagingFragment :
    IMicroBitConnectionListener,
    IMicroBitButtonStateListener,
    Fragment() {
    private val logTag = "KiddoeComm::MessagingFragment"

    private lateinit var viewModel: MessagingViewModel


@@ 46,6 50,7 @@ class MessagingFragment : IMicroBitConnectionListener, Fragment() {

        (activity as IMicroBitHolder).setMicroBitConnectedListener(this)

        /*
        if (mMicroBit == null || !mMicroBit!!.connected) {
            snackbar = Snackbar.make(
                activity!!.findViewById(android.R.id.content),


@@ 54,15 59,14 @@ class MessagingFragment : IMicroBitConnectionListener, Fragment() {
            ).setAction("Action", null)
            snackbar!!.show()
        }
        */

        root.findViewById<ImageButton>(R.id.button_send_message).setOnClickListener {
            object : Thread() {
                override fun run() {
                    if (!textView.text?.isEmpty()!!) {
                        Log.d(logTag, "Sending message")
                        mMicroBit?.sendUART("s;a#")
                        // sleep(100)
                        // mMicroBit?.sendUART("s;a#")
                        mMicroBit?.let { it1 -> AudioFragment.playAlert(it1) }
                        sleep(300)
                        mMicroBit?.showLedString(textView.text.toString())
                    }


@@ 78,6 82,17 @@ class MessagingFragment : IMicroBitConnectionListener, Fragment() {
        return root
    }

    /*
    override fun onResume() {
        super.onResume()

        // val intentFilter = IntentFilter()
        // intentFilter.addAction(BTLEService.ACTION_GATT_SERVICES_DISCOVERED)
        // intentFilter.addAction(BTLEService.ACTION_DATA_AVAILABLE)
        // registerReceiver(blReceiver, intentFilter)
    }
    */

    override fun onDestroyView() {
        super.onDestroyView()



@@ 98,15 113,27 @@ override fun onStart() {
    override fun onMicroBitConnected(microbit: MicroBit) {
        Log.d(logTag, "Micro:Bit connected")
        mMicroBit = microbit
        Snackbar.make(activity!!.findViewById(android.R.id.content), "Micro:Bit connected", Snackbar.LENGTH_LONG).setAction("Action", null)
        // Snackbar.make(activity!!.findViewById(android.R.id.content), "Micro:Bit connected", Snackbar.LENGTH_LONG).setAction("Action", null)
        /*
        if (snackbar != null) {
            snackbar!!.dismiss()
        }
        */
        // viewModel.text.value = "Connected to ${microbit.string}"
        mMicroBit!!.addButtonStateListener(this)
    }

    override fun onMicroBitDisconnected() {
        Log.d(logTag, "Micro:Bit disconnected")
        mMicroBit!!.removeButtonStateListener(this)
        mMicroBit = null
    }

    override fun OnMicroBitButton1StateReceived(state: Int) {
        Log.d(logTag, "Button 1 state is $state")
    }

    override fun OnMicroBitButton2StateReceived(state: Int) {
        Log.d(logTag, "Button 2 state is $state")
    }
}