Android Question BLE2 = Leica Disto and Bosch laser rangefinder

Wolli013

Well-Known Member
Licensed User
Longtime User
Leica Disto and Bosch laser rangefinder.
I have two new laser rangefinders and would like to read out the measurement data with an app. I am using BLE2 code for testing. I can connect both devices and display data via Read Data. but I don't know what I have to do to get the measurement data. I'm doing something wrong, but I can't figure out what the problem is. Maybe someone can look over the code and find the problem.

Translated with DeepL.com (free version)


B4X:
Sub Class_Globals
    Private Root As B4XView
    Private xui As XUI
    Private btnReadData As B4XView
    Private btnDisconnect As B4XView
    Private btnScan As B4XView
    Private lblDeviceStatus As B4XView
    Private lblState As B4XView
    Private pbReadData As B4XLoadingIndicator
    Private clv As CustomListView
    #if B4A
    Private manager As BleManager2
    Private rp As RuntimePermissions
    #else if B4i
    Private manager As BleManager
    #end if
    Private currentStateText As String = "UNKNOWN"
    Private currentState As Int
    Private connected As Boolean = False
    Private ConnectedName As String
    Private ConnectedServices As List
    Private pbScan As B4XLoadingIndicator
        
Private MODEL_NUMBER As String = "00002a00-0000-1000-8000-00805f9b34fb" ' Model Number String
Private MANUFACTURER As String = "00002a29-0000-1000-8000-00805f9b34fb" ' Manufacturer Name String       
    
'Bosch Geräte-----------------------------------------------------------------------------------------   
    Private Bosch_GLM_50_C_G_ServiceUUID As String = "02a6c0d0-0451-4000-b000-fb3210111989"
    Private Bosch_GLM_50_C_G_CharacteristicUUID As String = "02a6c0d1-0451-4000-b000-fb3210111989"
'Bosch Geräte-----------------------------------------------------------------------------------------       
    
'Leica Geräte-----------------------------------------------------------------------------------------   
    Private Leica_Disto_X3_ServiceUUID As String = "3ab10100-f831-4395-b29d-570977d5bf94"
    Private Leica_Disto_X3_CharacteristicUUID As String = "3ab1010d-f831-4395-b29d-570977d5bf94"
'Leica Geräte-----------------------------------------------------------------------------------------       
    
    Private firstRead As Boolean           
    
End Sub

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

'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("1")
    B4XPages.SetTitle(Me, "BLE Example")
    manager.Initialize("manager")
    StateChanged
End Sub

Public Sub StateChanged
    lblState.Text = currentStateText
    If connected Then
        lblDeviceStatus.Text = "Connected: " & ConnectedName
    Else
        lblDeviceStatus.Text = "Not connected"
    End If
    btnDisconnect.Enabled = connected
    btnScan.Enabled = Not(connected)
    pbReadData.Hide
    pbScan.Hide
    btnReadData.Enabled = connected
    btnScan.Enabled = (currentState = manager.STATE_POWERED_ON) And connected = False
End Sub

Sub btnScan_Click
    #if B4A
    'Don't forget to add permission to manifest
    Dim Permissions As List
    Dim phone As Phone
    If phone.SdkVersion >= 31 Then
        Permissions = Array("android.permission.BLUETOOTH_SCAN", "android.permission.BLUETOOTH_CONNECT", rp.PERMISSION_ACCESS_FINE_LOCATION)
    Else
        Permissions = Array(rp.PERMISSION_ACCESS_FINE_LOCATION)
    End If
    For Each per As String In Permissions
        rp.CheckAndRequest(per)
        Wait For B4XPage_PermissionResult (Permission As String, Result As Boolean)
        If Result = False Then
            ToastMessageShow("No permission: " & Permission, True)
            Return
        End If
    Next
    #end if
    pbScan.Show
    StartScan
End Sub

Sub btnDisconnect_Click
    manager.Disconnect
    Manager_Disconnected
End Sub

Sub btnReadData_Click
    pbReadData.Show
    clv.Clear
    For Each s As String In ConnectedServices
        manager.ReadData(s)
    Next
End Sub

