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

  • rBLEServer-089.zip
    144.3 KB · Views: 13
Last edited:

rwblinn

Well-Known Member
Licensed User
Longtime User
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: 28
Last edited:

rwblinn

Well-Known Member
Licensed User
Longtime User
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:

rwblinn

Well-Known Member
Licensed User
Longtime User
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)
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
 
Last edited:

rwblinn

Well-Known Member
Licensed User
Longtime User
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)
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

B4J B4XPages MainPage Code Snippet (without comments,logging)
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
 
Last edited:

thetrueman

Active Member
@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. :)
 

rwblinn

Well-Known Member
Licensed User
Longtime User
@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:

rwblinn

Well-Known Member
Licensed User
Longtime User
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)
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

B4J B4XPages MainPage Code Snippet (without comments,logging)
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
 

Similar Threads

Top