Initial commit: ESP32-S3-RLCD-4.2 hardware utility classes and demo

This commit is contained in:
Adolfo Reyna
2026-05-31 22:44:49 -04:00
commit 558ee43149
14 changed files with 1295 additions and 0 deletions
+140
View File
@@ -0,0 +1,140 @@
import bluetooth
import struct
import time
# BLE UUID definitions (Nordic UART Service)
_UART_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_TX = (
bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"),
bluetooth.FLAG_NOTIFY,
)
_UART_RX = (
bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"),
bluetooth.FLAG_WRITE | bluetooth.FLAG_WRITE_NO_RESPONSE,
)
_UART_SERVICE = (_UART_UUID, (_UART_TX, _UART_RX))
# BLE IRQ constants
_IRQ_CENTRAL_CONNECT = 1
_IRQ_CENTRAL_DISCONNECT = 2
_IRQ_GATTS_WRITE = 3
class BLEUART:
"""A helper class to manage BLE UART (Serial Over BLE) for raw text data exchange."""
def __init__(self, ble=None, name="ESP32-S3-RLCD"):
self._ble = ble if ble is not None else bluetooth.BLE()
self._ble.active(True)
self._ble.irq(self._irq)
# Register UART service
((self._tx_handle, self._rx_handle),) = self._ble.gatts_register_services((_UART_SERVICE,))
self._connections = set()
self._rx_callback = None
self._name = name
self._advertise()
def _irq(self, event, data):
# Handle connection, disconnection, and writes
if event == _IRQ_CENTRAL_CONNECT:
conn_handle, _, _ = data
self._connections.add(conn_handle)
print(f"BLE Central connected (handle: {conn_handle})")
elif event == _IRQ_CENTRAL_DISCONNECT:
conn_handle, _, _ = data
if conn_handle in self._connections:
self._connections.remove(conn_handle)
print(f"BLE Central disconnected (handle: {conn_handle})")
# Restart advertising
self._advertise()
elif event == _IRQ_GATTS_WRITE:
conn_handle, value_handle = data
if value_handle == self._rx_handle:
# Read received data
data_received = self._ble.gatts_read(self._rx_handle)
if self._rx_callback:
try:
decoded = data_received.decode("utf-8").strip()
self._rx_callback(decoded)
except Exception as e:
# Fallback for binary / non-UTF-8 data
self._rx_callback(data_received)
def _advertise(self):
# Generate advertising payload (flags + service UUID = 3 + 18 = 21 bytes)
adv_data = self._build_advertising_payload(services=[_UART_UUID], include_flags=True)
# Generate scan response payload (device name, no flags = 2 + len(name) bytes)
resp_data = self._build_advertising_payload(name=self._name, include_flags=False)
self._ble.gap_advertise(100000, adv_data=adv_data, resp_data=resp_data)
print(f"BLE advertising started as '{self._name}'")
def _build_advertising_payload(self, name=None, services=None, include_flags=True):
"""Generates standard BLE advertising packet format."""
payload = bytearray()
def append(ad_type, value):
payload.append(len(value) + 1)
payload.append(ad_type)
payload.extend(value)
# Flags: General discoverable mode, BR/EDR not supported
if include_flags:
append(0x01, struct.pack("B", 0x02 | 0x04))
# Name
if name:
append(0x09, name.encode("utf-8"))
# Services UUID
if services:
for uuid in services:
# UUID bytes are in little-endian order
append(0x07, bytes(uuid))
return bytes(payload)
def on_rx(self, callback):
"""Register a callback function to handle incoming BLE messages.
Args:
callback (function): Function accepting a single string argument.
"""
self._rx_callback = callback
return callback
def write(self, data):
"""Sends data (string or bytes) to all connected BLE Centrals."""
if not self.is_connected():
return False
# Ensure it is bytes
if isinstance(data, str):
data = data.encode("utf-8")
try:
# Write value to local GATT DB first
self._ble.gatts_write(self._tx_handle, data)
# Notify all connected clients
for conn_handle in self._connections:
self._ble.gatts_notify(conn_handle, self._tx_handle, data)
return True
except Exception as e:
print(f"Error sending BLE data: {e}")
return False
def is_connected(self):
"""Returns True if at least one central is connected."""
return len(self._connections) > 0
def close(self):
"""Disconnect and stop BLE."""
self._ble.gap_advertise(None) # Stop advertising
for conn_handle in self._connections:
self._ble.gap_disconnect(conn_handle)
self._ble.active(False)
print("BLE closed.")