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
libraryrequests@2.31.0
libraryasyncio
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
, andrequests
. - We define two data models (
OTPResponse
andBLEPayload
) using Python'sdataclasses
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 theOTPResponse
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:
- Retrieve an OTP from the server.
- Discover BLE devices and find the one with the specified service UUID.
- Connect to the BLE device.
- Send the OTP payload to the BLE device.
- 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.