Sub CreateServiceItem (service As String) As Panel
    Dim pnl As B4XView = xui.CreatePanel("")
    pnl.Color = 0xFF808080
    pnl.SetLayoutAnimated(0, 0, 0, clv.AsView.Width, 30dip)
    Dim lbl As B4XView = XUIViewsUtils.CreateLabel
    lbl.Text = service
    lbl.SetTextAlignment("CENTER", "CENTER")
    lbl.Font = xui.CreateDefaultBoldFont(14)
    pnl.AddView(lbl, 0, 0, clv.AsView.Width, 30dip)
    Return pnl
End Sub

Sub CreateCharacteristicItem(Id As String, Data() As Byte) As Panel
    Dim pnl As B4XView = xui.CreatePanel("")
    pnl.SetLayoutAnimated(0, 0, 0, clv.AsView.Width, 40dip)
    pnl.Color = Colors.White
    Dim lbl As B4XView = XUIViewsUtils.CreateLabel
    lbl.Text = Id
    pnl.AddView(lbl, 0, 0, clv.AsView.Width, 20dip)
    Dim lbl2 As B4XView = XUIViewsUtils.CreateLabel
    Try
        lbl2.Text = BytesToString(Data, 0, Data.Length, "UTF8")
    Catch
        Log(LastException)
        lbl2.Text = "Error reading data as string"
    End Try
    lbl2.TextColor = 0xFF909090
    lbl2.TextSize = 14
    pnl.AddView(lbl2, 0, 20dip, clv.AsView.Width, 20dip)
    Return pnl
End Sub

Sub Manager_StateChanged (State As Int)
    Select State
        Case manager.STATE_POWERED_OFF
            currentStateText = "POWERED OFF"
        Case manager.STATE_POWERED_ON
            currentStateText = "POWERED ON"
        Case manager.STATE_UNSUPPORTED
            currentStateText = "UNSUPPORTED"
    End Select
    currentState = State
    StateChanged
End Sub

Sub Manager_DeviceFound (Name As String, Id As String, AdvertisingData As Map, RSSI As Double)
    Log("Found: " & Name & ", " & Id & ", RSSI = " & RSSI & ", " & AdvertisingData) 'ignore
'    If Id = "6D:D4:F2:0C:A4:74" Then
    ConnectedName = Name
    manager.StopScan
    Log("connecting")
        #if B4A
    manager.Connect2(Id, False) 'disabling auto connect can make the connection quicker
    #else if B4I
    manager.Connect(Id)
    #end if
'    End If
End Sub

Sub Manager_Disconnected
    Log("Disconnected")
    connected = False
    StateChanged
End Sub

Sub Manager_Connected (services As List)
    Log("Connected")
    connected = True
    ConnectedServices = services
    StateChanged
    'manager.ReadData("3ab1010d-f831-4395-b29d-570977d5bf94")
End Sub


'utility to convert short UUIDs to long format on Android
Private Sub UUID(id As String) As String 'ignore
#if B4A
    Return "0000" & id.ToLowerCase & "-0000-1000-8000-00805f9b34fb"
#else if B4I
    Return id.ToUpperCase
#End If
End Sub


Public Sub StartScan
    If manager.State <> manager.STATE_POWERED_ON Then
        Log("Not powered on.")
    Else
        manager.Scan2(Null, False)

''Für die Boschlaser GLM 50-27 CG x9577------------------------------------
'    Dim List1 As List
'    List1.Initialize
'    List1.AddAll(Array As String("02a6c0d0-0451-4000-b000-fb3210111989","3ab10100-f831-4395-b29d-570977d5bf94"))   
'    manager.Scan(List1)
''Für die Boschlaser GLM 50-27 CG x9577------------------------------------
    
    End If
End Sub

Sub Manager_DataAvailable (ServiceId As String, Characteristics As Map)
    pbReadData.Hide
    
'    If firstRead Then
'        firstRead = False
'        manager.SetNotify(Leica_Disto_X3_ServiceUUID,Leica_Disto_X3_CharacteristicUUID,True)
'        Return
'    End If   

