v0.8.9 (Build 20250310) (see post #1)
- NEW: Example UI Application B4J Pages with PyBridge and Bleak (see this thread)
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]