Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows: AdvertisementPayload empty #302

Open
universam1 opened this issue Nov 2, 2024 · 11 comments
Open

Windows: AdvertisementPayload empty #302

universam1 opened this issue Nov 2, 2024 · 11 comments
Labels

Comments

@universam1
Copy link

universam1 commented Nov 2, 2024

Thank you @jagobagascon and others for the work. Unfortunately on Windows (11) the AdvertisementPayload or manufacturer data is empty.
However, via Python I receive that data just fine, on the same machine.

Go results:

BTH01-A, Address: 38:1F:8D:6C:C8:EB, RSSI: -78, Data:&{{BTH01-A [] [] []}}
THB1-a, Address: 38:1F:8D:E9:87:2B, RSSI: -86, Data:&{{THB1-a [] [] []}}
BTH01-D, Address: 38:1F:8D:36:0D:CF, RSSI: -82, Data:&{{BTH01-D [] [] []}}
Amazfit GTS, Address: D3:52:C4:29:C6:C4, RSSI: -75, Data:&{{Amazfit GTS [] [] []}}
Go code:
package main

import (
	"fmt"
	"strings"

	"tinygo.org/x/bluetooth"
)

func main() {
	adapter := bluetooth.DefaultAdapter

	// Enable BLE adapter
	err := adapter.Enable()
	if err != nil {
		fmt.Printf("Error enabling adapter: %s\n", err)
		return
	}
	fmt.Println("Scanning for BLE devices...")
	err = adapter.Scan(func(adapter *bluetooth.Adapter, advertisement bluetooth.ScanResult) {
		// Check if the device name is available and starts with "BTH"
		if advertisement.LocalName() != "" && strings.Contains(advertisement.LocalName(), "T") {
			fmt.Printf("%s, Address: %s, RSSI: %d, Data:%v\n",
				advertisement.LocalName(),
				advertisement.Address.String(),
				advertisement.RSSI,
				advertisement.AdvertisementPayload,
			)
		}
	})

	if err != nil {
		fmt.Printf("Error during scan: %s\n", err)
	}
}

Python results:

Found Device - Name: BTH01-D, Address: 38:1F:8D:36:0D:CF, RSSI: -77
Advertisement Data: AdvertisementData(local_name='BTH01-D', service_data={'0000fcd2-0000-1000-8000-00805f9b34fb': b'@\x00\xa1\x015\x02\xf0\x04\x039!\x0c\xe6\t'}, rssi=-77)
Found Device - Name: BTH01-A, Address: 38:1F:8D:6C:C8:EB, RSSI: -71
Advertisement Data: AdvertisementData(local_name='BTH01-A', service_data={'0000fcd2-0000-1000-8000-00805f9b34fb': b'@\x00\xe9\x013\x023\x08\x03S\x17\x0c\xd2\t'}, rssi=-71)
Python code:
import asyncio
from bleak import BleakScanner

def print_advertisement(device, advertisement_data):
    # Check if the device name starts with "BTH"
    if device.name and device.name.startswith("BTH"):
        print(f"Found Device - Name: {device.name}, Address: {device.address}, RSSI: {device.rssi}")
        print("Advertisement Data:", advertisement_data)

async def main():
    # Start scanning with a callback to filter devices by name
    scanner = BleakScanner()
    scanner.register_detection_callback(print_advertisement)

    print("Scanning for BLE devices...")
    await scanner.start()
    
    # Scan for 30 seconds
    await asyncio.sleep(30000)
    await scanner.stop()

    print("Scan completed.")

# Run the main function
asyncio.run(main())

Any idea what could be fixed?

@universam1
Copy link
Author

I see this from args.GetAdvertisement();
*advertisement.BluetoothLEAdvertisement {IUnknown: github.com/go-ole/go-ole.IUnknown {RawVTable: *(unreadable could not resolve interface type)}}

@deadprogram
Copy link
Member

@universam1
Copy link
Author

Thank you @deadprogram , so what makes Python succeed here?

@deadprogram
Copy link
Member

These comments are interesting here https://github.com/hbldh/bleak/blob/develop/bleak/backends/winrt/scanner.py#L126

Perhaps @jagobagascon can interpret this code to figure out how to parse this data?

@jagobagascon
Copy link
Member

It looks like we forgot to load the service information data from the advertising:

bluetooth/gap_windows.go

Lines 245 to 248 in a668e1b

AdvertisementFields{
LocalName: localName,
ManufacturerData: manufacturerData,
},

