[Python] How can I send data as a string from an Android device to a Raspberry Pi from the...

Discussão em 'Python' iniciado por Stack, Março 25, 2025.

  1. Stack

    Stack Membro Participativo

    I programmed an Android app in Android Studio using Kotlin and I am able to establish a connection with a Raspberry Pi 5 using BLE. I have the Android device as the peripheral and it is successfully advertising its service and characteristics. They will connect to each other, but I can't get any data to be transferred between them. I have tried everything I can possibly think of to solve my issue.

    I created a write and read characteristic in Android Studio and I want to send the String over the read characteristic when the Rapsberry Pi requests to read over that characteristic. I want the user to be able to press any button and then for that corresponding string associated with the button to send to the service terminal of the Raspberry Pi.

    This is the BLE code from Kotlin:

    class BLEAdvertiser(private val context: Context) {

    private val bluetoothManager: BluetoothManager = context.getSystemService(BluetoothManager::class.java) //Gets the BluetoothManager system service.
    private val bluetoothAdapter : BluetoothAdapter = bluetoothManager.adapter //Gets the BluetoothAdapter instance.
    private var bluetoothLeAdvertiser: BluetoothLeAdvertiser? = bluetoothAdapter.bluetoothLeAdvertiser //Creates the bluetoothLeAdvertiser variable.
    var bluetoothGattServer: BluetoothGattServer? = null
    private val connectedDevices = mutableSetOf<BluetoothDevice>()

    companion object {
    private const val TAG = "BLEAdvertiser"
    val APP_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb".lowercase())
    val RX_APP_UUID: UUID = UUID.fromString("00002201-0000-1000-8000-00805f9b34fb".lowercase())
    val TX_APP_UUID: UUID = UUID.fromString("00002202-0000-1000-8000-00805f9b34fb".lowercase())
    }

    fun setupGattServer(): Boolean {

    Log.d(TAG, "setupGattServer() is being called!")

    if (bluetoothGattServer != null) {
    Log.d(TAG, "setupGattServer() has already been called! Skipping setup process.")
    return true
    }

    bluetoothGattServer = bluetoothManager.openGattServer(context, gattServerCallback) //Starts the GATT server.

    if (bluetoothGattServer == null) {
    Log.e(TAG, "❌ Failed to open GATT server!")
    Handler(Looper.getMainLooper()).postDelayed({
    bluetoothGattServer = bluetoothManager.openGattServer(context, gattServerCallback)
    if (bluetoothGattServer == null) {
    Log.e(TAG, "❌ GATT server still null after retry! Restarting BLE...")
    restartBLE()
    } else {
    Log.d(TAG, "✅ GATT server opened successfully after retry.")
    }
    }, 3000) // Retry after 3 seconds
    return false
    } else {
    Log.d(TAG, "✅ GATT server opened successfully.")
    }

    //Create the service object.
    val elevatorService = BluetoothGattService(APP_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY)

    //Create read request characteristic. (Pi writes data here, Android reads it.)
    val rxCharacteristic = BluetoothGattCharacteristic(
    RX_APP_UUID,
    BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_NOTIFY,
    BluetoothGattCharacteristic.PERMISSION_READ
    )

    //Set initial value
    rxCharacteristic.value = byteArrayOf(0x12, 0x34)

    //Create write request characteristic. (Pi writes '2' here.)
    val txCharacteristic = BluetoothGattCharacteristic(
    TX_APP_UUID,
    BluetoothGattCharacteristic.PROPERTY_WRITE or BluetoothGattCharacteristic.PROPERTY_NOTIFY or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
    BluetoothGattCharacteristic.PERMISSION_WRITE or BluetoothGattCharacteristic.PERMISSION_READ
    )

    elevatorService.addCharacteristic(rxCharacteristic) //Adds the write characteristic to the service.
    Log.d(TAG, "Adding characteristic to service. ${rxCharacteristic.uuid}")
    elevatorService.addCharacteristic(txCharacteristic) //Adds the read characteristic to the service.
    Log.d(TAG, "Adding characteristic to service. ${txCharacteristic.uuid}")

    val result =
    bluetoothGattServer?.addService(elevatorService) //Adds the service to the GATT server.
    if (result == true) {
    Log.d(TAG, "✅ Service successfully added: ${elevatorService.uuid}")
    } else {
    Log.e(TAG, "❌ Failed to add service to GATT server!")
    }

    Handler(Looper.getMainLooper()).postDelayed({
    if (bluetoothGattServer?.services.isNullOrEmpty()) {
    Log.e(TAG, "❌ No services registered in GATT server! (Delayed Check)")
    } else {
    Log.d(TAG, "✅ Services successfully registered. (Delayed Check)")
    bluetoothGattServer?.services?.forEach { s ->
    Log.d(TAG, "✅ Service Registered: ${s.uuid}")
    s.characteristics.forEach { c ->
    Log.d(TAG, " - Characteristic: ${c.uuid} (Properties: ${c.properties})")
    }
    }
    }
    }, 2000) //Wait for 2 seconds.

    return result == true

    }

    //Create the GATT server callback.
    val gattServerCallback = object : BluetoothGattServerCallback() {

    override fun onConnectionStateChange(device: BluetoothDevice?, status: Int, newState: Int) {
    super.onConnectionStateChange(device, status, newState)

    if (device == null) {
    Log.e(TAG, "onConnectionStateChange: Device is null!")
    return
    }

    val stateDescription = when (newState) {
    BluetoothProfile.STATE_CONNECTED -> "CONNECTED"
    BluetoothProfile.STATE_DISCONNECTED -> "DISCONNECTED"
    BluetoothProfile.STATE_CONNECTING -> "CONNECTING"
    BluetoothProfile.STATE_DISCONNECTING -> "DISCONNECTING"
    else -> "UNKNOWN"
    }

    Log.d(TAG, " Connection state changed: $stateDescription for ${device.address}")

    when (newState) {
    BluetoothProfile.STATE_CONNECTED -> {
    Log.d(TAG, "Device successfully connected: ${device.address}")
    connectedDevices.add(device) // Store connected device
    }
    BluetoothProfile.STATE_DISCONNECTED -> {
    Log.d(TAG, "Device disconnected: ${device.address}")
    connectedDevices.remove(device) // Remove disconnected device
    }
    }
    }

    override fun onCharacteristicWriteRequest(
    device: BluetoothDevice?,
    requestId: Int,
    characteristic: BluetoothGattCharacteristic?,
    preparedWrite: Boolean,
    responseNeeded: Boolean,
    offset: Int,
    value: ByteArray?
    ) {
    super.onCharacteristicWriteRequest(
    device,
    requestId,
    characteristic,
    preparedWrite,
    responseNeeded,
    offset,
    value
    )

    if (characteristic?.uuid == TX_APP_UUID) {
    val receivedCommand = value?.toString(Charsets.UTF_8) ?: "Unknown"
    Log.d(TAG, " Received command from Pi: $receivedCommand")

    // Send acknowledgment response if required
    if (responseNeeded) {
    bluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value)
    }

    // Respond by setting RX characteristic value
    val responseData = "ACK: $receivedCommand".toByteArray(Charsets.UTF_8)
    val rxCharacteristic = bluetoothGattServer?.getService(APP_UUID)?.getCharacteristic(RX_APP_UUID)
    rxCharacteristic?.value = responseData

    // Notify Pi that new data is available
    rxCharacteristic?.let {
    device?.let { dev ->
    bluetoothGattServer?.notifyCharacteristicChanged(dev, it, false)
    }
    }
    Log.d(TAG, " Sent ACK response to Pi: $receivedCommand")
    }
    }

    override fun onCharacteristicReadRequest(
    device: BluetoothDevice?,
    requestId: Int,
    offset: Int,
    characteristic: BluetoothGattCharacteristic?
    ) {
    Log.d(TAG, " Read request received for: ${characteristic?.uuid}")

    if (characteristic?.uuid == RX_APP_UUID) {
    val responseData = characteristic.value ?: byteArrayOf(0x12, 0x34)
    Log.d(TAG, " Pi read from RX_APP_UUID: ${responseData.contentToString()}")

    bluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, responseData)
    } else {
    Log.e(TAG, "❌ Unknown characteristic for read request: ${characteristic?.uuid}")
    bluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null)
    }
    }

    override fun onServiceAdded(status: Int, service: BluetoothGattService?) {
    super.onServiceAdded(status, service)
    Log.d(TAG, "Service added: ${service?.uuid} (Status: $status)")

    }

    }

    //Function used to start advertising the device.
    fun startAdvertising() {

    Log.d(TAG, "Starting BLE advertising...")

    // //Make sure the GATT services are properly set up first.
    // if (!setupGattServer()) {
    // Log.e(TAG, "Failed to set up GATT server! Not advertising.")
    // return
    // }

    Handler(Looper.getMainLooper()).postDelayed({
    //Check Bluetooth Adapter status before starting server.
    if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled) {
    Log.e(TAG, "Bluetooth is disabled. Cannot start advertising.")
    }

    if (bluetoothAdapter == null || !bluetoothAdapter.isMultipleAdvertisementSupported) {
    Log.e(TAG, "BLE not supported on this device")
    }

    val advertiseSettings = AdvertiseSettings.Builder()
    .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
    .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
    .setConnectable(true)
    .build()

    val advertiseData = AdvertiseData.Builder()
    .setIncludeDeviceName(true)
    .addServiceUuid(ParcelUuid(APP_UUID)) //This ensures that the service UUID is advertised
    .build()

    bluetoothLeAdvertiser?.startAdvertising(
    advertiseSettings,
    advertiseData,
    advertiseCallback
    ) //Starts advertising.
    Log.d(TAG, "Starting BLE advertising.")
    }, 2000) //Delay of 2 seconds to give the GATT server time to be ready.
    }

    fun stopAdvertising() {
    bluetoothLeAdvertiser?.stopAdvertising(advertiseCallback) //Stops advertising.
    Log.d(TAG, "Stopping BLE advertising.")
    }

    private val advertiseCallback = object : AdvertiseCallback() {
    override fun onStartSuccess(settingsInEffect: AdvertiseSettings?) {
    super.onStartSuccess(settingsInEffect)
    Log.d(TAG, "BLE advertising started successfully.")
    //startGattServer() //Starts the GATT server.
    }

    override fun onStartFailure(errorCode: Int) {
    super.onStartFailure(errorCode)
    Log.e(TAG, "BLE advertising failed with error code: $errorCode.")
    }
    }


    fun sendDataToPi(data: String): Boolean {
    Log.d(TAG, " Attempting to send: $data")

    if (connectedDevices.isEmpty()) {
    Log.e(TAG, "No connected devices (tracked manually). Checking Bluetooth Manager...")

    val detectedDevices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT)
    if (detectedDevices.isNotEmpty()) {
    Log.d(TAG, " Bluetooth Manager reports ${detectedDevices.size} connected devices. Updating manual list.")
    connectedDevices.addAll(detectedDevices)
    } else {
    Log.e(TAG, "Still no devices detected. Cannot send: $data")
    return false
    }
    }

    // // Ensure the GATT server is still valid
    // if (bluetoothGattServer == null) {
    // Log.e(TAG, "GATT server is null! Restarting BLE...")
    // val success = setupGattServer()
    // if (!success) return false
    // }

    // Get updated GATT service
    val service = bluetoothGattServer?.getService(APP_UUID)
    if (service == null) {
    Log.e(TAG, "❌ Service $APP_UUID not found! Retrying...")
    bluetoothGattServer?.services?.forEach { Log.e(TAG, "Registered Service: ${it.uuid}") }
    Handler(Looper.getMainLooper()).postDelayed({
    val recheckedService = bluetoothGattServer?.getService(APP_UUID)
    if (recheckedService == null) {
    Log.e(TAG, "❌ Service still missing! Restarting BLE...")
    restartBLE()
    }
    }, 2000) // Wait 2 seconds before retrying
    return false
    } else {
    Log.d(TAG, "Service $APP_UUID found.")
    }

    val characteristic = service.getCharacteristic(RX_APP_UUID)
    if (characteristic == null) {
    Log.e(TAG, "❌ Characteristic not found! Restarting BLE...!")
    restartBLE()
    return false
    } else {
    Log.d(TAG, "Characteristic ${RX_APP_UUID} found.")
    }

    //Ensure that a device is connected before writing.
    val devicesToSend = connectedDevices.toList()
    if (devicesToSend.isEmpty()) {
    Log.e(TAG, "❌ No connected devices (tracked manually). Cannot send: $data")
    return false
    }

    devicesToSend.forEach { device ->
    bluetoothGattServer?.notifyCharacteristicChanged(device, characteristic, false)
    Log.d(TAG, " Sent to ${device.address}: $data")
    }

    characteristic.value = data.toByteArray()

    //Notify all connected devices.
    connectedDevices.forEach { device ->
    bluetoothGattServer?.notifyCharacteristicChanged(device, characteristic, false)
    Log.d(TAG, " Sent to ${device.address}: $data")
    }

    return true
    }

    fun restartBLE() {
    Log.d(TAG, "Restarting BLE services...")

    stopAdvertising() //Stop advertising.

    bluetoothGattServer?.clearServices() //Clear old services.


    startAdvertising() //Restart advertising.

    Log.d(TAG, "BLE services restarted.")
    }

    }


    This is the python code:

    #!/usr/bin/env python3

    import rclpy
    from rclpy.node import Node
    from std_msgs.msg import Int16, String
    from bluetooth_app_msgs.srv import SendValue, ConnectDevice # ROS2 services
    import asyncio
    from bleak import BleakScanner, BleakClient
    import struct
    import time

    # UUIDs from the Android BLE server.
    ANDROID_DEVICE_NAME = "Christopher's A14" # Android device name
    ANDROID_MAC_ADDRESS = "94:E1:29:E2:0B:51" # Android MAC address.
    SERVICE_UUID = "00001101-0000-1000-8000-00805f9b34fb".lower()
    TX_CHAR_UUID = "00002202-0000-1000-8000-00805f9b34fb".lower()
    RX_CHAR_UUID = "00002201-0000-1000-8000-00805f9b34fb".lower()

    async def find_device():
    """Scans for the Android BLE device and returns its address."""
    print("Scanning for BLE devices...")

    while True: # Keep scanning until device is found
    try:
    devices = await BleakScanner.discover()

    if not devices:
    print("No BLE devices found. Retrying in 5 seconds...")
    await asyncio.sleep(5)
    continue

    for device in devices:
    print(f"Found: {device.name} - {device.address}")
    if device.name == ANDROID_DEVICE_NAME:
    print(f"Found target device: {device.name} at {device.address}")
    return device.address

    except Exception as e:
    print(f"Error scanning for BLE device: {e}. Retrying in 5 seconds...")
    await asyncio.sleep(5)

    async def connect_to_device(device_address):
    """Keeps the connection alive and continuously retries service discovery."""
    print("Attempting to connect to Android device...")
    print(f"Connecting to {device_address}...")

    client = BleakClient(device_address)

    try:
    await client.connect() # Explicitly try to connect.
    if not client.is_connected:
    print("Connection failed!")
    await asyncio.sleep(5)
    return

    print("Successfully connected to the Android device!")

    #Keep the connection alive and retry service discovery.
    attempts = 0
    while client.is_connected and attempts < 10:
    print(f"Attempt {attempts + 1}: Discovering services...")
    services = await client.get_services()
    found_services = [service.uuid for service in services]

    if SERVICE_UUID in found_services:
    print(f"Found expected service: {SERVICE_UUID}.")
    return
    else:
    print(f"Expected service {SERVICE_UUID} not found! Retrying...")
    await asyncio.sleep(2)
    attempts += 1

    print("Service discovery failed. Disconnecting...")
    finally:
      await client.disconnect() #Explicitly disconnect when connection fails.
      print("Disconnected from the device.")

    async def communicate_with_android(device_address):
    """Connects to the Android BLE device and exchanges data."""
    while True: # Keep retrying connection if it fails
    try:
    async with BleakClient(device_address) as client:
    print("Connected to Android BLE device!")

    # Wait before attempting to find services.
    print("Waiting 5 seconds for services to initialize...")
    await asyncio.sleep(5)

    # Ensure that the GATT service and characteristics exist.
    services = await client.get_services()
    print("Available services:")
    found_services = []
    for service in services:
    print(f" - {service.uuid}")
    found_services.append(service.uuid)
    for characteristic in service.characteristics:
    print(f" - {characteristic.uuid} (Properties: {characteristic.properties})")

    #Validate service presence.
    if SERVICE_UUID not in found_services:
    print(f"Error: Service {SERVICE_UUID} not found!")
    return

    # Get the expected service.
    print(f"Waiting 5 seconds for services to initialize...")
    await asyncio.sleep(5)
    service = client.services.get_service(SERVICE_UUID)
    print(f"Found service {SERVICE_UUID}!")

    # Print all characteristics of the found service.
    for char in service.characteristics:
    print(f" - Characteristic: {char.uuid} (Properties: {char.properties})")

    # Check if characteristics exist
    if TX_CHAR_UUID not in [char.uuid for char in service.characteristics]:
    print("TX Characteristic not found!")
    return

    if RX_CHAR_UUID not in [char.uuid for char in service.characteristics]:
    print("RX Characteristic not found!")
    return
                            
                            #Discover services.
                            await client.services #Ensure the services are loaded.
                            print("Services discovered!")
                            
                            async def notification_handler(sender, data):
                                  received_command = data.decode("utf-8")
                                  print(f"Received command from Android: {received_command}.")
                                  
                            print(f"Subscribing to the notifications for the RX Characteristic {RX_CHAR_UUID}...")
                            await client.start_notify(RX_CHAR_UUID, notification_handler)
                            
                            #Keep the connection open indefinitely.
                            while client.is_connected:
                                  await asyncio.sleep(1) #Keeps the event loop alive.
                            

    except Exception as e:
    print(f"Connection failed: {e}. Retrying...")
    await asyncio.sleep(5) # Retry after delay
    continue

    async def main():
    """Main function to find and connect to the Android BLE device."""
    device_address = await find_device()
    if device_address:
    await connect_to_device(device_address)
    await communicate_with_android(device_address)

    if __name__ == "__main__":
    asyncio.run(main())

    Continue reading...

Compartilhe esta Página