diff --git a/android/app/src/main/java/nl/eddydevink/sylda/BleRelayManager.kt b/android/app/src/main/java/nl/eddydevink/sylda/BleRelayManager.kt index 1cc0195..9ec1740 100644 --- a/android/app/src/main/java/nl/eddydevink/sylda/BleRelayManager.kt +++ b/android/app/src/main/java/nl/eddydevink/sylda/BleRelayManager.kt @@ -23,23 +23,27 @@ import java.nio.ByteOrder import java.util.UUID import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec +import kotlin.text.Charsets class BleRelayManager( private val context: Context, private val callbacks: BleCallbacks ) { companion object { + var instance: BleRelayManager? = null private const val TAG = "BleRelayManager" private val SERVICE_UUID: UUID = UUID.fromString("4fafc201-1fb5-459e-8fcc-c5c9c331914b") private val CHARACTERISTIC_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d8") private val UUID_CHAR_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d9") + private val VERSION_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26db") + private val OTA_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26da") private const val SCAN_TIMEOUT_MS = 30000L private const val RECONNECT_DELAY_MS = 2000L private const val MAX_SCAN_RETRIES = 3 private const val HMAC_PKT_SIZE = 37 private const val HMAC_LEN = 32 - private const val BLE_MTU = 256 + private const val BLE_MTU = 512 } private var bluetoothGatt: BluetoothGatt? = null @@ -52,6 +56,13 @@ class BleRelayManager( private var hmacKey: ByteArray? = null private var deviceUuid: ByteArray? = null private var triedAddresses = mutableSetOf() + private var otaCharacteristic: BluetoothGattCharacteristic? = null + private var espVersion: String? = null + private var appVersion: String = "" + + init { + instance = this + } private val scanRunnable = Runnable { startScan() } private var scanTimeoutRunnable: Runnable? = null @@ -170,6 +181,22 @@ class BleRelayManager( stopScan() bluetoothGatt?.disconnect() } + + @SuppressLint("MissingPermission") + fun sendOtaData(data: ByteArray) { + val char = otaCharacteristic ?: return + val gatt = bluetoothGatt ?: return + char.value = data + char.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE + gatt.writeCharacteristic(char) + } + + @SuppressLint("MissingPermission") + fun enableOtaNotifications(): Boolean { + val char = otaCharacteristic ?: return false + val gatt = bluetoothGatt ?: return false + return gatt.setCharacteristicNotification(char, true) + } fun updateKeys() { loadState() } @@ -182,6 +209,10 @@ class BleRelayManager( return bluetoothGatt != null } + fun setAppVersion(version: String) { + appVersion = version + } + @SuppressLint("MissingPermission") private fun connectToDevice(device: android.bluetooth.BluetoothDevice) { handler.post { callbacks.onStatusChanged(BleStatus.Connecting) } @@ -230,7 +261,9 @@ class BleRelayManager( val service = gatt.getService(SERVICE_UUID) if (service != null) { relayCharacteristic = service.getCharacteristic(CHARACTERISTIC_UUID) + otaCharacteristic = service.getCharacteristic(OTA_UUID) val uuidChar = service.getCharacteristic(UUID_CHAR_UUID) + val versionChar = service.getCharacteristic(VERSION_UUID) if (relayCharacteristic != null && uuidChar != null) { Log.d(TAG, "Reading device UUID characteristic...") gatt.readCharacteristic(uuidChar) @@ -249,14 +282,26 @@ class BleRelayManager( if (characteristic.uuid == UUID_CHAR_UUID && status == BluetoothGatt.GATT_SUCCESS) { val storedUuid = deviceUuid if (storedUuid != null && value.contentEquals(storedUuid)) { - Log.d(TAG, "UUID verified, setting up relay") - handler.post { callbacks.onStatusChanged(BleStatus.Connected) } + Log.d(TAG, "UUID verified, reading version...") + val service = gatt.getService(SERVICE_UUID) + val versionChar = service?.getCharacteristic(VERSION_UUID) + if (versionChar != null) { + gatt.readCharacteristic(versionChar) + } else { + handler.post { callbacks.onStatusChanged(BleStatus.Connected) } + } } else { Log.w(TAG, "UUID mismatch -- disconnecting") triedAddresses.add(gatt.device.address) handler.post { callbacks.onStatusChanged(BleStatus.WrongDevice) } gatt.disconnect() } + } else if (characteristic.uuid == VERSION_UUID && status == BluetoothGatt.GATT_SUCCESS) { + val version = String(value, Charsets.UTF_8).trim() + espVersion = version + Log.d(TAG, "ESP firmware version: $version, app bundled: $appVersion") + handler.post { callbacks.onFirmwareVersion(version, appVersion) } + handler.post { callbacks.onStatusChanged(BleStatus.Connected) } } }