The Python library works because they do load that information:
https://github.com/hbldh/bleak/blob/e01e2640994b99066552b6161f84799712c396fa/bleak/backends/winrt/scanner.py#L147C41-L147C54

I made a quick test and I can get the service UUIDs, but I'm still not sure what to add in the ServiceData field.
I can't see where this information is in the WinRT API (https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisement?view=winrt-26100)

Any idea?

@jagobagascon
Copy link
Member

jagobagascon commented Jan 7, 2025

Good news! I got it to work after doing the same they do here: https://github.com/hbldh/bleak/blob/e01e2640994b99066552b6161f84799712c396fa/bleak/backends/winrt/scanner.py#L169

found device: 57:57:57:BC:D9:9B -63 
ServiceData length: 1
- UUID: 0000fe9f-0000-1000-8000-00805f9b34fb
- Data: len = 20, data = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

But it required a few changes to WinRT:

  • Some APIs were missing, so we need to add them (easy change)
  • There's currently no support for struct types, so getting the UUID for each service using WinRT is impossible at the moment. And getting it to work may take a while. The good news is that we can work around it here by making the syscall ourselves
var outGuid syscall.GUID
hr, _, _ := syscall.SyscallN(
	servVector.VTable().GetAt,
	uintptr(unsafe.Pointer(vectorObject)), // this
	uintptr(index),                          // in uint32
	uintptr(unsafe.Pointer(&outGuid)),
)

I'll try to find some time to work on this next week.

@deadprogram
Copy link
Member

That is great news @jagobagascon !

@dbogoslovtsev
Copy link

Hi! I'm trying to get UUIDs in the getScanResultFromArgs function of windows_gap.go like this:

var serviceUUIDs []UUID
if winAdv, err := args.GetAdvertisement(); err == nil && winAdv != nil {
	vector, _ := winAdv.GetServiceUuids()
	size, _ := vector.GetSize()
	for i := uint32(0); i < size; i++ {
		element, _ := vector.GetAt(i)
		var outGuid syscall.GUID
		hr, _, _ := syscall.SyscallN(
			vector.VTable().GetAt,
			uintptr(unsafe.Pointer(element)),
			uintptr(i),
			uintptr(unsafe.Pointer(&outGuid)),
		)
		uuid := (*UUID)(unsafe.Pointer(hr))
		serviceUUIDs = append(serviceUUIDs, *uuid)
	}
}

but getting an error

GOROOT=C:\Users\Denis\sdk\go1.23.2 #gosetup
GOPATH=C:\Users\Denis\go #gosetup
C:\Users\Denis\sdk\go1.23.2\bin\go.exe build -o C:\Users\Denis\AppData\Local\JetBrains\GoLand2024.3\tmp\GoLand___9go_build_go_ble_library.exe go-ble-library #gosetup
C:\Users\Denis\AppData\Local\JetBrains\GoLand2024.3\tmp\GoLand___9go_build_go_ble_library.exe #gosetup
Device found: {4E:C6:E1:DA:25:7D -60 0xc000100180}
Device found: {69:C1:4B:18:6B:89 -61 0xc0001001e0}
Device found: {69:C1:4B:18:6B:89 -55 0xc000100240}
Device found: {67:01:0E:82:41:4D -66 0xc00018c120}
Device found: {67:01:0E:82:41:4D -66 0xc00018c180}
Exception 0xc0000005 0x0 0xffffffffffffffff 0x7fffabb8e530
PC=0x7fffabb8e530

