2020-01-24 17:05:55 -08:00
package com.geeksville.mesh
2020-01-27 16:00:00 -08:00
import android.app.Service
2020-01-27 14:54:35 -08:00
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
2020-01-27 16:00:00 -08:00
import android.bluetooth.BluetoothGattCharacteristic
2020-01-27 16:58:47 -08:00
import android.bluetooth.BluetoothGattCharacteristic.PERMISSION_WRITE
2020-01-27 14:54:35 -08:00
import android.bluetooth.BluetoothManager
2020-01-24 17:05:55 -08:00
import android.content.Context
import android.content.Intent
2020-01-27 16:00:00 -08:00
import android.os.IBinder
2020-01-24 17:47:32 -08:00
import com.geeksville.android.DebugLogFile
2020-01-24 17:05:55 -08:00
import com.geeksville.android.Logging
2020-01-24 20:35:42 -08:00
import com.google.protobuf.util.JsonFormat
2020-01-27 14:54:35 -08:00
import java.util.*
/ * Info for the esp32 device side code . See that source for the ' gold ' standard docs on this interface .
MeshBluetoothService UUID 6 ba1b218 - 15 a8 - 461f - 9f a8 - 5 dcae273eafd
FIXME - notify vs indication for fromradio output . Using notify for now , not sure if that is best
FIXME - in the esp32 mesh management code , occasionally mirror the current net db to flash , so that if we reboot we still have a good guess of users who are out there .
FIXME - make sure this protocol is guaranteed robust and won ' t drop packets
" According to the BLE specification the notification length can be max ATT_MTU - 3. The 3 bytes subtracted is the 3-byte header(OP-code (operation, 1 byte) and the attribute handle (2 bytes)).
In BLE 4.1 the ATT _MTU is 23 bytes ( 20 bytes for payload ) , but in BLE 4.2 the ATT _MTU can be negotiated up to 247 bytes . "
MAXPACKET is 256 ? look into what the lora lib uses . FIXME
Characteristics :
UUID
properties
description
8 ba2bcc2 - ee02 - 4 a55 - a531 - c525c5e454d5
read
fromradio - contains a newly received packet destined towards the phone ( up to MAXPACKET bytes ? per packet ) .
After reading the esp32 will put the next packet in this mailbox . If the FIFO is empty it will put an empty packet in this
mailbox .
f75c76d2 - 129e-4 dad - a1dd - 7866124401 e7
write
toradio - write ToRadio protobufs to this charstic to send them ( up to MAXPACKET len )
ed9da18c - a800 - 4f 66 - a670 - aa7547e34453
read | notify | write
fromnum - the current packet # in the message waiting inside fromradio , if the phone sees this notify it should read messages
until it catches up with this number .
The phone can write to this register to go backwards up to FIXME packets , to handle the rare case of a fromradio packet was dropped after the esp32
callback was called , but before it arrives at the phone . If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio .
When the esp32 advances fromnum , it will delay doing the notify by 100 ms , in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio .
Note : that if the phone ever sees this number decrease , it means the esp32 has rebooted .
Re : queue management
Not all messages are kept in the fromradio queue ( filtered based on SubPacket ) :
* only the most recent Position and User messages for a particular node are kept
* all Data SubPackets are kept
* No WantNodeNum / DenyNodeNum messages are kept
A variable keepAllPackets , if set to true will suppress this behavior and instead keep everything for forwarding to the phone ( for debugging )
* /
2020-01-24 17:05:55 -08:00
/ * *
* Handles the bluetooth link with a mesh radio device . Does not cache any device state ,
* just does bluetooth comms etc .. .
*
* This service is not exposed outside of this process .
*
* Note - this class intentionally dumb . It doesn ' t understand protobuf framing etc .. .
* It is designed to be simple so it can be stubbed out with a simulated version as needed .
* /
2020-01-27 16:00:00 -08:00
class RadioInterfaceService : Service ( ) , Logging {
2020-01-24 17:05:55 -08:00
companion object {
/ * *
* The RECEIVED _FROMRADIO
* Payload will be the raw bytes which were contained within a MeshProtos . FromRadio protobuf
* /
const val RECEIVE _FROMRADIO _ACTION = " $prefix .RECEIVE_FROMRADIO "
2020-01-25 11:40:13 -08:00
2020-01-27 14:54:35 -08:00
private val BTM _SERVICE _UUID = UUID . fromString ( " 6ba1b218-15a8-461f-9fa8-5dcae273eafd " )
private val BTM _FROMRADIO _CHARACTER =
UUID . fromString ( " 8ba2bcc2-ee02-4a55-a531-c525c5e454d5 " )
private val BTM _TORADIO _CHARACTER =
UUID . fromString ( " f75c76d2-129e-4dad-a1dd-7866124401e7 " )
private val BTM _FROMNUM _CHARACTER =
UUID . fromString ( " ed9da18c-a800-4f66-a670-aa7547e34453 " )
2020-01-25 12:24:53 -08:00
/// This is public only so that SimRadio can bootstrap our message flow
fun broadcastReceivedFromRadio ( context : Context , payload : ByteArray ) {
val intent = Intent ( RECEIVE _FROMRADIO _ACTION )
intent . putExtra ( EXTRA _PAYLOAD , payload )
context . sendBroadcast ( intent )
}
2020-01-24 17:05:55 -08:00
}
2020-01-27 14:54:35 -08:00
private val bluetoothAdapter : BluetoothAdapter by lazy ( LazyThreadSafetyMode . NONE ) {
val bluetoothManager = getSystemService ( Context . BLUETOOTH _SERVICE ) as BluetoothManager
bluetoothManager . adapter !!
}
// Both of these are created in onCreate()
private lateinit var device : BluetoothDevice
private lateinit var safe : SafeBluetooth
2020-01-27 16:24:38 -08:00
val service get ( ) = safe . gatt . services . find { it . uuid == BTM _SERVICE _UUID } !!
2020-01-27 16:00:00 -08:00
private lateinit var fromRadio : BluetoothGattCharacteristic
private lateinit var fromNum : BluetoothGattCharacteristic
2020-01-24 20:35:42 -08:00
lateinit var sentPacketsLog : DebugLogFile // inited in onCreate
2020-01-24 17:47:32 -08:00
2020-01-27 16:00:00 -08:00
// for debug logging only
private val jsonPrinter = JsonFormat . printer ( )
2020-01-24 17:05:55 -08:00
fun broadcastConnectionChanged ( isConnected : Boolean ) {
val intent = Intent ( " $prefix .CONNECTION_CHANGED " )
intent . putExtra ( EXTRA _CONNECTED , isConnected )
sendBroadcast ( intent )
}
/// Send a packet/command out the radio link
2020-01-25 10:00:57 -08:00
private fun handleSendToRadio ( p : ByteArray ) {
2020-01-24 17:47:32 -08:00
// For debugging/logging purposes ONLY we convert back into a protobuf for readability
val proto = MeshProtos . ToRadio . parseFrom ( p )
2020-01-25 11:40:13 -08:00
2020-01-24 20:35:42 -08:00
val json = jsonPrinter . print ( proto ) . replace ( '\n' , ' ' )
2020-01-25 11:40:13 -08:00
info ( " TODO sending to radio: $json " )
2020-01-24 20:35:42 -08:00
sentPacketsLog . log ( json )
2020-01-24 17:05:55 -08:00
}
// Handle an incoming packet from the radio, broadcasts it as an android intent
private fun handleFromRadio ( p : ByteArray ) {
2020-01-25 12:24:53 -08:00
broadcastReceivedFromRadio ( this , p )
2020-01-24 17:05:55 -08:00
}
2020-01-27 16:00:00 -08:00
/// Attempt to read from the fromRadio mailbox, if data is found broadcast it to android apps
private fun doReadFromRadio ( ) {
safe . asyncReadCharacteristic ( fromRadio ) {
2020-01-27 16:24:38 -08:00
val b = it . getOrThrow ( ) . value
if ( b . isNotEmpty ( ) ) {
debug ( " Received ${b.size} bytes from radio " )
handleFromRadio ( b )
// Queue up another read, until we run out of packets
doReadFromRadio ( )
} else
debug ( " Done reading from radio, fromradio is empty " )
2020-01-27 16:00:00 -08:00
}
}
2020-01-24 20:35:42 -08:00
override fun onCreate ( ) {
super . onCreate ( )
2020-01-27 16:58:47 -08:00
info ( " Creating radio interface service " )
2020-01-27 14:54:35 -08:00
// FIXME, let user GUI select which device we are talking to
// Note: this call does no comms, it just creates the device object (even if the
// device is off/not connected)
device = bluetoothAdapter . getRemoteDevice ( " B4:E6:2D:EA:32:B7 " )
// Note this constructor also does no comm
safe = SafeBluetooth ( this , device )
// FIXME, pass in true for autoconnect - so we will autoconnect whenever the radio
// comes in range (even if we made this connect call long ago when we got powered on)
// see https://stackoverflow.com/questions/40156699/which-correct-flag-of-autoconnect-in-connectgatt-of-ble for
// more info
2020-01-27 16:00:00 -08:00
// FIXME, broadcast connection lost/gained via broadcastConnecionChanged
safe . asyncConnect ( true ) { connRes ->
// This callback is invoked after we are connected
2020-01-27 16:24:38 -08:00
connRes . getOrThrow ( ) // FIXME, instead just try to reconnect?
2020-01-27 16:58:47 -08:00
info ( " Connected to radio! " )
2020-01-27 16:00:00 -08:00
2020-01-27 16:58:47 -08:00
// FIXME - no need to discover services, instead just hardwire the characteristics (like we do for toRadio)
2020-01-27 16:00:00 -08:00
safe . asyncDiscoverServices { discRes ->
2020-01-27 16:24:38 -08:00
discRes . getOrThrow ( ) // FIXME, instead just try to reconnect?
2020-01-27 18:47:13 -08:00
debug ( " Discovered services! " )
2020-01-27 16:00:00 -08:00
2020-01-27 16:58:47 -08:00
// we begin by setting our MTU size as high as it can go
safe . asyncRequestMtu ( 512 ) { mtuRes ->
2020-01-27 18:47:13 -08:00
debug ( " requested MTU result= $mtuRes " )
2020-01-27 16:58:47 -08:00
mtuRes . getOrThrow ( )
2020-01-27 18:47:13 -08:00
2020-01-27 16:58:47 -08:00
fromRadio = service . getCharacteristic ( BTM _FROMRADIO _CHARACTER )
fromNum = service . getCharacteristic ( BTM _FROMNUM _CHARACTER )
2020-01-27 16:00:00 -08:00
2020-01-27 16:58:47 -08:00
doReadFromRadio ( )
}
2020-01-27 16:00:00 -08:00
}
}
2020-01-27 14:54:35 -08:00
2020-01-24 20:35:42 -08:00
sentPacketsLog = DebugLogFile ( this , " sent_log.json " )
}
2020-01-24 17:47:32 -08:00
override fun onDestroy ( ) {
2020-01-27 16:58:47 -08:00
info ( " Destroying radio interface service " )
2020-01-24 17:47:32 -08:00
sentPacketsLog . close ( )
2020-01-27 18:47:13 -08:00
safe . disconnect ( )
2020-01-24 17:47:32 -08:00
super . onDestroy ( )
}
2020-01-27 16:00:00 -08:00
override fun onBind ( intent : Intent ? ) : IBinder ? {
return binder ;
2020-01-24 17:05:55 -08:00
}
2020-01-27 16:00:00 -08:00
private val binder = object : IRadioInterfaceService . Stub ( ) {
override fun sendToRadio ( a : ByteArray ) {
2020-01-27 16:24:38 -08:00
debug ( " queuing ${a.size} bytes to radio " )
// Note: we generate a new characteristic each time, because we are about to
// change the data and we want the data stored in the closure
2020-01-27 16:58:47 -08:00
val toRadio = BluetoothGattCharacteristic (
BTM _FROMRADIO _CHARACTER ,
BluetoothGattCharacteristic . PROPERTY _WRITE ,
PERMISSION _WRITE
)
2020-01-27 16:24:38 -08:00
toRadio . value = a
2020-01-27 18:47:13 -08:00
if ( true )
safe . asyncWriteCharacteristic ( toRadio ) {
it . getOrThrow ( ) // FIXME, handle the error better
}
else
error ( " FIXME ignoring writes for now - because they slide in before discovery - bad bad " )
2020-01-27 16:00:00 -08:00
}
}
2020-01-24 17:05:55 -08:00
}