'    Private Leica_Disto_X3_ServiceUUID As String = "3ab10100-f831-4395-b29d-570977d5bf94"
'    Private Leica_Disto_X3_CharacteristicUUID As String = "3ab1010d-f831-4395-b29d-570977d5bf94"

    clv.Add(CreateServiceItem(ServiceId), "")
    For Each id As String In Characteristics.Keys
        clv.Add(CreateCharacteristicItem(id, Characteristics.Get(id)), "")
    Next
    
For Each id As String In Characteristics.Keys
    Dim dataContent() As Byte = Characteristics.Get(id)
    Select id       
        Case MODEL_NUMBER
            Dim conModelNumber As String = BytesToString(dataContent, 0, dataContent.Length, "UTF8")
            Log("Model: " & conModelNumber)

        Case MANUFACTURER
            Dim conManufacturerName As String = BytesToString(dataContent, 0, dataContent.Length, "UTF8")
            Log("Manufacturer: " & conManufacturerName)           

        Dim ArrayBytes() As Byte = Characteristics.Get(Leica_Disto_X3_CharacteristicUUID)         
        If ArrayBytes <> Null And ArrayBytes.Length > 0 Then
            Log("Der Inhalt von ArrayBytes ist ungleich null")
        Else
             Log("Der Inhalt von ArrayBytes ist null")
        End If
            
    End Select
Next   

End Sub


'Sub BluetoothCodeLeicaundBosch
    
'Die Leica-Generation, zu der der X3 gehört, verwendet im Prinzip einfach nur andere Characteristics.
'Der Service ist derselbe, nämlich immer noch der mit der
'UUID '{3AB10100-F831-4395-B29D-570977D5BF94}'.

'Der Trick besteht darin, dass man sich anscheinend mit sämtlichen Characteristics,
'die dieser Service zur Verfügung stellt, verbindet (SubscribeToCharacteristic),
'was mit allen außer ~ 2 möglich ist.

'Hat man das getan, so empfängt man, sobald man mit dem Laser misst,
'über die "BASIC_MEASUREMENT"-Characteristic mit der
'UUID '{3AB1010D-F831-4395-B29D-570977D5BF94}' ein Byte-Array,
'welches den Messwert enthält,den man sich auslesen kann,
'indem man die ersten 4 Werte des Arrays in umgekehrter Reihenfolge (Little Endian) in eine Float umwandelt.   

'        If debug_mode:
'            # this is here for debugging ... there many more things to implement
'            # if characteristic.uuid == '3ab10102-f831-4395-b29d-570977d5bf94':
'            print(characteristic.uuid,':',type(value),len(value)) # ,int.from_bytes(value,byteorder='big', signed=False))
'            # else:
'            #     print( characteristic.uuid, ":", value.decode("utf-8"))
'            print('\traw   :',value)
'            If len(value) == 2:
'                print("\tuint16  :",struct.unpack('>H',value)[0])
'            If len(value) == 4:
'                print("\tfloat :",struct.unpack('f',value)[0])
'            elif len(value) == 8:
'                print('\tdouble:',struct.unpack('d',value)[0])
'               
'        elif characteristic.uuid == '3ab10101-f831-4395-b29d-570977d5bf94':
'            self.report_measurement(value)
'        # Vendor ID
'        elif characteristic.uuid == '00002a29-0000-1000-8000-00805f9b34fb':
'            # for whatever reasons the D2 reports itself as the BLE SoC
'            # thats driving it - a Nordic Semi nRF51822 - a 16MhZ Cortex-M0
'            is_leica = value.decode('utf-8') == 'nRF51822'
'        elif characteristic.uuid == '00002a1a-0000-1000-8000-00805f9b34fb':
'                print('Battery level',value)

'    def report_measurement(self,value):
'        float_val = struct.unpack('f',value)[0]
'        print(Round(float_val,3),'m')   
'End Sub
 

emexes

Expert
Licensed User
You've probably already done this, but sometimes in the middle of programming battles, things can be missed, so just in case:

Can you see the measurement data bytes with nRF Connect?

As in bytes that:
1/ don't change when the measured distance doesn't change
2/ do change when the measured distance does change

Presumably a distance reading has to be done first, before you can read the data, by:
1/ pressing the physical "measure distance" button
2/ sending a "measure distance" request

I don't think reading the measurement characteristic is going to trigger doing an actual reading. I imagine that switching the laser on, sending a coded laser pulse, and waiting for an echo, will take longer than the allowable turnaround time of a BLE request-response exchange.

Although BLE does have a notify facility, where it will send data automatically, when it becomes available. But still you'd have to somehow tell the rangefinder to do that (assuming that it does not automatically emit data all the time, which would quickly empty the battery).
 
Upvote 0

emexes

Expert
Licensed User
I would expect the new Bosch rangefinder to return data in a similar format to the previous Bosch rangefinder, so peraps we should get that one going first (ie Bosch first, then Leica).

What is the exact model number on the label of the rangefinder? And is it the same as on the retail box that it came in? And as in the user manual?

Also is there a Bosch app for reading the rangefinder, and if so, what procedure does the app follow, eg, how to initiate a measurement?

I bought a bluetooth multimeter, turned out the measurements were transmitted as LCD segment data :rolleyes: which is great if you just want to mirror the display on a phone, but a pain in the donkey if you want the actual measurement value.
 
Upvote 0

Wolli013

Well-Known Member
Licensed User
Longtime User
Here are screenshots of the Bosch device.
nothing changes when measuring.
I have received an SDK from Bosch that you can install in your app. But I can't integrate it or I'm too inexperienced with it. If you want I can send you the files by e-mail.
 

Attachments

  • Screenshot_20240410_071038_nRF Connect.jpg
    Screenshot_20240410_071038_nRF Connect.jpg
    323 KB · Views: 115
  • Screenshot_20240410_071359_nRF Connect.jpg
    Screenshot_20240410_071359_nRF Connect.jpg
    272.7 KB · Views: 118
  • Screenshot_20240410_071500_nRF Connect.jpg
    Screenshot_20240410_071500_nRF Connect.jpg
    262.1 KB · Views: 114
  • Screenshot_20240410_071514_nRF Connect.jpg
    Screenshot_20240410_071514_nRF Connect.jpg
    316.5 KB · Views: 118
  • Screenshot_20240410_071530_nRF Connect.jpg
    Screenshot_20240410_071530_nRF Connect.jpg
    346.9 KB · Views: 117
  • Screenshot_20240410_071542_nRF Connect.jpg
    Screenshot_20240410_071542_nRF Connect.jpg
    302.1 KB · Views: 115
Upvote 0

emexes

Expert
Licensed User
This looks interesting. Try:

1/ reading the descriptors (with luck, one of them might have distance units eg metres or millimetres)

2/ do a manual distance measurement and then download the characteristic
(repeat a few times, look for bytes that change when distance is changed)

3/ enable notifications for that characteristic, then do some manual distance measurements, see what data is sent (if any)
(repeat a few times, look for bytes that change when distance is changed)

1712732158428.png
 
Upvote 0

emexes

Expert
Licensed User
Upvote 0

f0raster0

Well-Known Member
Licensed User
Longtime User
Starting point: I could click all the "notify true" options using nRFapp, then conduct measurements to observe when the values start to change, in order to initiate understanding..
 
Upvote 0

emexes

Expert
Licensed User
Last edited:
Upvote 0

Wolli013

Well-Known Member
Licensed User
Longtime User
Another clue... this post references the same characteristic as shown in one of your nRF Connect screenshots above:

https://devzone.nordicsemi.com/f/nordic-q-a/80589/sniffing-a-bosch-laser-tape-2/337684

Also a couple of posts one post up from that there is mention of a Bosch rangefinder emitting BLE advertisement packets every 8 seconds (cf usually every 1 second) even when off (wtf?)
I've already noticed that with the constant sending. It only stops when the batteries are removed
 
Upvote 0

Sickculture

New Member
I'm a non programmer but it is interesting. I've a GLM120C and a Leica D810. The disto transfer app will only accept distance and shift. On PC you are able to get more infos. Searching a app to get all data from Leica and send a keystrokes to QField Gis app. Also for Bosch.
 
Upvote 0
Top