runtime.cgocall(0x5404e0, 0xc00005a330)
C:/Users/Denis/sdk/go1.23.2/src/runtime/cgocall.go:167 +0x3e fp=0xc000029798 sp=0xc000029730 pc=0x532ade
syscall.SyscallN(0x2767aea11c0?, {0xc000110228?, 0x50fee5?, 0xc00004e380?})
C:/Users/Denis/sdk/go1.23.2/src/runtime/syscall_windows.go:519 +0x46 fp=0xc0000297b8 sp=0xc000029798 pc=0x53aa66
tinygo.org/x/bluetooth.getScanResultFromArgs(0x2767ae7b7f0)
C:/Users/Denis/GolandProjects/go-ble-library/vendor/tinygo.org/x/bluetooth/gap_windows.go:249 +0x2f5 fp=0xc0000298f0 sp=0xc0000297b8 pc=0x595315
tinygo.org/x/bluetooth.(*Adapter).Scan.func2(0x58b5a0?, 0x6e9120?, 0xc000029958?)
C:/Users/Denis/GolandProjects/go-ble-library/vendor/tinygo.org/x/bluetooth/gap_windows.go:160 +0x28 fp=0xc000029958 sp=0xc0000298f0 pc=0x594f48
github.com/saltosystems/winrt-go/windows/foundation.(*TypedEventHandler).Invoke(0x2767ae54e10, 0x53a64f?, 0x2767ae551a0, 0x2767ae7b7f0, 0x4dfe05?, 0xc000029ab0?, 0x2767ae54e10?, 0x5?, 0xc000029a28?, 0x2767ae5cfb8, ...)
C:/Users/Denis/GolandProjects/go-ble-library/vendor/github.com/saltosystems/winrt-go/windows/foundation/typedeventhandler.go:114 +0x47 fp=0xc000029980 sp=0xc000029958 pc=0x58c2c7
github.com/saltosystems/winrt-go/internal/delegate.invoke(0x2767ae54e10, 0x2767ae551a0, 0x2767ae7b7f0, 0xd10d9ff370, 0x2767ae9efd0, 0x7fffabc58f72, 0x2767ae54e10, 0x2767ae60dc0, 0x2767ae5cfb8, 0x7fffabb7def8)
C:/Users/Denis/GolandProjects/go-ble-library/vendor/github.com/saltosystems/winrt-go/internal/delegate/delegate.go:113 +0xb6 fp=0xc0000299e8 sp=0xc000029980 pc=0x58b916
runtime.call128(0x0, 0x5d2468, 0xc000029aa8, 0x8, 0x8, 0x50, 0xc000029cd8)
C:/Users/Denis/sdk/go1.23.2/src/runtime/asm_amd64.s:778 +0x5d fp=0xc000029a78 sp=0xc0000299e8 pc=0x53d43d
runtime.callbackWrap(0xd10d9ff520)
C:/Users/Denis/sdk/go1.23.2/src/runtime/syscall_windows.go:396 +0x205 fp=0xc000029e50 sp=0xc000029a78 pc=0x521e05
runtime.cgocallbackg1(0x521c00, 0xd10d9ff520, 0x0)
C:/Users/Denis/sdk/go1.23.2/src/runtime/cgocall.go:442 +0x295 fp=0xc000029f10 sp=0xc000029e50 pc=0x4d5195
runtime.cgocallbackg(0x521c00, 0xd10d9ff520, 0x0)
C:/Users/Denis/sdk/go1.23.2/src/runtime/cgocall.go:361 +0x145 fp=0xc000029f90 sp=0xc000029f10 pc=0x4d4e45
runtime.cgocallbackg(0x521c00, 0xd10d9ff520, 0x0)
:1 +0x2e fp=0xc000029fb8 sp=0xc000029f90 pc=0x54364e
runtime.cgocallback(0x0, 0x0, 0x0)
C:/Users/Denis/sdk/go1.23.2/src/runtime/asm_amd64.s:1084 +0xec fp=0xc000029fe0 sp=0xc000029fb8 pc=0x53eaec
runtime.goexit({})
C:/Users/Denis/sdk/go1.23.2/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc000029fe8 sp=0xc000029fe0 pc=0x53ed41
...
etc. etc.

The same happens when trying to fetch service data.
I'm not as good at pointers, stack splitting, memory management etc. so I would appreciate any help you could give me.
It would be perfect if you could share some very rough workaround or an example of how to do it properly as you said you were able to get UUIDs and service data

@jagobagascon
Copy link
Member

jagobagascon commented Jan 9, 2025

You are calling GetAt twice. Remove the function provided by WinRT and use only the syscall. It should work.

Also hr is the error result. Not the output parameter.

var serviceUUIDs []UUID
if winAdv, err := args.GetAdvertisement(); err == nil && winAdv != nil {
	vector, _ := winAdv.GetServiceUuids()
	size, _ := vector.GetSize()
	for i := uint32(0); i < size; i++ {
-		element, _ := vector.GetAt(i)
		var outGuid syscall.GUID
		hr, _, _ := syscall.SyscallN(
			vector.VTable().GetAt,
-			uintptr(unsafe.Pointer(element)),
+			uintptr(unsafe.Pointer(vector)),
			uintptr(i),
			uintptr(unsafe.Pointer(&outGuid)),
		)
+		if hr != 0 {
+			return ole.NewError(hr)
+		}
-		uuid := (*UUID)(unsafe.Pointer(hr))
-		serviceUUIDs = append(serviceUUIDs, *uuid)
+		serviceUUIDs = append(serviceUUIDs, outGuid)
	}
}

@dbogoslovtsev
Copy link

After a couple of adjustments, I managed to add them to the scan results. Many thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants