B4R Library rBLEServer ESP32

B4R Library rBLEServer

Purpose
rBLEServer
is an open source library to create a Bluetooth Low Energy (BLE) server.

The purpose is to
  • run the BLE server on ESP32 hardware with sensors & actuators connected.
  • connect a single client to read sensor data or set the state of actuator(s).
This B4R library is
  • using the UART service and single characteristic for notify, read, write.
  • written in C++ (using the Arduino IDE 2.3.4 and the B4Rh2xml tool).
  • tested with an ESP32 Wrover Kit and the Arduino app BLE Scanner.
  • tested with B4R 4.00 (64 bit), ESP32 library 3.1.1.
Files
rBLEServer.zip archive contains the library and sample project.

Install
From the zip archive, copy the content of the library folder, to the B4R additional libraries folder keeping the folder structure.

Functions
Initialize BLE server
Name - Name of the BLE server.
NewDataSub - Sub to handle new data from the connected client.
ErrorSub - Sub to log an error code.
mtuSize - Size of the MTU between 23 (default) and 512.
B4X:
Initialize (Name As String , NewDataSub As SubVoidArray, Error As SubSubVoidByte , mtuSize As UInt)

Flag check client connected
B4X:
Connected() As Boolean

Write data to BLE client
data = Array as Bytes
B4X:
Write (data() As Byte)

Constants
Warning and error codes
B4X:
Byte WARNING_INVALID_MTU
Byte ERROR_INVALID_CHARACTERISTIC
Byte ERROR_EMPTY_DATA

Events
NewData
Handle new data received from the connected client.
B4X:
NewData(buffer() as byte)

Error
Handle errors BLE communication.
B4X:
Error(code as byte)

Examples
  • Basic - Simple example to start the BLE server and send string "hello" (see below).
  • InputOutput - Control traffic-light LED's via push-button or BLE server client (B4A) (see post #3).
  • EnvMonitor - Read regular intervals DHT22 Temp+Hum sensor data, set display TM1637 & RGB LEDs T+H indicators, advertise serialized data to BLE server client (B4A) (see post #4).
  • Home Assistant UI (HA) - Custom Integration BLE Server DHT; ESP32 advertises data to 2 entities; Listens to commands from HA to set Traffic-Light or advertisement timer-interval (see post #6).
  • UI application - B4J Pages with a Python BLE-TCP-Bridge (see post #7).
  • UI application - B4J Pages with PyBridge and Bleak (see post #10).
B4R Basic Example
Handle new data received from the connected client. No components connected.
B4X:
Sub Process_Globals
    Public Serial1 As Serial
    Private BLEServer As BLEServer
    Private MTUSize As UInt = 100
    Private bc As ByteConverter
End Sub

Private Sub AppStart
    Serial1.Initialize(115200)
    Log(CRLF, "[AppStart]")
    BLEServer.Initialize("BLEServer", "BLEServer_NewData", "BLEServer_Error", MTUSize)
    CallSubPlus("Test", 5000, 5)
End Sub

Private Sub Test(tag As Byte)
    BLEServer_Write("hello".GetBytes)
End Sub

Private Sub BLEServer_NewData(buffer() As Byte)
    Log("[BLEServer_NewData]buffer=",bc.HexFromBytes(buffer))
    Log("[BLEServer_NewData]connected=",BLEServer.Connected)
End Sub

Private Sub BLEServer_Error(code As Byte)
    Log("[BLEServer_Error]code=",code)
    Select code
        Case BLEServer.WARNING_INVALID_MTU
            Log("[WARNING][Initialize] MTU out of range 23-512, set default 23.")
        Case BLEServer.ERROR_INVALID_CHARACTERISTIC
            Log("[ERROR][Write] failed: No valid characteristic.")
        Case BLEServer.ERROR_INVALID_CHARACTERISTIC
            Log("[ERROR][Write] failed: No data.")
    End Select
End Sub

Private Sub BLEServer_Write(data() As Byte)
    If data == Null Then
        Log("[ERROR][BLEServer_Write] No data.")
        Return
    End If
    Log("[BLEServer_Write]data=",bc.HexFromBytes(data))
    BLEServer.Write(data)
End Sub

ToDo
  • Explore B4J UI Application with new PyBridge instead BLE-TCP-Bridge.
Licence
GNU General Public License v3.0.
 

Attachments

Last edited:
v0.8.0 (Build 20250207) (see post #1)
  • NEW: Add descriptor 0x2902.
  • NEW: Advertising the service UUID.
  • NEW: Example InputOutput with B4A Client
  • FIX: Various improvements.
Example InputOutput

1738929768963.png
 

Attachments

  • 1738927777686.png
    1738927777686.png
    163.6 KB · Views: 502
Last edited:
v0.8.4 (Build 20250212) (see post #1)
  • NEW: Example EnvMonitor
Example EnvMonitor
Read in regular intervals DHT22 Temperature & Humidity data, set display TM1637 (TTHH) & RGB LEDs T+H indicators, advertise serialized data (9 objects) to BLE server client (B4A). B4A notes: B4XPages app, B4XPreferencesDialog, additional digital font for T+H, B4RSerializer.

1739355067746.png
 
Last edited:
v0.8.5 (Build 20250224) (see post #1)
  • NEW: Set BLE transmit power to maximum (+9 dBm).
  • NEW: Example Home Assistant UI with DHT22 & Traffic-Light.
  • Fix: onWrite proper handle of value 0x00.
Example Home Assistant UI
Purpose is integrating an ESP32 (running as BLE Server) in Home Assistant (HA) as a Custom Integration named BLE Server DHT (device BLEServerDHT).
The ESP32 has a DHT22 sensor connected and advertises in regular intervals temperature (int), humidity (int) and listens to commands send from connect clients.
The HA Custom Integration BLE Server DHT connects to the BLEServer and listens to BLE advertisements, parses the payload and updates 2 entities.
In addition commands are send from HA and handled by the BLEServer, like setting a traffic-light or change timer-advertising interval.
The integration is added using the HA UI and not YAML (except for setting debug logs).
The custom component must be placed in the HA folder config/custom_components with folder name ble_server_dht.
After copied the files to the custom component folder > restart HA > Add Integration BLE Server DHT > Check HA Logs (settings > system > logs).
To debug, add to HA configuration.yaml:
YAML:
logger:
  logs:
    custom_components.ble_server_dht: debug

1740417718897.png


B4R Code Snippet (without comments,logging)
[CODE lang="b4x" title="B4R Code Snippet"]
Sub Process_Globals
Private VERSION As String = "rBLEServer HomeAssistant Example v20250224"
Public Serial1 As Serial
Private BLEServer As BLEServer
Private MTUSize As UInt = BLEServer.MTU_SIZE_MIN
Private DHT As DHTESP
Private DHTPinNumber As UInt = 0x04
Private Temperature As Int
Private Humidity As Int
Private LEDRed As Pin
Private LEDRedGPIO As Byte = 25
Private LEDYellow As Pin
Private LEDYellowGPIO As Byte = 26
Private LEDGreen As Pin
Private LEDGreenGPIO As Byte = 27
Private TimerDataAdvertising As Timer
Private TimerDataAdvertisingInterval As ULong = 15000
Private CMD_LENGTH As Byte = 2
Private CMD_LED_RED = 1, CMD_LED_YELLOW = 2, CMD_LED_GREEN = 3 As Byte
Private CMD_TIMER As Byte = 4
Private bc As ByteConverter
End Sub

Private Sub AppStart
Serial1.Initialize(115200)
DHT.Setup22(DHTPinNumber)
LEDRed.Initialize(LEDRedGPIO, LEDRed.MODE_OUTPUT)
LEDYellow.Initialize(LEDYellowGPIO, LEDYellow.MODE_OUTPUT)
LEDGreen.Initialize(LEDGreenGPIO, LEDGreen.MODE_OUTPUT)
BLEServer.Initialize("BLEServer", "BLEServer_NewData", "BLEServer_Error", MTUSize)
TimerDataAdvertising.Initialize("TimerDataAdvertising_Tick", TimerDataAdvertisingInterval)
TimerDataAdvertising.Enabled = True
TimerDataAdvertising_Tick
End Sub

Sub TimerDataAdvertising_Tick
Temperature = DHT.GetTemperature * 100
Humidity = DHT.GetHumidity * 100
Dim data(4) As Byte
Dim t(2) As Byte = BytesFromInt(Temperature, True)
Dim h(2) As Byte = BytesFromInt(Humidity, True)
data(0) = t(0)
data(1) = t(1)
data(2) = h(0)
data(3) = h(1)
BLEServer_Write(data)
End Sub

Private Sub BLEServer_NewData(buffer() As Byte)
If buffer.Length >= CMD_LENGTH Then
Dim cmd As Byte = buffer(0)
Select cmd
Case CMD_LED_RED
SetTrafficLight(LEDRed, buffer(1))
Case CMD_LED_YELLOW
SetTrafficLight(LEDYellow, buffer(1))
Case CMD_LED_GREEN
SetTrafficLight(LEDGreen, buffer(1))
Case CMD_TIMER
Dim timerinterval As Byte = buffer(1)
TimerDataAdvertising.Enabled = False
If timerinterval > 0 Then
TimerDataAdvertising.Interval = timerinterval * 1000
TimerDataAdvertising.Enabled = True
End If
Case Else
Log("[BLEServer_NewData][WARNING] Unknown Command ", cmd)
End Select
Else
Log("[BLEServer_NewData][ERROR] Command length out of range, len is ", buffer.Length, " instead minumum ", CMD_LENGTH)
End If
End Sub

Private Sub BLEServer_Error(code As Byte)
Select code
Case BLEServer.WARNING_INVALID_MTU
Log("[WARNING][Initialize] MTU out of range 23-512, default is set (23).")
Case BLEServer.ERROR_INVALID_CHARACTERISTIC
Log("[ERROR][Write] failed: No valid characteristic.")
Case BLEServer.ERROR_EMPTY_DATA
Log("[ERROR][Write] failed: No data.")
End Select
End Sub

Private Sub BLEServer_Write(data() As Byte)
If data == Null Then
Log("[ERROR][BLEServer_Write] No data.")
Return
End If
Log("[BLEServer_Write]data=", bc.HexFromBytes(data))
BLEServer.Write(data)
End Sub

Private Sub BLEServer_WriteAdvertisement(data() As Byte)
If data == Null Then
Log("[ERROR][BLEServer_WriteAdvertisement] No data.")
Return
End If
Log("[BLEServer_WriteAdvertisement]data=", bc.HexFromBytes(data))
BLEServer.WriteAdvertisement(data)
End Sub

Public Sub SetTrafficLight(ledpin As Pin, state As Byte)
ledpin.DigitalWrite(IIf(state == 1, True, False))
End Sub

Public Sub HexFromByte(b As Byte) As String
Return bc.HexFromBytes(Array As Byte(b))
End Sub

Public Sub HexFromInt(i As Int) As String
Dim b() As Byte = bc.IntsToBytes(Array As Int(i))
'Log("[HexFromInt]i=", i, ",bytes=",b(0),b(1))
Return bc.HexFromBytes(b)
End Sub

Public Sub BytesFromLong(value As Long, littleendian As Boolean) As Byte()
Dim raf As RandomAccessFile
'Set the return byte array size = 4
Dim b(4) As Byte
raf.Initialize(b, littleendian)
raf.WriteULong32(value, 0)
Return b
End Sub

Public Sub BytesFromInt(value As Int, littleendian As Boolean) As Byte()
Dim raf As RandomAccessFile
'Set the return byte array size = 2
Dim b(2) As Byte
raf.Initialize(b, littleendian)
raf.WriteUInt16(value, 0)
Return b
End Sub
[/CODE]
 
Last edited:
v0.8.7 (Build 20250302) (see post #1)
  • NEW: Example B4J Pages UI application with a Python BLE-TCP-Bridge.
Example BLE-TCP-Bridge
B4J B4XPages UI application listening to advertisements from the BLE Server and sent commands to the BLE server.

1740908124122.png


This example has 3 components:
B4R BLE Server
  • Advertises via Bluetooth Low Energy DHT22 sensor data temperature & humidity.
  • Listening incoming data: set Traffic-Light RED LED or timer-advertising interval.
  • Hardware ESP32-Wrover-Kit, name BLEServer with connected components DHT22 sensor, Traffic-Light LED.
Python BLE-TCP-Bridge
This is a Python-based asynchronous application (using Bleak, asyncio) that enables bidirectional communication between a Bluetooth Low Energy (BLE) device and a TCP network. It acts as a bridge, allowing TCP clients to send commands to a BLE device and receive BLE notifications via TCP.
Features
  • Connects to a BLE device using either a MAC address or device name.
  • Runs a TCP server that listens for client connections.
  • Relays TCP commands to the BLE device via GATT write operations.
  • Forwards BLE notifications to all connected TCP clients.
  • Supports an optional initial HEX command sent upon BLE connection.
  • Implements automatic BLE reconnection on failure.
  • Handles multiple TCP clients simultaneously.
B4J B4XPages UI Application
  • Starting & connecting to the BLE-TCP-Bridge.
  • Listening advertised data & sets the Traffic-Light RED LED or timer-advertising interval.
  • Note B4J 10.20 BETA #3 used.
B4R Code Snippet (without comments,logging)
[CODE lang="b4x" title="B4R Code Snippet"]
Sub Process_Globals
Private VERSION As String = "rBLEServer Example BLETCPBridge v20250302"
Public Serial1 As Serial
Private BLEServer As BLEServer
Private MTUSize As UInt = BLEServer.MTU_SIZE_MIN
Private DHT As DHTESP
Private DHTPinNumber As UInt = 0x04
Private Temperature As Int
Private Humidity As Int
Private LEDRed As Pin
Private LEDRedGPIO As Byte = 25 '0x19
Private LEDYellow As Pin
Private LEDYellowGPIO As Byte = 26 '0x1A
Private LEDGreen As Pin
Private LEDGreenGPIO As Byte = 27 '0x1B
Private TimerDataAdvertising As Timer
Private TimerDataAdvertisingInterval As ULong = 5000 'milliseconds
Private CMD_LENGTH As Byte = 2
Private CMD_LED_RED = 1, CMD_LED_YELLOW = 2, CMD_LED_GREEN = 3 As Byte
Private CMD_TIMER As Byte = 4
Private bc As ByteConverter
End Sub

Private Sub AppStart
Serial1.Initialize(115200)
Log(CRLF, "[AppStart]", VERSION)
DHT.Setup22(DHTPinNumber)
LEDRed.Initialize(LEDRedGPIO, LEDRed.MODE_OUTPUT)
LEDYellow.Initialize(LEDYellowGPIO, LEDYellow.MODE_OUTPUT)
LEDGreen.Initialize(LEDGreenGPIO, LEDGreen.MODE_OUTPUT)
BLEServer.Initialize("BLEServer", "BLEServer_NewData", "BLEServer_Error", MTUSize)
TimerDataAdvertising.Initialize("TimerDataAdvertising_Tick", TimerDataAdvertisingInterval)
TimerDataAdvertising.Enabled = True
TimerDataAdvertising_Tick
End Sub

Sub TimerDataAdvertising_Tick
Temperature = DHT.GetTemperature * 100
Humidity = DHT.GetHumidity * 100
Dim Interval As UInt = TimerDataAdvertising.Interval
Dim data(6) As Byte
Dim t(2) As Byte = BytesFromInt(Temperature, True)
Dim h(2) As Byte = BytesFromInt(Humidity, True)
Dim i(2) As Byte = BytesFromInt(Interval, True)
data(0) = t(0)
data(1) = t(1)
data(2) = h(0)
data(3) = h(1)
data(4) = i(0)
data(5) = i(1)
BLEServer_Write(data)
End Sub

Private Sub BLEServer_NewData(buffer() As Byte)
If buffer.Length >= CMD_LENGTH Then
Dim cmd As Byte = buffer(0)
Select cmd
Case CMD_LED_RED
SetTrafficLight(LEDRed, buffer(1))
Case CMD_LED_YELLOW
SetTrafficLight(LEDYellow, buffer(1))
Case CMD_LED_GREEN
SetTrafficLight(LEDGreen, buffer(1))
Case CMD_TIMER
Dim timerinterval As Byte = buffer(1)
TimerDataAdvertising.Enabled = False
If timerinterval > 0 Then
TimerDataAdvertising.Interval = timerinterval * 1000
TimerDataAdvertising.Enabled = True
End If
Case Else
Log("[BLEServer_NewData][WARNING] Unknown Command ", cmd)
End Select
Else
Log("[BLEServer_NewData][ERROR] Command length out of range, len is ", buffer.Length, " instead minumum ", CMD_LENGTH)
End If
End Sub

Private Sub BLEServer_Error(code As Byte)
Select code
Case BLEServer.WARNING_INVALID_MTU
Log("[WARNING][Initialize] MTU out of range 23-512, default is set (23).")
Case BLEServer.ERROR_INVALID_CHARACTERISTIC
Log("[ERROR][Write] failed: No valid characteristic.")
Case BLEServer.ERROR_EMPTY_DATA
Log("[ERROR][Write] failed: No data.")
End Select
End Sub

Private Sub BLEServer_Write(data() As Byte)
If data == Null Then
Return
End If
BLEServer.Write(data)
End Sub

Private Sub BLEServer_WriteAdvertisement(data() As Byte)
If data == Null Then
Return
End If
BLEServer.WriteAdvertisement(data)
End Sub

Public Sub SetTrafficLight(ledpin As Pin, state As Byte)
ledpin.DigitalWrite(IIf(state == 1, True, False))
End Sub

Public Sub HexFromByte(b As Byte) As String
Return bc.HexFromBytes(Array As Byte(b))
End Sub

Public Sub HexFromInt(i As Int) As String
Dim b() As Byte = bc.IntsToBytes(Array As Int(i))
Return bc.HexFromBytes(b)
End Sub

Public Sub BytesFromLong(value As Long, littleendian As Boolean) As Byte()
Dim raf As RandomAccessFile
Dim b(4) As Byte
raf.Initialize(b, littleendian)
raf.WriteULong32(value, 0)
Return b
End Sub

Public Sub BytesFromInt(value As Int, littleendian As Boolean) As Byte()
Dim raf As RandomAccessFile
Dim b(2) As Byte
raf.Initialize(b, littleendian)
raf.WriteUInt16(value, 0)
Return b
End Sub
[/CODE]

B4J B4XPages MainPage Code Snippet (without comments,logging)
[CODE lang="b4x" title="B4J Code Snippet"]
Sub Class_Globals
Private VERSION As String = "rBLEServer Example BLETCPBridge v20250302"
Private Root As B4XView
Private xui As XUI
Private LabelBLEState As B4XView
Private LabelTemperature As B4XView
Private LabelHumidity As B4XView
Private LabelInterval As B4XView
Private CustomListViewLog As CustomListView
Private ButtonReconnect As B4XView
Private B4XSeekBarInterval As B4XSeekBar
Private LabelSeekBarIntervalValue As B4XView
Private ButtonIntervalSet As B4XView
Private CheckBoxLED As B4XView
Private Shl As Shell
Private BLE_TCP_BRIDGE_SCRIPT As String = "bletcpbridge.py"
Private CMD_STOP As String = "stop"
Private ClientSocket As Socket
Private AStream As AsyncStreams
Private SERVER_IP As String = "127.0.0.1"
Private SERVER_PORT As Int = 58001
Private bc As ByteConverter
End Sub

Public Sub Initialize
B4XPages.GetManager.LogEvents = True
End Sub

Private Sub B4XPage_Created (Root1 As B4XView)
Root = Root1
Root.LoadLayout("MainPage")
B4XPages.SetTitle(Me, VERSION)
LabelBLEState.Text = "Disconnected"
ButtonReconnect.Enabled = True
CheckBoxLED.Enabled = False
RunPythonScript($"${BLE_TCP_BRIDGE_SCRIPT}"$)
ClientSocket.Initialize("ClientSocket")
'ClientSocket_Connect is called in the Shell event StdErr after the bridge connects to the ESP32 via BLE
End Sub

Private Sub B4XPage_CloseRequest As ResumableSub
Dim command() As Byte = CMD_STOP.GetBytes("UTF8")
AStream_Write(command)
Sleep(100)
Return True
End Sub

Private Sub B4XPage_Background
'NOT USED
End Sub

Private Sub ButtonReconnect_Click
If Not(ClientSocket.Connected) Then
ClientSocket_Connect
End If
End Sub

Private Sub CheckBoxLED_CheckedChange(Checked As Boolean)
SetLED(Checked)
End Sub

Private Sub SetLED(state As Boolean)
Dim command() As Byte = IIf(state, Array As Byte(0x01, 0x01), Array As Byte(0x01, 0x00) )
If ClientSocket.Connected Then
AStream_Write(command)
CustomListViewLog_Insert($"${DateTime.time(DateTime.now)} :: Set LED state to ${IIf(state,"ON","OFF")}"$)
End If
End Sub

Private Sub ButtonIntervalSet_Click
Dim command(2) As Byte
command(0) = 0x04
command(1) = IntToByte(B4XSeekBarInterval.Value)
If ClientSocket.Connected Then
AStream_Write(command)
CustomListViewLog_Insert($"[B4XSeekBarInterval_ValueChanged] value=${B4XSeekBarInterval.Value}, hex=${bc.HexFromBytes(command)}"$)
End If
End Sub

Private Sub B4XSeekBarInterval_ValueChanged (Value As Int)
LabelSeekBarIntervalValue.Text = $"${Value} s."$
End Sub

Private Sub CustomListViewLog_Insert(item As String)
CustomListViewLog.InsertAt(0, CustomListViewLog_CreateItem(item), "")
'CustomListViewLog.Add(CustomListViewLog_CreateItem(item), "")
End Sub

Sub CustomListViewLog_CreateItem(item As String) As Pane
Dim pnl As B4XView = xui.CreatePanel("")
pnl.Color = xui.Color_White
pnl.SetLayoutAnimated(0, 0, 0, CustomListViewLog.AsView.Width, 30dip)
Dim lbl As B4XView = XUIViewsUtils.CreateLabel
lbl.Text = item
lbl.SetTextAlignment("CENTER", "LEFT")
lbl.Font = xui.CreateDefaultBoldFont(14)
pnl.AddView(lbl, 0, 0, CustomListViewLog.AsView.Width, 30dip)
Return pnl
End Sub

Public Sub RunPythonScript(script As String)
Shl.Initialize("shl", "python", Array(script))
Shl.WorkingDirectory = File.DirApp

Shl.RunWithOutputEvents(-1)
CustomListViewLog_Insert($"[BLE-TCP-Bridge] Waiting for BLE connection"$)
End Sub

Sub shl_ProcessCompleted (Success As Boolean, ExitCode As Int, StdOut As String, StdErr As String)
Log($"[shl_ProcessCompleted] Success=${Success}, ExitCode=${ExitCode}, StdOut=${StdOut}, StdErr=${StdErr}"$)
End Sub

Sub shl_StdOut (Buffer() As Byte, Length As Int)
Dim j As JSONParser
Try
j.Initialize(BytesToString(Buffer, 0, Length, "UTF8"))
Dim m As Map = j.NextObject
If m.Get("event") == "connect" Then
Dim status As Int = m.Get("status")
If status == 1 Then
ClientSocket_Connect
End If
End If
Catch
Log($"[Stdout][INFO]${LastException}"$)
End Try
End Sub

Sub shl_StdErr (Buffer() As Byte, Length As Int)
'TODO: Improve error handling
End Sub

Sub ClientSocket_Connect
Dim statemsg As String
ClientSocket.Connect(SERVER_IP, SERVER_PORT, 0)
Wait For (ClientSocket) ClientSocket_Connected (Successful As Boolean)
If Successful Then
AStream.Initialize(ClientSocket.InputStream, ClientSocket.OutputStream, "AStream")
End If
statemsg = IIf(Successful, "Connected", "Disconnected")
CallSubDelayed2(Me, "SetLabelBLEState", statemsg)
CheckBoxLED.Enabled = ClientSocket.connected
Log($"[ClientSocket_Connect] ${statemsg}"$)
End Sub

Sub SetLabelBLEState(msg As String)
LabelBLEState.Text = msg
End Sub

'Sub ClientSocket_Connected (Successful As Boolean)
'NOT NEEDED because using wait for in connect
'End Sub

Sub AStream_NewData( Buffer() As Byte )
Dim temperature As Float = IntFromBytes(Buffer(1),Buffer(0)) / 100
Dim humidity As Float = IntFromBytes(Buffer(3),Buffer(2)) / 100
Dim interval As Short = IntFromBytes(Buffer(5),Buffer(4)) / 1000
LabelTemperature.Text = $"${temperature} °C"$
LabelHumidity.Text = $"${humidity} %RH"$
LabelInterval.Text = $"Advertising interval"$
If interval <> B4XSeekBarInterval.Value Then
B4XSeekBarInterval.Value = interval
LabelSeekBarIntervalValue.Text = $"${interval} s"$
End If
CustomListViewLog_Insert($"${DateTime.time(DateTime.now)} :: ${temperature} °C, ${humidity} %RH, ${interval} s"$)
End Sub

Sub AStream_Write(command() As Byte)
If ClientSocket.Connected Then
AStream.Write(command)
End If
End Sub

Sub AStream_Error
Log("[AStream_Error] " & LastException.Message )
End Sub

Sub AStream_Terminated
Log("[AStream_Terminated]")
End Sub

Public Sub PrintMap(m As Map)
For Each key As String In m.Keys
Dim value As String = m.Get(key)
Log($"key: ${key}, value: ${value}"$)
Next
End Sub

Public Sub IntFromBytes(b1 As Byte, b2 As Byte) As Int
Dim hex As String = bc.HexFromBytes(Array As Byte(b1,b2))
Dim result As Int = Bit.ParseInt(hex, 16)
'Log($"[IntFromBytes] hex=${hex}, int=${result}"$)
Return result
End Sub

Public Sub IntToByte(value As Int) As Byte
If value >= 0 And value <= 255 Then
Dim result As Byte = value
End If
Return result
End Sub
[/CODE]
 
Last edited:
@rwblinn thanks so much for working on BLE and making base for new projects startups.
I am studying them and will jump into practically to play with them soon. Thanks. :)
Thanks and please share on progress made.

To note
The development of the Python BLE-TCP-Bridge was challenging esp. the task handling between the 2 classes BLEBridge & BLEServer - using Bleak & asyncio.
May be in the future, the new PyBridge could make communication simpler, by event & callback handling - bidirectional communication between the PyBridge & B4J.

Practical Project
Have started on a project to watch on a small display the solar panel production & battery state near the battery & inverter in the house basement.
Every time, I walk thru the basement, the battery is blinking but can not see its state except look at my smartphone which I do not have with me all-the-time.
Exploring two solutions = ESP32 to run the BLEServer plus display handling:
  • ESP32 + LCD2004 display + Traffic-Light running the BLEServer + Home Assistant Custom Integration (based on example post #6).
  • ESP32 + Cheap-Yellow-Display + Home Assistant Custom Integration (based on example post #6).
 
Last edited:
v0.8.9 (Build 20250310) (see post #1)
  • NEW: Example UI Application B4J Pages with PyBridge and Bleak (see this thread)

1741599027256.png


This example has 3 components:
B4R BLE Server
  • Advertises via Bluetooth Low Energy DHT22 sensor data temperature & humidity.
  • Listening incoming data: set Traffic-Light RED LED or timer-advertising interval.
  • Hardware ESP32-Wrover-Kit, name BLEServer with connected components DHT22 sensor, Traffic-Light LED.
PyBridge with Bleak
  • Connect to BLE Server.
  • Handle read, notify & write data from & to the BLE Server.
B4J B4XPages UI Application
  • Start PyBridge & connect to the BLE Server using Bleak.
  • Listen advertised data & set Traffic-Light RED LED or advertising interval.
  • Note developed with B4J 10.20 BETA #3, PyBridge 0.70, Bleak 1.01.
B4R Code Snippet (without comments,logging)
[CODE lang="b4x" title="B4R Code Snippet"]
Sub Process_Globals
Private VERSION As String = "rBLEServer Example BLEPyBridge v20250310"
Public Serial1 As Serial
Private BLEServer As BLEServer
Private MTUSize As UInt = BLEServer.MTU_SIZE_MIN
Private DHT As DHTESP
Private DHTPinNumber As UInt = 0x04
Private Temperature As Int
Private Humidity As Int
Private LEDRed As Pin
Private LEDRedGPIO As Byte = 25 '0x19
Private LEDYellow As Pin
Private LEDYellowGPIO As Byte = 26 '0x1A
Private LEDGreen As Pin
Private LEDGreenGPIO As Byte = 27 '0x1B
Private TimerDataAdvertising As Timer
Private TimerDataAdvertisingInterval As ULong = 5000 'milliseconds
Private CMD_LENGTH As Byte = 2
Private CMD_LED_RED = 1, CMD_LED_YELLOW = 2, CMD_LED_GREEN = 3 As Byte
Private CMD_TIMER As Byte = 4
Private bc As ByteConverter
End Sub

Private Sub AppStart
Serial1.Initialize(115200)
Log(CRLF, "[AppStart]", VERSION)
DHT.Setup22(DHTPinNumber)
LEDRed.Initialize(LEDRedGPIO, LEDRed.MODE_OUTPUT)
LEDYellow.Initialize(LEDYellowGPIO, LEDYellow.MODE_OUTPUT)
LEDGreen.Initialize(LEDGreenGPIO, LEDGreen.MODE_OUTPUT)
BLEServer.Initialize("BLEServer", "BLEServer_NewData", "BLEServer_Error", MTUSize)
TimerDataAdvertising.Initialize("TimerDataAdvertising_Tick", TimerDataAdvertisingInterval)
TimerDataAdvertising.Enabled = True
TimerDataAdvertising_Tick
End Sub

Sub TimerDataAdvertising_Tick
Temperature = DHT.GetTemperature * 100
Humidity = DHT.GetHumidity * 100
Dim Interval As UInt = TimerDataAdvertising.Interval
Dim data(6) As Byte
Dim t(2) As Byte = BytesFromInt(Temperature, True)
Dim h(2) As Byte = BytesFromInt(Humidity, True)
Dim i(2) As Byte = BytesFromInt(Interval, True)
data(0) = t(0)
data(1) = t(1)
data(2) = h(0)
data(3) = h(1)
data(4) = i(0)
data(5) = i(1)
BLEServer_Write(data)
End Sub

Private Sub BLEServer_NewData(buffer() As Byte)
If buffer.Length >= CMD_LENGTH Then
Dim cmd As Byte = buffer(0)
Select cmd
Case CMD_LED_RED
SetTrafficLight(LEDRed, buffer(1))
Case CMD_LED_YELLOW
SetTrafficLight(LEDYellow, buffer(1))
Case CMD_LED_GREEN
SetTrafficLight(LEDGreen, buffer(1))
Case CMD_TIMER
Dim timerinterval As Byte = buffer(1)
TimerDataAdvertising.Enabled = False
If timerinterval > 0 Then
TimerDataAdvertising.Interval = timerinterval * 1000
TimerDataAdvertising.Enabled = True
End If
Case Else
Log("[BLEServer_NewData][WARNING] Unknown Command ", cmd)
End Select
Else
Log("[BLEServer_NewData][ERROR] Command length out of range, len is ", buffer.Length, " instead minumum ", CMD_LENGTH)
End If
End Sub

Private Sub BLEServer_Error(code As Byte)
Select code
Case BLEServer.WARNING_INVALID_MTU
Log("[WARNING][Initialize] MTU out of range 23-512, default is set (23).")
Case BLEServer.ERROR_INVALID_CHARACTERISTIC
Log("[ERROR][Write] failed: No valid characteristic.")
Case BLEServer.ERROR_EMPTY_DATA
Log("[ERROR][Write] failed: No data.")
End Select
End Sub

Private Sub BLEServer_Write(data() As Byte)
If data == Null Then
Return
End If
BLEServer.Write(data)
End Sub

Private Sub BLEServer_WriteAdvertisement(data() As Byte)
If data == Null Then
Return
End If
BLEServer.WriteAdvertisement(data)
End Sub

Public Sub SetTrafficLight(ledpin As Pin, state As Byte)
ledpin.DigitalWrite(IIf(state == 1, True, False))
End Sub

Public Sub HexFromByte(b As Byte) As String
Return bc.HexFromBytes(Array As Byte(b))
End Sub

Public Sub HexFromInt(i As Int) As String
Dim b() As Byte = bc.IntsToBytes(Array As Int(i))
Return bc.HexFromBytes(b)
End Sub

Public Sub BytesFromLong(value As Long, littleendian As Boolean) As Byte()
Dim raf As RandomAccessFile
Dim b(4) As Byte
raf.Initialize(b, littleendian)
raf.WriteULong32(value, 0)
Return b
End Sub

Public Sub BytesFromInt(value As Int, littleendian As Boolean) As Byte()
Dim raf As RandomAccessFile
Dim b(2) As Byte
raf.Initialize(b, littleendian)
raf.WriteUInt16(value, 0)
Return b
End Sub
[/CODE]

B4J B4XPages MainPage Code Snippet (without comments,logging)
[CODE lang="b4x" title="B4J Code Snippet"]
Sub Class_Globals
Private VERSION As String = "rBLEServer Example BLEPyBridge v20250310"
Private Root As B4XView
Private xui As XUI
Private LabelBLEState As B4XView
Private LabelTemperature As B4XView
Private LabelHumidity As B4XView
Private LabelInterval As B4XView
Private CustomListViewLog As CustomListView
Private ButtonConnect As B4XView
Private ProgressBarScan As AnotherProgressBar
Private B4XSeekBarInterval As B4XSeekBar
Private LabelSeekBarIntervalValue As B4XView
Private ButtonIntervalSet As B4XView
Private CheckBoxLED As B4XView
Private Py As PyBridge
Private SERVICE_UUID As String = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
Private CHARACTERISTIC_UUID As String = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" 'Flags read,notify,write
Private BLE_SERVER_NAME As String = "BLEServer" 'Name set in B4R program
Private ScanState_Init = 0, ScanState_Not_Scanning = 1, ScanState_Scanning = 2 As Int
Private ble As Bleak 'Bleak instance
Private BleDevice As BleakDevice
Private BleCLient As BleakClient 'Bleak client connected to the ble device
Private IsConnected As Boolean = False
Private bc As ByteConverter
End Sub

Public Sub Initialize
B4XPages.GetManager.LogEvents = True
End Sub

Private Sub B4XPage_Created (Root1 As B4XView)
Root = Root1
Root.LoadLayout("MainPage")
B4XPages.SetTitle(Me, VERSION)
LabelBLEState.Text = "Disconnected"
Py.Initialize(Me, "Py")
Dim opt As PyOptions = Py.CreateOptions("Python/python/python.exe")
Py.Start(opt)
SetScanState(ScanState_Init)
Wait For Py_Connected (Success As Boolean)
If Success = False Then
LogError("[ERROR][PyBridge] Failed to start Python process.")
Return
End If
SetScanState(ScanState_Not_Scanning)
ble.Initialize(Me, "ble", Py)
ButtonConnect_Click
End Sub

Private Sub B4XPage_CloseRequest As ResumableSub
Py.KillProcess
Sleep(100)
Return True
End Sub

Private Sub B4XPage_Background
'NOT USED
End Sub

Private Sub ButtonConnect_Click
Wait For (ble.Scan(Array As String(SERVICE_UUID))) Complete (Success As Boolean)
If Success Then
SetScanState(ScanState_Scanning)
Else
Log(Py.PyLastException)
End If
End Sub

Private Sub CheckBoxLED_CheckedChange(Checked As Boolean)
SetLED(Checked)
End Sub

Private Sub SetLED(state As Boolean)
Dim command() As Byte = IIf(state, Array As Byte(0x01, 0x01), Array As Byte(0x01, 0x00) )
BLE_Write(command)
CustomListViewLog_Insert($"[SetLED] ${DateTime.time(DateTime.now)} :: LED state changed to ${IIf(state,"ON","OFF")}"$)
End Sub

Private Sub ButtonIntervalSet_Click
Dim command(2) As Byte
command(0) = 0x04
command(1) = IntToByte(B4XSeekBarInterval.Value)
BLE_Write(command)
CustomListViewLog_Insert($"[B4XSeekBarInterval_ValueChanged] value=${B4XSeekBarInterval.Value}, hex=${bc.HexFromBytes(command)}"$)
End Sub

Private Sub B4XSeekBarInterval_ValueChanged (Value As Int)
LabelSeekBarIntervalValue.Text = $"${Value} s."$
End Sub

Private Sub CustomListViewLog_Insert(item As String)
CustomListViewLog.InsertAt(0, CustomListViewLog_CreateItem(item), "")
End Sub

Sub CustomListViewLog_CreateItem(item As String) As Pane
Dim pnl As B4XView = xui.CreatePanel("")
pnl.Color = xui.Color_White
pnl.SetLayoutAnimated(0, 0, 0, CustomListViewLog.AsView.Width, 30dip)
Dim lbl As B4XView = XUIViewsUtils.CreateLabel
lbl.Text = item
lbl.SetTextAlignment("CENTER", "LEFT")
lbl.Font = xui.CreateDefaultBoldFont(14)
pnl.AddView(lbl, 0, 0, CustomListViewLog.AsView.Width, 30dip)
Return pnl
End Sub

Private Sub SetScanState(State As Int)
ProgressBarScan.Visible = State = ScanState_Scanning
End Sub

Private Sub BLE_DeviceFound(Device As BleakDevice)
If ble.IsScanning = False Then Return
If Device.name.EqualsIgnoreCase(BLE_SERVER_NAME) Then
BleDevice = Device
ble.StopScan
SetScanState(ScanState_Not_Scanning)
BLE_Connect
End If
End Sub

Private Sub BLE_Connect
Dim item As String
BleCLient = ble.CreateClient(BleDevice)
Wait For (BleCLient.Connect) Complete (Success As Boolean)
If Success Then
IsConnected = True
item = $"[BLE_Connect] connected. deviceid=${BleDevice.DeviceId}"$
Sleep(10)
BLE_Notify
LabelBLEState.Text = "Connected"
Log(item)
Else
IsConnected = False
item = $"[ERROR][BLE_Connect] ${ParseErrorMessage(Py.PyLastException)}"$
xui.MsgboxAsync(ParseErrorMessage(Py.PyLastException), "failed to connect: " & BleDevice.DeviceId)
LabelBLEState.Text = "Disconnected"
LogError(item)
End If
CustomListViewLog.InsertAt(0, CustomListViewLog_CreateItem(item), "")
End Sub

Private Sub BLE_DeviceDisconnected(DeviceId As String)
'
End Sub

Private Sub BLE_CharNotify(Notification As BleakNotification)
Dim item As String
Dim buffer() As Byte = Notification.Value
Dim temperature As Float = IntFromBytes(buffer(1),buffer(0)) / 100
Dim humidity As Float = IntFromBytes(buffer(3),buffer(2)) / 100
Dim interval As Short = IntFromBytes(buffer(5),buffer(4)) / 1000
LabelTemperature.Text = $"${temperature} °C"$
LabelHumidity.Text = $"${humidity} %RH"$
LabelInterval.Text = $"Advertising interval"$
If interval <> B4XSeekBarInterval.Value Then
B4XSeekBarInterval.Value = interval
LabelSeekBarIntervalValue.Text = $"${interval} s"$
End If
item = $"[BLE_CharNotify] ${DateTime.time(DateTime.now)} :: ${temperature} °C, ${humidity} %RH, ${interval} s"$
CustomListViewLog_Insert(item)
End Sub

Private Sub BLE_Read
Dim item As String
If Not(IsConnected) Then Return
Wait For (BleCLient.ReadChar(CHARACTERISTIC_UUID)) Complete (Result As PyWrapper)
If Result.IsSuccess = False Then
item = $"[ERROR][BLE_Read] ${B4XPages.MainPage.ParseErrorMessage(Result.ErrorMessage)}"$
Else
Dim data() As Byte = Result.Value
item = $"[BLE_Read] data=${bc.HexFromBytes(data)}"$
End If
CustomListViewLog.InsertAt(0, CustomListViewLog_CreateItem(item), "")
End Sub

Private Sub BLE_Notify
Dim item As String
If Not(IsConnected) Then Return
Wait For (BleCLient.SetNotify(CHARACTERISTIC_UUID)) Complete (Result As PyWrapper)
If Result.IsSuccess = False Then
item = $"[ERROR][BLE_SetNotify] ${B4XPages.MainPage.ParseErrorMessage(Result.ErrorMessage)}"$
Else
item = "[BLE_SetNotify] OK. Waiting for data..."
End If
CustomListViewLog.InsertAt(0, CustomListViewLog_CreateItem(item), "")
End Sub

Private Sub BLE_Write(b() As Byte)
Dim item As String
If Not(IsConnected) Then Return
Dim rs As Object = BleCLient.Write(CHARACTERISTIC_UUID, b)
Wait For (rs) Complete (Result2 As PyWrapper)
If Result2.IsSuccess = False Then
item = $"[ERROR][BLE_Write] ${B4XPages.MainPage.ParseErrorMessage(Result2.ErrorMessage)}"$
Else
item = $"[BLE_Write] OK. data=${bc.HexFromBytes(b)}"$
End If
CustomListViewLog.InsertAt(0, CustomListViewLog_CreateItem(item), "")
End Sub

Private Sub Py_Disconnected
LabelBLEState.Text = "Disconnected"
End Sub

Public Sub PrintMap(m As Map)
For Each key As String In m.Keys
Dim value As String = m.Get(key)
Log($"key: ${key}, value: ${value}"$)
Next
End Sub

Public Sub IntFromBytes(b1 As Byte, b2 As Byte) As Int
Dim hex As String = bc.HexFromBytes(Array As Byte(b1,b2))
Dim result As Int = Bit.ParseInt(hex, 16)
Return result
End Sub

Public Sub IntToByte(value As Int) As Byte
If value >= 0 And value <= 255 Then
Dim result As Byte = value
End If
Return result
End Sub

Public Sub ParseErrorMessage(Raw As String) As String
Dim m As Matcher = Regex.Matcher("\(([^)]+)\) - Method: [^.]+\.[^:]+:(.*)$", Raw)
Dim msg As String = Raw
If m.Find Then
msg = m.Group(1)
If m.Group(2).Trim <> "" Then
msg = msg & " - " & m.Group(2)
End If
End If
Return msg
End Sub

Public Sub BytesMapToString(map As Map, Title As String) As String
Dim sb As StringBuilder
sb.Initialize
If map.Size > 0 Then
sb.Append(Title).Append(CRLF)
For Each key As Object In map.Keys
Dim b() As Byte = map.Get(key)
sb.Append($"${key}: ${BytesToString(b, 0, b.Length, "ASCII")}"$).Append(CRLF)
Next
End If
Return sb.ToString
End Sub
[/CODE]
 
It would be nice to also have the wiring diagram which would make it easier to implement with ESP32
 
It would be nice to also have the wiring diagram which would make it easier to implement with ESP32
Hi, thanks for feedback.
Pls note that I had added in the B4R code at the top, following information as an example.
Would that be sufficient?

B4X:
...
' MCU:        ESP-WROOM-32, B4R 4.00 (64 bit), IDE ESP32 Dev Module, Arduino Board Manager ESP32 3.0.0.

' Wiring:   
' DHT22        = ESP32 (wirecolor)
' VCC        = 5V (red)
' Signal     = D4 (brown)
' GND         = GND

' RGBLED Temperature = ESP32 (wirecolor)
' LED RED    = D25 (red)
' LED GREEN    = D27 (green)
' LED BlUE    = D26 (blue)
' GND        = GND (black)

' RGBLED Humidity = ESP32 (wirecolor)
' LED RED    = D14 (red)
' LED GREEN    = D12 (green)
' LED BLUE    = D13 (blue)
' GND        = GND (black)

' TM1637     = ESP32 (wirecolor)
' DIO        = D33 (blue)
' CLK        = D32 (green)
' VCC        = 5V  (red)
' GND        = GND (black)
 

Similar Threads

Back
Top