Skip to main content

Python Quickstart

This tutorial will guide you through creating a Python script to connect to a BLE device, retrieve an OTP from a server, and send the OTP to the BLE device. We'll use the bleak library for BLE interactions and requests for HTTP requests.

Prerequisites

This script works for the below requisite, but may also work for older or newer versions. Make sure you are within 5 metres of the device.

  • Python 3.12+
  • bleak@0.21.1 library
  • requests@2.31.0 library
  • asyncio library (part of the Python standard library)

Step 1: Install Required Libraries

First, install the necessary libraries using pip:

pip install bleak requests

Step 2: Create Your Python Script

Create a file named main.py and add the following code:

1. Import Required Libraries and Define Data Models

import asyncio
from bleak import BleakClient, BleakScanner
import requests
from dataclasses import dataclass

@dataclass
class OTPResponse:
service_uuid: str
characteristic_uuid: str
datetime: str
otp: str
device: any

@dataclass
class BLEPayload:
datetime: str
otp: str
action: str

Explanation:

  • We import necessary libraries: asyncio, BleakClient, BleakScanner, and requests.
  • We define two data models (OTPResponse and BLEPayload) using Python's dataclasses module for structured data handling.

2. Define the Function to Retrieve OTP from the Server

def get_otp(device_id, access_token) -> OTPResponse:
url = "https://getotp-lygrxayxsa-uc.a.run.app"

headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}

params = {
"deviceId": device_id
}

response = requests.get(url, headers=headers, params=params)
response.raise_for_status() # Raise an exception for HTTP errors (4xx or 5xx)
data = response.json()
print("Received Response: ", data)
return OTPResponse(
characteristic_uuid=data["device"]["characteristicUUID"],
service_uuid=data["device"]["serviceUUID"],
datetime=data["datetime"],
otp=data["otp"],
device=data["device"]
)

Explanation:

  • The get_otp function sends an HTTP GET request to retrieve an OTP for a specified device.
  • The response is parsed and returned as an OTPResponse object.

3. Define the Function to Create the BLE Payload

def create_BLE_payload(resp: OTPResponse) -> BLEPayload:
return {
"datetime": resp.datetime,
"otp": resp.otp,
"action": "press"
}

Explanation:

  • The create_BLE_payload function creates a payload dictionary from the OTPResponse object to be sent to the BLE device.

4. Define the Function to Find the BLE Device by Service UUID

async def find_ble_device(service_uuid: str) -> str | None:
devices = await BleakScanner.discover()
for device in devices:
if device.metadata["uuids"]:
for uuid in device.metadata["uuids"]:
if uuid == service_uuid:
return device.address
raise Exception("Failed to find BLE device.")

Explanation:

  • The find_ble_device function scans for BLE devices and returns the address of the device that matches the specified service UUID.

5. Define the Function to Send the Payload to the BLE Device

async def send_payload_to_ble_device(address: str, characteristic_uuid: str, payload: dict):
async with BleakClient(address) as client:
if client.is_connected:
print(f"Connected to {address}")

await client.write_gatt_char(characteristic_uuid, str(payload).encode())
print(f"Sent payload: {payload}")

response = await client.read_gatt_char(characteristic_uuid)
print(f"Received response: {response.decode()}")
else:
print(f"Failed to connect to {address}")
raise Exception("Failed to connect to BLE device.")

Explanation:

  • The send_payload_to_ble_device function connects to the specified BLE device using its address.
  • It sends the payload to the specified characteristic UUID and reads the response from the BLE device.

6. Define the Main Function to Execute the Workflow

async def __main__():
api_key = "<<YOUR_API_KEY>>"
device_id = "<<YOUR_DEVICE_ID>>"

print("Retrieving OTP...")
resp = None
try:
resp = get_otp(device_id, api_key)
except:
print("Failed to retrieve OTP.")
return

print("OTP Received.")
payload = create_BLE_payload(resp)

print("Connecting via BLE...")

try:
device_address = await find_ble_device(resp.service_uuid)
except:
print("Failed to find BLE device.")
return

print("BLE Device found.")
print("Sending BLE request...")

try:
await send_payload_to_ble_device(device_address, resp.characteristic_uuid, payload)
except:
print("Failed to send BLE request.")
return

print("Acknowledgment received.")

Explanation:

  • The __main__ function coordinates the overall workflow: retrieving the OTP, creating the BLE payload, finding the BLE device, and sending the payload to the BLE device.

7. Run the Main Function

if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(__main__())

Explanation:

  • This code block ensures the main function runs when the script is executed.

Step 3: Run Your Script

To run your script, simply execute main.py:

python main.py

This script will:

  1. Retrieve an OTP from the server.
  2. Discover BLE devices and find the one with the specified service UUID.
  3. Connect to the BLE device.
  4. Send the OTP payload to the BLE device.
  5. Read and print the response from the BLE device.

Conclusion

You've now created a Python script that interacts with a BLE device using an OTP retrieved from a server. This tutorial demonstrated how to use bleak for BLE operations and requests for HTTP interactions. Customize the script further to fit your specific use case and hardware setup.