Python

Pybluez Python send file via Bluetooth

In this article, we would use the Raspberry Pi 4 and Samsung tablet. This is server-client connection. We create an advertise service.

Bluetooth Low Energy (BLE) has two ways of communicating. The first one is using advertisements, where a BLE peripheral device broadcasts packets to every device around it. The receiving device can then act on this information or connect to receive more information. This is essentially what Beacons do: they just transmit packets. The second way to communicate is to receive packets using a connection, where both the peripheral and central send packets. I introduce two ways to do this.

Installation

Please follow the instruction in Reference section.

sudo apt-get install bluez python3-bluez
sudo hciconfig hci0 piscan

Use Advertise Service

Advertising is by design unidirectional. A Central device can’t send any data to the Peripheral device without a connection. But a single peripheral can advertise to multiple masters in the area.

Full Server Code

import bluetooth
import time

def sendFile(client_sock):
    print("sending file...")
    client_sock.sendall(open("./data.csv", "rb").read())
    print(open("./data.csv", "rb").read())
    print("data.csv file sent")

def receiveData(client_sock):
    while True:
        try:
            data = client_sock.recv(1024)
            if not data:
                break
            print("Received", data)
        except OSError:
            print("OSError")
            client_sock.close()
            pass

def main(sendFile, receiveData):
    server_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
    server_sock.bind(("", bluetooth.PORT_ANY))
    server_sock.listen(1)

    port = server_sock.getsockname()[1]

    uuid = "94f39d29-7d6d-437d-973b-fba39e49d4ee"

    bluetooth.advertise_service(server_sock, "SampleServer", service_id=uuid,
                            service_classes=[uuid, bluetooth.SERIAL_PORT_CLASS],
                            profiles=[bluetooth.SERIAL_PORT_PROFILE],
                            # protocols=[bluetooth.OBEX_UUID]
                            )

    print("Waiting for connection on RFCOMM channel", port)
    client_sock, client_info = server_sock.accept()
    print("Accepted connection from", client_info)
    time.sleep(3)
    sendFile(client_sock)
    # receiveData(client_sock)

    print("Disconnected.")

    client_sock.close()
    server_sock.close()
    print("All done.")

main(sendFile, receiveData)

The socket will wait for a connection. It then sends a file to client (tablet) as binary.

def sendFile(client_sock):
    print("sending file...")
    client_sock.sendall(open("./data.csv", "rb").read())
    print(open("./data.csv", "rb").read())
    print("data.csv file sent")

Dart Client Code

final flutterReactiveBle = FlutterReactiveBle();

  void createCsv(fileName, filecontent) async {
    var csvStr = filecontent;
    print(csvStr.toString());

    var path = await ExternalPath.getExternalStoragePublicDirectory(
        ExternalPath.DIRECTORY_DOWNLOADS);

    path = '$path/$fileName';

    print("creating file...at $path");
    File file = File(path);
    file.writeAsString(csvStr);
    print("$path CREATED");
  }

  Future<void> bluetoothConnection() async {
    print(
        '****************************** connecting... ******************************');
    try {
      BluetoothConnection connection = await BluetoothConnection.toAddress(foundDeviceId);
      print('Connected to the device');

      // sendBluetoothMessage(connection);
      var fileName = "data.csv" ;
      var filecontent = '';
      connection.input?.listen((Uint8List data) {
        print('--------------------------------------------------------------');
        print('Data incoming: ${ascii.decode(data)}');
        // connection.output.add(data); // Sending data
        filecontent = filecontent + ascii.decode(data);

        if (ascii.decode(data).contains('!')) {
          connection.finish(); // Closing connection
          print('Disconnecting by local host');
        }
      }).onDone(() {
        print('FINAL CONTENT: $filecontent');
        createCsv(fileName, filecontent);
        print('Disconnected by remote request');
      });
    }
    catch (exception) {
      print('Cannot connect, exception occured');
    }
  }

  void sendBluetoothMessage(BluetoothConnection connection) {
    connection!.output.add(Uint8List.fromList(utf8.encode("Will says hello" + "\r\n")));
  }

I have commented out the code for sending back message to server. Play around if you want. In this code, we want to concatenate the data. Because server splits the entire data to many parts. When we use the listen method which means we listen for the whole data being sent in loop. When the last part was sent, it will trigger onDone callback.

connection.input?.listen((Uint8List data) {
        
      }).onDone(() {
        print('FINAL CONTENT: $filecontent');
        createCsv(fileName, filecontent);
        print('Disconnected by remote request');
      });

Hence, we want to put the code for creating CSV file at this callback when the whole data concatenated.

Use OBEX service built-in tablet

The method, we only send the file directly from server to tablet using OBEX Object Push service which is existing in tablet.

from bluetooth import *
from PyOBEX.client import Client
import sys

addr = sys.argv[1]
print("Searching for OBEX service on %s" % addr)

service_matches = find_service(name='OBEX Object Push', address = addr )
# print(find_service(address = addr ))
if len(service_matches) == 0:
    print("Couldn't find the service.")
    sys.exit(0)

first_match = service_matches[0]
port = first_match["port"]
name = first_match["name"]
host = first_match["host"]

print("Connecting to \"%s\" on %s" % (name, host))
client = Client(host, port)
client.connect()
client.put("Bluetooth/test.txt", open("./test.txt", "rb").read())
client.disconnect()

Using this way, we have to press the confirm button on tablet every time file transfer. We can’t do more at client side. What we can do is listening for Download folder if any file created. But this is tricky. Many files which are not what we want.

Bonus

We might want to place the file name inside csv file. Because bluetooth splits file to many parts to send. Below is python code to add the File Name at first row.

import csv

path = 'salary_123.csv'
List = ['File Name:', 'salary_123.csv']
with open(path,newline='') as f:
    r = csv.reader(f)
    data = [line for line in r]
    print(data[0][0])
if (data[0][0] != 'File Name:'):
    with open(path,'w',newline='') as f:
        w = csv.writer(f)
        w.writerow(List)
        w.writerows(data)

Reference

https://github.com/EnableTech/raspberry-bluetooth-demo

https://github.com/EnableTech/raspberry-bluetooth-demo

https://pythonic.rapellys.biz/articles/interfacing-with-bluetooth-devices-using-python/

https://stackoverflow.com/questions/37913796/bluetooth-error-no-advertisable-device

https://opencoursehub.cs.sfu.ca/bfraser/grav-cms/ensc351/links/files/2022-student-howtos/ConnectAndroidAppToPythonBluetoothServer.pdf

https://bluedot.readthedocs.io/en/latest/pairpiandroid.html

1