Compare commits
2 commits
release
...
channelsca
Author | SHA1 | Date | |
---|---|---|---|
|
afe3dff19e | ||
|
58d88cd7e7 |
86 changed files with 1161 additions and 6220 deletions
5
.github/workflows/linux.yml
vendored
5
.github/workflows/linux.yml
vendored
|
@ -26,5 +26,8 @@ jobs:
|
||||||
run: make smoketest-tinygo
|
run: make smoketest-tinygo
|
||||||
- name: Run Linux smoke tests
|
- name: Run Linux smoke tests
|
||||||
run: make smoketest-linux
|
run: make smoketest-linux
|
||||||
- name: "Run Windows cross-compiled smoke tests"
|
- name: Install Windows cross compiler
|
||||||
|
run: |
|
||||||
|
apt-get install -y gcc-mingw-w64-x86-64
|
||||||
|
- name: "Run Windows smoke tests"
|
||||||
run: make smoketest-windows
|
run: make smoketest-windows
|
||||||
|
|
38
.github/workflows/macos.yml
vendored
38
.github/workflows/macos.yml
vendored
|
@ -12,46 +12,16 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
macos-11:
|
build:
|
||||||
name: macos-11
|
name: build
|
||||||
runs-on: macos-11
|
runs-on: macos-11
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4.1.0
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '1.18.3'
|
go-version: '1.18.3'
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3.6.0
|
uses: actions/checkout@v3
|
||||||
- name: Run unit tests
|
|
||||||
run: go test
|
|
||||||
- name: "Run macOS smoke tests"
|
|
||||||
run: make smoketest-macos
|
|
||||||
|
|
||||||
macos-12:
|
|
||||||
name: macos-12
|
|
||||||
runs-on: macos-12
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v4.1.0
|
|
||||||
with:
|
|
||||||
go-version: '1.21.0'
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3.6.0
|
|
||||||
- name: Run unit tests
|
|
||||||
run: go test
|
|
||||||
- name: "Run macOS smoke tests"
|
|
||||||
run: make smoketest-macos
|
|
||||||
|
|
||||||
macos-13:
|
|
||||||
name: macos-13
|
|
||||||
runs-on: macos-13
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v4.1.0
|
|
||||||
with:
|
|
||||||
go-version: '1.21.0'
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3.6.0
|
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: go test
|
run: go test
|
||||||
- name: "Run macOS smoke tests"
|
- name: "Run macOS smoke tests"
|
||||||
|
|
24
.github/workflows/windows.yml
vendored
24
.github/workflows/windows.yml
vendored
|
@ -1,24 +0,0 @@
|
||||||
name: Windows
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-windows:
|
|
||||||
name: build-windows
|
|
||||||
runs-on: windows-2022
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v4.1.0
|
|
||||||
with:
|
|
||||||
go-version: '1.21.0'
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3.6.0
|
|
||||||
- name: Run unit tests
|
|
||||||
run: go test
|
|
||||||
- name: "Run Windows smoke tests"
|
|
||||||
run: make smoketest-windows
|
|
146
CHANGELOG.md
146
CHANGELOG.md
|
@ -1,149 +1,3 @@
|
||||||
0.10.0
|
|
||||||
---
|
|
||||||
|
|
||||||
* **core**
|
|
||||||
- gap: fix ServiceDataElement.UUID comment
|
|
||||||
* **docs**
|
|
||||||
- add mention of support for rp2040-W to README
|
|
||||||
- Improve documentation of RSSI Fixes https://github.com/tinygo-org/bluetooth/issues/272
|
|
||||||
* **hci**
|
|
||||||
- cyw43439: HCI implementation
|
|
||||||
- refactor to separate HCI transport implementation from interface to not always assume UART.
|
|
||||||
- update for cyw43439 HCI functionality
|
|
||||||
* **windows**
|
|
||||||
- Add Address field to Windows Device struct
|
|
||||||
- Winrt full support (#266)
|
|
||||||
- winrt-go: bump to latest
|
|
||||||
- assign char handle write event (#274)
|
|
||||||
* **test**
|
|
||||||
- add hci_uart based implementation to smoke tests
|
|
||||||
|
|
||||||
0.9.0
|
|
||||||
---
|
|
||||||
|
|
||||||
* **build**
|
|
||||||
- add arduino-nano33 and pyportal to smoke tests
|
|
||||||
- add nina-fw smoketest as peripheral
|
|
||||||
- add some ninafw examples to smoketest
|
|
||||||
* **core**
|
|
||||||
- add ServiceData advertising element (#243)
|
|
||||||
- add RequestConnectionParams to request new connection parameters
|
|
||||||
- change ManufacturerData from a map to a slice
|
|
||||||
- don't use a pointer receiver for many method calls
|
|
||||||
- make Device a value instead of a pointer
|
|
||||||
- use 'debug' variable protected by build tags for debug logging
|
|
||||||
- use Device instead of Address in SetConnectHandler
|
|
||||||
* **docs**
|
|
||||||
- a small mention of the NINA BLE support
|
|
||||||
- complete README info about nina-fw support
|
|
||||||
* **linux**
|
|
||||||
- fix characteristic value
|
|
||||||
- rewrite everything to use DBus directly
|
|
||||||
* **macos**
|
|
||||||
- add Write command to the gattc implementation
|
|
||||||
* **examples**
|
|
||||||
- tinyscan to replace clue-scanner, also works on pyportal and pybadge+airlift
|
|
||||||
- update MCU central examples to use ldflags to pass the desired device to connect to
|
|
||||||
- discover: add MTU
|
|
||||||
* **hci**
|
|
||||||
- add check for poll buffer overflow
|
|
||||||
- allow for both ninafw and pure hci uart adapter implementations
|
|
||||||
- implement Characteristic WriteHandler
|
|
||||||
- multiple connections
|
|
||||||
- return service UUIDs with scan results
|
|
||||||
- add l2cap signaling support
|
|
||||||
- implement evtNumCompPkts to count in-flight packets
|
|
||||||
- correct implementation for WriteWithoutReponse
|
|
||||||
- speed up time waiting for hardware - corrections to MTU exchange
|
|
||||||
- add support for software RTS/CTS flow control for boards where hardware support is not available
|
|
||||||
- BLE central implementation on nina-fw co-processors
|
|
||||||
- fix connection timeout
|
|
||||||
- implement BLE peripheral support
|
|
||||||
- implement GetMTU()
|
|
||||||
- remove some pointer receivers from method calls
|
|
||||||
- should support muliple connections as a central
|
|
||||||
- correctly return from read requests instead of returning spurious error
|
|
||||||
- move some steps previously being done during Configure() into Start() where they more correctly belonged.
|
|
||||||
- use advertising display name as the correct default value for the generic access characteristic.
|
|
||||||
- speed up the polling for new notifications for Centrals
|
|
||||||
- use NINA settings from board file in main TinyGo repo
|
|
||||||
* **nordic semi**
|
|
||||||
- replace unsafe.SliceData call with expression that is still supported in older Go versions
|
|
||||||
- update to prepare for changes in the TinyGo CGo implementation
|
|
||||||
- add address of connecting device
|
|
||||||
- add support for connection timeout on connect
|
|
||||||
- don't send a notify/indicate without a CCCD
|
|
||||||
- fix connect timeout
|
|
||||||
- fix writing to a characteristic
|
|
||||||
- print connection parameters when debug is enabled
|
|
||||||
- return an error on a connection timeout
|
|
||||||
* **windows**
|
|
||||||
- Release AsyncOperationCompletedHandler (#208)
|
|
||||||
- check for error when scanning
|
|
||||||
- bump to latest winrt
|
|
||||||
|
|
||||||
|
|
||||||
0.8.0
|
|
||||||
---
|
|
||||||
|
|
||||||
* **build**
|
|
||||||
- remove CGo dependencies for Windows cross-compiler tests
|
|
||||||
- add Windows to GH actions build jobs
|
|
||||||
- add macOS 12 to GH actions build jobs
|
|
||||||
* **core**
|
|
||||||
- go 1.18 and remove old-style build tags
|
|
||||||
- Noescape workaround
|
|
||||||
* **docs**
|
|
||||||
- update README to remove CGo requirement for Windows
|
|
||||||
- add documentation to heartrate-monitor
|
|
||||||
* **linux**
|
|
||||||
- Added option to add ManufacturerData to Advertisement
|
|
||||||
* **macos**
|
|
||||||
- enable support for duplicate chars by moving from a map to a slice
|
|
||||||
* **examples**
|
|
||||||
- Include WriteWithoutResponse permission, for examples, where Write exists
|
|
||||||
* **nordic semi**
|
|
||||||
- softdevice: added manufacturer data support
|
|
||||||
- softdevice: test creation of raw BLE advertisement packets
|
|
||||||
* **windows**
|
|
||||||
- update github.com/saltosystems/winrt-go to no longer require CGo
|
|
||||||
|
|
||||||
|
|
||||||
0.7.0
|
|
||||||
---
|
|
||||||
|
|
||||||
* **build**
|
|
||||||
- switch to ghcr.io for docker container
|
|
||||||
- update to actions/checkout@v3
|
|
||||||
- work around for CVE-2022-24765
|
|
||||||
* **core**
|
|
||||||
- gap: Set and SetRandom methods should have a pointer receiver
|
|
||||||
- mtu-{darwin,linux,windows,sd}: add get mtu function
|
|
||||||
- remove Addresser
|
|
||||||
- update uuid generation
|
|
||||||
* **docs**
|
|
||||||
- CONTRIBUTING: add note on new APIs
|
|
||||||
- correct badge link for GH actions
|
|
||||||
- README: add note on macOS Big Sur and iTerm2
|
|
||||||
* **linux**
|
|
||||||
- do not randomize order of returned discovered services/chars
|
|
||||||
- fix characteristic scan order
|
|
||||||
- implement disconnect handling
|
|
||||||
* **macos**
|
|
||||||
- implement disconnect handling
|
|
||||||
- fix characteristic scan order
|
|
||||||
* **examples**
|
|
||||||
- add examples/stop-advertisement
|
|
||||||
* **nordic semi**
|
|
||||||
- nrf528xx: handle BLE_GAP_EVT_PHY_UPDATE_REQUEST and explicitly ignore some other events
|
|
||||||
- softdevice: avoid a heap allocation in the SoftDevice event handler
|
|
||||||
* **windows**
|
|
||||||
- Added Indicate support to Windows driver
|
|
||||||
- gap/windows: Scan should set scanning mode to active to match other platforms
|
|
||||||
- support empty manufacturer data
|
|
||||||
- winrt-go: bump to latest
|
|
||||||
|
|
||||||
|
|
||||||
0.6.0
|
0.6.0
|
||||||
---
|
---
|
||||||
* **core**
|
* **core**
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2019-2023 TinyGo Authors. All rights reserved.
|
Copyright (c) 2019-2022 TinyGo Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
|
|
30
Makefile
30
Makefile
|
@ -11,10 +11,10 @@ smoketest-tinygo:
|
||||||
@md5sum test.hex
|
@md5sum test.hex
|
||||||
$(TINYGO) build -o test.uf2 -size=short -target=circuitplay-bluefruit ./examples/circuitplay
|
$(TINYGO) build -o test.uf2 -size=short -target=circuitplay-bluefruit ./examples/circuitplay
|
||||||
@md5sum test.hex
|
@md5sum test.hex
|
||||||
$(TINYGO) build -o test.hex -size=short -target=circuitplay-bluefruit ./examples/connparams
|
|
||||||
@md5sum test.hex
|
|
||||||
$(TINYGO) build -o test.uf2 -size=short -target=circuitplay-bluefruit ./examples/discover
|
$(TINYGO) build -o test.uf2 -size=short -target=circuitplay-bluefruit ./examples/discover
|
||||||
@md5sum test.hex
|
@md5sum test.hex
|
||||||
|
$(TINYGO) build -o test.uf2 -size=short -target=circuitplay-bluefruit ./examples/channelscan
|
||||||
|
@md5sum test.hex
|
||||||
$(TINYGO) build -o test.hex -size=short -target=pca10040-s132v6 ./examples/heartrate
|
$(TINYGO) build -o test.hex -size=short -target=pca10040-s132v6 ./examples/heartrate
|
||||||
@md5sum test.hex
|
@md5sum test.hex
|
||||||
$(TINYGO) build -o test.hex -size=short -target=reelboard-s140v7 ./examples/ledcolor
|
$(TINYGO) build -o test.hex -size=short -target=reelboard-s140v7 ./examples/ledcolor
|
||||||
|
@ -34,38 +34,23 @@ smoketest-tinygo:
|
||||||
@md5sum test.hex
|
@md5sum test.hex
|
||||||
$(TINYGO) build -o test.hex -size=short -target=microbit-v2-s113v7 ./examples/nusserver
|
$(TINYGO) build -o test.hex -size=short -target=microbit-v2-s113v7 ./examples/nusserver
|
||||||
@md5sum test.hex
|
@md5sum test.hex
|
||||||
$(TINYGO) build -o test.uf2 -size=short -target=nano-rp2040 ./examples/discover
|
|
||||||
@md5sum test.hex
|
|
||||||
$(TINYGO) build -o test.uf2 -size=short -target=arduino-nano33 ./examples/discover
|
|
||||||
@md5sum test.hex
|
|
||||||
$(TINYGO) build -o test.uf2 -size=short -target=pyportal ./examples/discover
|
|
||||||
@md5sum test.hex
|
|
||||||
$(TINYGO) build -o test.uf2 -size=short -target=nano-rp2040 ./examples/advertisement
|
|
||||||
@md5sum test.hex
|
|
||||||
$(TINYGO) build -o test.uf2 -size=short -target=circuitplay-express -tags="hci hci_uart" ./examples/advertisement
|
|
||||||
@md5sum test.hex
|
|
||||||
$(TINYGO) build -o test.uf2 -size=short -target=pico-w ./examples/discover
|
|
||||||
@md5sum test.hex
|
|
||||||
$(TINYGO) build -o test.uf2 -size=short -target=badger2040-w ./examples/advertisement
|
|
||||||
@md5sum test.hex
|
|
||||||
|
|
||||||
smoketest-linux:
|
smoketest-linux:
|
||||||
# Test on Linux.
|
# Test on Linux.
|
||||||
GOOS=linux go build -o /tmp/go-build-discard ./examples/advertisement
|
GOOS=linux go build -o /tmp/go-build-discard ./examples/advertisement
|
||||||
GOOS=linux go build -o /tmp/go-build-discard ./examples/connparams
|
|
||||||
GOOS=linux go build -o /tmp/go-build-discard ./examples/heartrate
|
GOOS=linux go build -o /tmp/go-build-discard ./examples/heartrate
|
||||||
GOOS=linux go build -o /tmp/go-build-discard ./examples/heartrate-monitor
|
GOOS=linux go build -o /tmp/go-build-discard ./examples/heartrate-monitor
|
||||||
GOOS=linux go build -o /tmp/go-build-discard ./examples/nusserver
|
GOOS=linux go build -o /tmp/go-build-discard ./examples/nusserver
|
||||||
GOOS=linux go build -o /tmp/go-build-discard ./examples/scanner
|
GOOS=linux go build -o /tmp/go-build-discard ./examples/scanner
|
||||||
GOOS=linux go build -o /tmp/go-build-discard ./examples/discover
|
GOOS=linux go build -o /tmp/go-build-discard ./examples/discover
|
||||||
|
GOOS=linux go build -o /tmp/go-build-discard ./examples/channelscan
|
||||||
|
|
||||||
smoketest-windows:
|
smoketest-windows:
|
||||||
# Test on Windows.
|
# Test on Windows.
|
||||||
GOOS=windows go build -o /tmp/go-build-discard ./examples/scanner
|
GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -o /tmp/go-build-discard ./examples/scanner
|
||||||
GOOS=windows go build -o /tmp/go-build-discard ./examples/discover
|
GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -o /tmp/go-build-discard ./examples/discover
|
||||||
GOOS=windows go build -o /tmp/go-build-discard ./examples/heartrate-monitor
|
GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -o /tmp/go-build-discard ./examples/heartrate-monitor
|
||||||
GOOS=windows go build -o /tmp/go-build-discard ./examples/advertisement
|
GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -o /tmp/go-build-discard ./examples/channelscan
|
||||||
GOOS=windows go build -o /tmp/go-build-discard ./examples/heartrate
|
|
||||||
|
|
||||||
smoketest-macos:
|
smoketest-macos:
|
||||||
# Test on macos.
|
# Test on macos.
|
||||||
|
@ -73,6 +58,7 @@ smoketest-macos:
|
||||||
GOOS=darwin CGO_ENABLED=1 go build -o /tmp/go-build-discard ./examples/discover
|
GOOS=darwin CGO_ENABLED=1 go build -o /tmp/go-build-discard ./examples/discover
|
||||||
GOOS=darwin CGO_ENABLED=1 go build -o /tmp/go-build-discard ./examples/nusclient
|
GOOS=darwin CGO_ENABLED=1 go build -o /tmp/go-build-discard ./examples/nusclient
|
||||||
GOOS=darwin CGO_ENABLED=1 go build -o /tmp/go-build-discard ./examples/heartrate-monitor
|
GOOS=darwin CGO_ENABLED=1 go build -o /tmp/go-build-discard ./examples/heartrate-monitor
|
||||||
|
GOOS=darwin CGO_ENABLED=1 go build -o /tmp/go-build-discard ./examples/channelscan
|
||||||
|
|
||||||
gen-uuids:
|
gen-uuids:
|
||||||
# generate the standard service and characteristic UUIDs
|
# generate the standard service and characteristic UUIDs
|
||||||
|
|
77
README.md
77
README.md
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
[![Go Bluetooth](./images/gobluetooth.png)](https://tinygo.org/bluetooth)
|
[![Go Bluetooth](./images/gobluetooth.png)](https://tinygo.org/bluetooth)
|
||||||
|
|
||||||
[![PkgGoDev](https://pkg.go.dev/badge/pkg.go.dev/gitrepo.ru/neonxp/bluetooth)](https://pkg.go.dev/gitrepo.ru/neonxp/bluetooth) [![Linux](https://github.com/tinygo-org/bluetooth/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/bluetooth/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/bluetooth/actions/workflows/macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/bluetooth/actions/workflows/macos.yml)
|
[![PkgGoDev](https://pkg.go.dev/badge/pkg.go.dev/tinygo.org/x/bluetooth)](https://pkg.go.dev/tinygo.org/x/bluetooth) [![Linux](https://github.com/tinygo-org/bluetooth/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/bluetooth/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/bluetooth/actions/workflows/macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/bluetooth/actions/workflows/macos.yml)
|
||||||
|
|
||||||
Go Bluetooth is a cross-platform package for using [Bluetooth Low Energy](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy) hardware from the Go programming language.
|
Go Bluetooth is a cross-platform package for using [Bluetooth Low Energy](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy) hardware from the Go programming language.
|
||||||
|
|
||||||
It works on typical operating systems such as [Linux](#linux), [macOS](#macos), and [Windows](#windows).
|
It works on typical operating systems such as [Linux](#linux), [macOS](#macos), and [Windows](#windows).
|
||||||
|
|
||||||
It can also be used running "bare metal" on microcontrollers produced by [Nordic Semiconductor](https://www.nordicsemi.com/) or using the Bluetooth Host Controller Interface (HCI) by using [TinyGo](https://tinygo.org/).
|
It can also be used running "bare metal" on microcontrollers produced by [Nordic Semiconductor](https://www.nordicsemi.com/) by using [TinyGo](https://tinygo.org/).
|
||||||
|
|
||||||
The Go Bluetooth package can be used to create both Bluetooth Low Energy Centrals as well as to create Bluetooth Low Energy Peripherals.
|
The Go Bluetooth package can be used to create both Bluetooth Low Energy Centrals as well as to create Bluetooth Low Energy Peripherals.
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ This example shows a central that scans for peripheral devices and then displays
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adapter = bluetooth.DefaultAdapter
|
var adapter = bluetooth.DefaultAdapter
|
||||||
|
@ -58,7 +58,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adapter = bluetooth.DefaultAdapter
|
var adapter = bluetooth.DefaultAdapter
|
||||||
|
@ -92,21 +92,21 @@ func must(action string, err error) {
|
||||||
|
|
||||||
## Current support
|
## Current support
|
||||||
|
|
||||||
| | Linux | macOS | Windows | Nordic Semi | ESP32 (NINA-FW) | CYW43439 (RP2040-W) |
|
| | Linux | macOS | Windows | Nordic Semi |
|
||||||
| -------------------------------- | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------- |
|
| -------------------------------- | ------------------ | ------------------ | ------------------ | ------------------ |
|
||||||
| API used | BlueZ | CoreBluetooth | WinRT | SoftDevice | HCI | HCI |
|
| API used | BlueZ | CoreBluetooth | WinRT | SoftDevice |
|
||||||
| Scanning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
| Scanning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| Connect to peripheral | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
| Connect to peripheral | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| Write peripheral characteristics | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
| Write peripheral characteristics | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| Receive notifications | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
| Receive notifications | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| Advertisement | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
| Advertisement | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: |
|
||||||
| Local services | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
| Local services | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: |
|
||||||
| Local characteristics | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
| Local characteristics | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: |
|
||||||
| Send notifications | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
| Send notifications | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: |
|
||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
|
||||||
Go Bluetooth support for Linux uses [BlueZ](http://www.bluez.org/) via the [D-Bus](https://en.wikipedia.org/wiki/D-Bus) interface. This should work with most distros that support BlueZ such as Ubuntu, Debian, Fedora, and Arch Linux, among others.
|
Go Bluetooth support for Linux uses [BlueZ](http://www.bluez.org/) via the [D-Bus](https://en.wikipedia.org/wiki/D-Bus) interface thanks to the https://github.com/muka/go-bluetooth package. This should work with most distros that support BlueZ such as Ubuntu, Debian, Fedora, and Arch Linux, among others.
|
||||||
|
|
||||||
Linux can be used both as a BLE Central or as a BLE Peripheral.
|
Linux can be used both as a BLE Central or as a BLE Peripheral.
|
||||||
|
|
||||||
|
@ -165,9 +165,11 @@ The Windows support only can only act as a BLE Central at this time, with some a
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
Only the Go compiler itself is needed to compile Go Bluetooth code targeting Windows.
|
In order to compile Go Bluetooth code targeting Windows, you must have a GCC compiler installed.
|
||||||
|
|
||||||
You can obtain the Go Bluetooth package using Git:
|
On Windows, you can download and install mingw-w64 (https://github.com/mingw-w64/mingw-w64)
|
||||||
|
|
||||||
|
Once you have done this, you can obtain the Go Bluetooth package using Git:
|
||||||
|
|
||||||
git clone https://github.com/tinygo-org/bluetooth.git
|
git clone https://github.com/tinygo-org/bluetooth.git
|
||||||
|
|
||||||
|
@ -264,45 +266,6 @@ After that, don't reset the board but instead flash a new program to it. For exa
|
||||||
|
|
||||||
Flashing will normally reset the board.
|
Flashing will normally reset the board.
|
||||||
|
|
||||||
## ESP32 (NINA)
|
|
||||||
|
|
||||||
Go Bluetooth has bare metal support for boards that include a separate ESP32 Bluetooth Low Energy radio co-processor. The ESP32 must be running the Arduino or Adafruit `nina_fw` firmware.
|
|
||||||
|
|
||||||
Several boards created by Adafruit and Arduino already have the `nina-fw` firmware pre-loaded. This means you can use TinyGo and the Go Bluetooth package without any additional steps required.
|
|
||||||
|
|
||||||
Currently supported boards include:
|
|
||||||
|
|
||||||
* [Adafruit Metro M4 AirLift](https://www.adafruit.com/product/4000)
|
|
||||||
* [Adafruit PyBadge](https://www.adafruit.com/product/4200) with [AirLift WiFi FeatherWing](https://www.adafruit.com/product/4264)
|
|
||||||
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
|
|
||||||
* [Arduino Nano 33 IoT](https://docs.arduino.cc/hardware/nano-33-iot)
|
|
||||||
* [Arduino Nano RP2040 Connect](https://docs.arduino.cc/hardware/nano-rp2040-connect)
|
|
||||||
|
|
||||||
After you have installed TinyGo and the Go Bluetooth package, you should be able to compile/run code for your device.
|
|
||||||
|
|
||||||
For example, this command can be used to compile and flash an Arduino Nano RP2040 Connect board with the example we provide that turns it into a BLE peripheral to act like a heart rate monitor:
|
|
||||||
|
|
||||||
tinygo flash -target nano-rp2040 ./examples/heartrate
|
|
||||||
|
|
||||||
If you want more information about the `nina-fw` firmware, or want to add support for other ESP32-equipped boards, please see https://github.com/arduino/nina-fw
|
|
||||||
|
|
||||||
## CYW43439 (RP2040-W)
|
|
||||||
|
|
||||||
Go Bluetooth has bare metal support for boards that include a separate CYW43439 Bluetooth Low Energy radio co-processor.
|
|
||||||
|
|
||||||
Currently supported boards include:
|
|
||||||
|
|
||||||
* [Raspberry Pi Pico RP2040-W](https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html#raspberry-pi-pico-w)
|
|
||||||
* [Pimoroni Badger2040-W](https://shop.pimoroni.com/products/badger-2040-w)
|
|
||||||
|
|
||||||
After you have installed TinyGo and the Go Bluetooth package, you should be able to compile/run code for your device.
|
|
||||||
|
|
||||||
For example, this command can be used to compile and flash a Pico RP2040-W board with the example we provide that turns it into a BLE peripheral to act like a heart rate monitor:
|
|
||||||
|
|
||||||
tinygo flash -target pico-w ./examples/heartrate
|
|
||||||
|
|
||||||
If you want more information about the `cyw43439` support, please see https://github.com/soypat/cyw43439
|
|
||||||
|
|
||||||
## API stability
|
## API stability
|
||||||
|
|
||||||
**The API is not stable!** Because many features are not yet implemented and some platforms (e.g. Windows and macOS) are not yet fully supported, it's hard to say what a good API will be. Therefore, if you want stability you should pick a particular git commit and use that. Go modules can be useful for this purpose.
|
**The API is not stable!** Because many features are not yet implemented and some platforms (e.g. Windows and macOS) are not yet fully supported, it's hard to say what a good API will be. Therefore, if you want stability you should pick a particular git commit and use that. Go modules can be useful for this purpose.
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
// Set this to true to print debug messages, for example for unknown events.
|
||||||
|
const debug = false
|
||||||
|
|
||||||
// SetConnectHandler sets a handler function to be called whenever the adaptor connects
|
// SetConnectHandler sets a handler function to be called whenever the adaptor connects
|
||||||
// or disconnects. You must call this before you call adaptor.Connect() for centrals
|
// or disconnects. You must call this before you call adaptor.Connect() for centrals
|
||||||
// or adaptor.Start() for peripherals in order for it to work.
|
// or adaptor.Start() for peripherals in order for it to work.
|
||||||
func (a *Adapter) SetConnectHandler(c func(device Device, connected bool)) {
|
func (a *Adapter) SetConnectHandler(c func(device Address, connected bool)) {
|
||||||
a.connectHandler = c
|
a.connectHandler = c
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
//go:build cyw43439
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"github.com/soypat/cyw43439"
|
|
||||||
)
|
|
||||||
|
|
||||||
const maxConnections = 1
|
|
||||||
|
|
||||||
// Adapter represents a SPI connection to the HCI controller on an attached CYW4349 module.
|
|
||||||
type Adapter struct {
|
|
||||||
hciAdapter
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultAdapter is the default adapter on the current system.
|
|
||||||
//
|
|
||||||
// Make sure to call Enable() before using it to initialize the adapter.
|
|
||||||
var DefaultAdapter = &Adapter{
|
|
||||||
hciAdapter: hciAdapter{
|
|
||||||
isDefault: true,
|
|
||||||
connectHandler: func(device Device, connected bool) {
|
|
||||||
return
|
|
||||||
},
|
|
||||||
connectedDevices: make([]Device, 0, maxConnections),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable configures the BLE stack. It must be called before any
|
|
||||||
// Bluetooth-related calls (unless otherwise indicated).
|
|
||||||
func (a *Adapter) Enable() error {
|
|
||||||
if debug {
|
|
||||||
println("Initializing CYW43439 device")
|
|
||||||
}
|
|
||||||
|
|
||||||
dev := cyw43439.NewPicoWDevice()
|
|
||||||
cfg := cyw43439.DefaultBluetoothConfig()
|
|
||||||
if debug {
|
|
||||||
cfg.Logger = slog.New(slog.NewTextHandler(machine.USBCDC, &slog.HandlerOptions{
|
|
||||||
Level: slog.LevelDebug - 2,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := dev.Init(cfg)
|
|
||||||
if err != nil {
|
|
||||||
if debug {
|
|
||||||
println("Error initializing CYW43439 device", err.Error())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
transport := &hciSPI{dev: dev}
|
|
||||||
|
|
||||||
a.hci, a.att = newBLEStack(transport)
|
|
||||||
if debug {
|
|
||||||
println("Enabling CYW43439 device")
|
|
||||||
}
|
|
||||||
|
|
||||||
a.enable()
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println("Enabled CYW43439 device")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type hciSPI struct {
|
|
||||||
dev *cyw43439.Device
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciSPI) startRead() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciSPI) endRead() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciSPI) Buffered() int {
|
|
||||||
return h.dev.BufferedHCI()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciSPI) ReadByte() (byte, error) {
|
|
||||||
var buf [1]byte
|
|
||||||
|
|
||||||
r, err := h.dev.HCIReadWriter()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if _, err := r.Read(buf[:]); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciSPI) Read(buf []byte) (int, error) {
|
|
||||||
r, err := h.dev.HCIReadWriter()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.Read(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciSPI) Write(buf []byte) (int, error) {
|
|
||||||
w, err := h.dev.HCIReadWriter()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.Write(buf)
|
|
||||||
}
|
|
|
@ -24,7 +24,7 @@ type Adapter struct {
|
||||||
// used to allow multiple callers to call Connect concurrently.
|
// used to allow multiple callers to call Connect concurrently.
|
||||||
connectMap sync.Map
|
connectMap sync.Map
|
||||||
|
|
||||||
connectHandler func(device Device, connected bool)
|
connectHandler func(device Address, connected bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultAdapter is the default adapter on the system.
|
// DefaultAdapter is the default adapter on the system.
|
||||||
|
@ -35,7 +35,7 @@ var DefaultAdapter = &Adapter{
|
||||||
pm: cbgo.NewPeripheralManager(nil),
|
pm: cbgo.NewPeripheralManager(nil),
|
||||||
connectMap: sync.Map{},
|
connectMap: sync.Map{},
|
||||||
|
|
||||||
connectHandler: func(device Device, connected bool) {
|
connectHandler: func(device Address, connected bool) {
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ func (cmd *centralManagerDelegate) DidDisconnectPeripheral(cmgr cbgo.CentralMana
|
||||||
addr := Address{}
|
addr := Address{}
|
||||||
uuid, _ := ParseUUID(id)
|
uuid, _ := ParseUUID(id)
|
||||||
addr.UUID = uuid
|
addr.UUID = uuid
|
||||||
cmd.a.connectHandler(Device{Address: addr}, false)
|
cmd.a.connectHandler(addr, false)
|
||||||
|
|
||||||
// like with DidConnectPeripheral, check if we have a chan allocated for this and send through the peripheral
|
// like with DidConnectPeripheral, check if we have a chan allocated for this and send through the peripheral
|
||||||
// this will only be true if the receiving side is still waiting for a connection to complete
|
// this will only be true if the receiving side is still waiting for a connection to complete
|
||||||
|
@ -141,32 +141,11 @@ func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) Sc
|
||||||
serviceUUIDs = append(serviceUUIDs, parsedUUID)
|
serviceUUIDs = append(serviceUUIDs, parsedUUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var manufacturerData []ManufacturerDataElement
|
manufacturerData := make(map[uint16][]byte)
|
||||||
if len(advFields.ManufacturerData) > 2 {
|
if len(advFields.ManufacturerData) > 2 {
|
||||||
// Note: CoreBluetooth seems to assume there can be only one
|
|
||||||
// manufacturer data fields in an advertisement packet, while the
|
|
||||||
// specification allows multiple such fields. See the Bluetooth Core
|
|
||||||
// Specification Supplement, table 1.1:
|
|
||||||
// https://www.bluetooth.com/specifications/css-11/
|
|
||||||
manufacturerID := uint16(advFields.ManufacturerData[0])
|
manufacturerID := uint16(advFields.ManufacturerData[0])
|
||||||
manufacturerID += uint16(advFields.ManufacturerData[1]) << 8
|
manufacturerID += uint16(advFields.ManufacturerData[1]) << 8
|
||||||
manufacturerData = append(manufacturerData, ManufacturerDataElement{
|
manufacturerData[manufacturerID] = advFields.ManufacturerData[2:]
|
||||||
CompanyID: manufacturerID,
|
|
||||||
Data: advFields.ManufacturerData[2:],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var serviceData []ServiceDataElement
|
|
||||||
for _, svcData := range advFields.ServiceData {
|
|
||||||
cbgoUUID := svcData.UUID
|
|
||||||
uuid, err := ParseUUID(cbgoUUID.String())
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
serviceData = append(serviceData, ServiceDataElement{
|
|
||||||
UUID: uuid,
|
|
||||||
Data: svcData.Data,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peripheral UUID is randomized on macOS, which means to
|
// Peripheral UUID is randomized on macOS, which means to
|
||||||
|
@ -181,7 +160,6 @@ func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) Sc
|
||||||
LocalName: advFields.LocalName,
|
LocalName: advFields.LocalName,
|
||||||
ServiceUUIDs: serviceUUIDs,
|
ServiceUUIDs: serviceUUIDs,
|
||||||
ManufacturerData: manufacturerData,
|
ManufacturerData: manufacturerData,
|
||||||
ServiceData: serviceData,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
205
adapter_hci.go
205
adapter_hci.go
|
@ -1,205 +0,0 @@
|
||||||
//go:build hci || ninafw || cyw43439
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// hciAdapter represents the implementation for the connection to the HCI controller.
|
|
||||||
type hciAdapter struct {
|
|
||||||
hciport hciTransport
|
|
||||||
hci *hci
|
|
||||||
att *att
|
|
||||||
|
|
||||||
isDefault bool
|
|
||||||
scanning bool
|
|
||||||
|
|
||||||
connectHandler func(device Device, connected bool)
|
|
||||||
|
|
||||||
connectedDevices []Device
|
|
||||||
notificationsStarted bool
|
|
||||||
charWriteHandlers []charWriteHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *hciAdapter) enable() error {
|
|
||||||
if err := a.hci.start(); err != nil {
|
|
||||||
if debug {
|
|
||||||
println("error starting HCI:", err.Error())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.hci.reset(); err != nil {
|
|
||||||
if debug {
|
|
||||||
println("error resetting HCI:", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(150 * time.Millisecond)
|
|
||||||
|
|
||||||
if err := a.hci.setEventMask(0x3FFFFFFFFFFFFFFF); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.hci.setLeEventMask(0x00000000000003FF)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *hciAdapter) Address() (MACAddress, error) {
|
|
||||||
if err := a.hci.readBdAddr(); err != nil {
|
|
||||||
return MACAddress{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return MACAddress{MAC: makeAddress(a.hci.address)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBLEStack(port hciTransport) (*hci, *att) {
|
|
||||||
h := newHCI(port)
|
|
||||||
a := newATT(h)
|
|
||||||
h.att = a
|
|
||||||
|
|
||||||
l := newL2CAP(h)
|
|
||||||
h.l2cap = l
|
|
||||||
|
|
||||||
return h, a
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a NINA MAC address into a Go MAC address.
|
|
||||||
func makeAddress(mac [6]uint8) MAC {
|
|
||||||
return MAC{
|
|
||||||
uint8(mac[0]),
|
|
||||||
uint8(mac[1]),
|
|
||||||
uint8(mac[2]),
|
|
||||||
uint8(mac[3]),
|
|
||||||
uint8(mac[4]),
|
|
||||||
uint8(mac[5]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a Go MAC address into a NINA MAC Address.
|
|
||||||
func makeNINAAddress(mac MAC) [6]uint8 {
|
|
||||||
return [6]uint8{
|
|
||||||
uint8(mac[0]),
|
|
||||||
uint8(mac[1]),
|
|
||||||
uint8(mac[2]),
|
|
||||||
uint8(mac[3]),
|
|
||||||
uint8(mac[4]),
|
|
||||||
uint8(mac[5]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *hciAdapter) startNotifications() {
|
|
||||||
if a.notificationsStarted {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println("starting notifications...")
|
|
||||||
}
|
|
||||||
|
|
||||||
a.notificationsStarted = true
|
|
||||||
|
|
||||||
// go routine to poll for HCI events for ATT notifications
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
if err := a.att.poll(); err != nil {
|
|
||||||
// TODO: handle error
|
|
||||||
if debug {
|
|
||||||
println("error polling for notifications:", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// go routine to handle characteristic notifications
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case not := <-a.att.notifications:
|
|
||||||
if debug {
|
|
||||||
println("notification received", not.connectionHandle, not.handle, not.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
d := a.findConnection(not.connectionHandle)
|
|
||||||
if d.deviceInternal == nil {
|
|
||||||
if debug {
|
|
||||||
println("no device found for handle", not.connectionHandle)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
n := d.findNotificationRegistration(not.handle)
|
|
||||||
if n == nil {
|
|
||||||
if debug {
|
|
||||||
println("no notification registered for handle", not.handle)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.callback != nil {
|
|
||||||
n.callback(not.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Gosched()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *hciAdapter) addConnection(d Device) {
|
|
||||||
a.connectedDevices = append(a.connectedDevices, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *hciAdapter) removeConnection(d Device) {
|
|
||||||
for i := range a.connectedDevices {
|
|
||||||
if d.handle == a.connectedDevices[i].handle {
|
|
||||||
a.connectedDevices[i] = a.connectedDevices[len(a.connectedDevices)-1]
|
|
||||||
a.connectedDevices[len(a.connectedDevices)-1] = Device{}
|
|
||||||
a.connectedDevices = a.connectedDevices[:len(a.connectedDevices)-1]
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *hciAdapter) findConnection(handle uint16) Device {
|
|
||||||
for _, d := range a.connectedDevices {
|
|
||||||
if d.handle == handle {
|
|
||||||
if debug {
|
|
||||||
println("found device", handle, d.Address.String(), "with notifications registered", len(d.notificationRegistrations))
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Device{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// charWriteHandler contains a handler->callback mapping for characteristic
|
|
||||||
// writes.
|
|
||||||
type charWriteHandler struct {
|
|
||||||
handle uint16
|
|
||||||
callback func(connection Connection, offset int, value []byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCharWriteHandler returns a characteristic write handler if one matches the
|
|
||||||
// handle, or nil otherwise.
|
|
||||||
func (a *Adapter) getCharWriteHandler(handle uint16) *charWriteHandler {
|
|
||||||
for i := range a.charWriteHandlers {
|
|
||||||
h := &a.charWriteHandlers[i]
|
|
||||||
if h.handle == handle {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
//go:build hci && hci_uart
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
)
|
|
||||||
|
|
||||||
const maxConnections = 1
|
|
||||||
|
|
||||||
// Adapter represents a "plain" UART connection to the HCI controller.
|
|
||||||
type Adapter struct {
|
|
||||||
hciAdapter
|
|
||||||
|
|
||||||
uart *machine.UART
|
|
||||||
|
|
||||||
// used for software flow control
|
|
||||||
cts, rts machine.Pin
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultAdapter is the default adapter on the current system.
|
|
||||||
//
|
|
||||||
// Make sure to call Enable() before using it to initialize the adapter.
|
|
||||||
var DefaultAdapter = &Adapter{
|
|
||||||
hciAdapter: hciAdapter{
|
|
||||||
isDefault: true,
|
|
||||||
connectHandler: func(device Device, connected bool) {
|
|
||||||
return
|
|
||||||
},
|
|
||||||
connectedDevices: make([]Device, 0, maxConnections),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUART sets the UART to use for the HCI connection.
|
|
||||||
// It must be called before calling Enable().
|
|
||||||
// Note that the UART must be configured with hardware flow control, or
|
|
||||||
// SetSoftwareFlowControl() must be called.
|
|
||||||
func (a *Adapter) SetUART(uart *machine.UART) error {
|
|
||||||
a.uart = uart
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSoftwareFlowControl sets the pins to use for software flow control,
|
|
||||||
// if hardware flow control is not available.
|
|
||||||
func (a *Adapter) SetSoftwareFlowControl(cts, rts machine.Pin) error {
|
|
||||||
a.cts = cts
|
|
||||||
a.rts = rts
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable configures the BLE stack. It must be called before any
|
|
||||||
// Bluetooth-related calls (unless otherwise indicated).
|
|
||||||
func (a *Adapter) Enable() error {
|
|
||||||
transport := &hciUART{uart: a.uart}
|
|
||||||
if a.cts != 0 && a.rts != 0 {
|
|
||||||
transport.rts = a.rts
|
|
||||||
a.rts.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
a.rts.High()
|
|
||||||
|
|
||||||
transport.cts = a.cts
|
|
||||||
a.cts.Configure(machine.PinConfig{Mode: machine.PinInput})
|
|
||||||
}
|
|
||||||
|
|
||||||
a.hci, a.att = newBLEStack(transport)
|
|
||||||
a.enable()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type hciUART struct {
|
|
||||||
uart *machine.UART
|
|
||||||
|
|
||||||
// used for software flow control
|
|
||||||
cts, rts machine.Pin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciUART) startRead() {
|
|
||||||
if h.rts != machine.NoPin {
|
|
||||||
h.rts.Low()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciUART) endRead() {
|
|
||||||
if h.rts != machine.NoPin {
|
|
||||||
h.rts.High()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciUART) Buffered() int {
|
|
||||||
return h.uart.Buffered()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciUART) ReadByte() (byte, error) {
|
|
||||||
return h.uart.ReadByte()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciUART) Read(buf []byte) (int, error) {
|
|
||||||
return h.uart.Read(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
const writeAttempts = 200
|
|
||||||
|
|
||||||
func (h *hciUART) Write(buf []byte) (int, error) {
|
|
||||||
if h.cts != machine.NoPin {
|
|
||||||
retries := writeAttempts
|
|
||||||
for h.cts.Get() {
|
|
||||||
retries--
|
|
||||||
if retries == 0 {
|
|
||||||
return 0, ErrHCITimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := h.uart.Write(buf)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build !baremetal
|
//go:build !baremetal
|
||||||
|
// +build !baremetal
|
||||||
|
|
||||||
// Some documentation for the BlueZ D-Bus interface:
|
// Some documentation for the BlueZ D-Bus interface:
|
||||||
// https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc
|
// https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc
|
||||||
|
@ -7,23 +8,18 @@ package bluetooth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/muka/go-bluetooth/api"
|
||||||
|
"github.com/muka/go-bluetooth/bluez/profile/adapter"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultAdapter = "hci0"
|
|
||||||
|
|
||||||
type Adapter struct {
|
type Adapter struct {
|
||||||
|
adapter *adapter.Adapter1
|
||||||
id string
|
id string
|
||||||
scanCancelChan chan struct{}
|
cancelChan chan struct{}
|
||||||
bus *dbus.Conn
|
|
||||||
bluez dbus.BusObject // object at /
|
|
||||||
adapter dbus.BusObject // object at /org/bluez/hciX
|
|
||||||
address string
|
|
||||||
defaultAdvertisement *Advertisement
|
defaultAdvertisement *Advertisement
|
||||||
|
|
||||||
connectHandler func(device Device, connected bool)
|
connectHandler func(device Address, connected bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultAdapter is the default adapter on the system. On Linux, it is the
|
// DefaultAdapter is the default adapter on the system. On Linux, it is the
|
||||||
|
@ -31,38 +27,29 @@ type Adapter struct {
|
||||||
//
|
//
|
||||||
// Make sure to call Enable() before using it to initialize the adapter.
|
// Make sure to call Enable() before using it to initialize the adapter.
|
||||||
var DefaultAdapter = &Adapter{
|
var DefaultAdapter = &Adapter{
|
||||||
id: defaultAdapter,
|
connectHandler: func(device Address, connected bool) {
|
||||||
connectHandler: func(device Device, connected bool) {
|
return
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable configures the BLE stack. It must be called before any
|
// Enable configures the BLE stack. It must be called before any
|
||||||
// Bluetooth-related calls (unless otherwise indicated).
|
// Bluetooth-related calls (unless otherwise indicated).
|
||||||
func (a *Adapter) Enable() (err error) {
|
func (a *Adapter) Enable() (err error) {
|
||||||
bus, err := dbus.SystemBus()
|
if a.id == "" {
|
||||||
if err != nil {
|
a.adapter, err = api.GetDefaultAdapter()
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return
|
||||||
a.bus = bus
|
|
||||||
a.bluez = a.bus.Object("org.bluez", dbus.ObjectPath("/"))
|
|
||||||
a.adapter = a.bus.Object("org.bluez", dbus.ObjectPath("/org/bluez/"+a.id))
|
|
||||||
addr, err := a.adapter.GetProperty("org.bluez.Adapter1.Address")
|
|
||||||
if err != nil {
|
|
||||||
if err, ok := err.(dbus.Error); ok && err.Name == "org.freedesktop.DBus.Error.UnknownObject" {
|
|
||||||
return fmt.Errorf("bluetooth: adapter %s does not exist", a.adapter.Path())
|
|
||||||
}
|
}
|
||||||
return fmt.Errorf("could not activate BlueZ adapter: %w", err)
|
a.id, err = a.adapter.GetAdapterID()
|
||||||
}
|
}
|
||||||
addr.Store(&a.address)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Adapter) Address() (MACAddress, error) {
|
func (a *Adapter) Address() (MACAddress, error) {
|
||||||
if a.address == "" {
|
if a.adapter == nil {
|
||||||
return MACAddress{}, errors.New("adapter not enabled")
|
return MACAddress{}, errors.New("adapter not enabled")
|
||||||
}
|
}
|
||||||
mac, err := ParseMAC(a.address)
|
mac, err := ParseMAC(a.adapter.Properties.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MACAddress{}, err
|
return MACAddress{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,134 +0,0 @@
|
||||||
//go:build ninafw
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const maxConnections = 1
|
|
||||||
|
|
||||||
// Adapter represents the HCI connection to the NINA fw using the hardware UART.
|
|
||||||
type Adapter struct {
|
|
||||||
hciAdapter
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultAdapter is the default adapter on the current system.
|
|
||||||
//
|
|
||||||
// Make sure to call Enable() before using it to initialize the adapter.
|
|
||||||
var DefaultAdapter = &Adapter{
|
|
||||||
hciAdapter: hciAdapter{
|
|
||||||
isDefault: true,
|
|
||||||
connectHandler: func(device Device, connected bool) {
|
|
||||||
return
|
|
||||||
},
|
|
||||||
connectedDevices: make([]Device, 0, maxConnections),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable configures the BLE stack. It must be called before any
|
|
||||||
// Bluetooth-related calls (unless otherwise indicated).
|
|
||||||
func (a *Adapter) Enable() error {
|
|
||||||
// reset the NINA in BLE mode
|
|
||||||
machine.NINA_CS.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
machine.NINA_CS.Low()
|
|
||||||
|
|
||||||
if machine.NINA_RESET_INVERTED {
|
|
||||||
resetNINAInverted()
|
|
||||||
} else {
|
|
||||||
resetNINA()
|
|
||||||
}
|
|
||||||
|
|
||||||
// serial port for nina chip
|
|
||||||
uart := machine.UART_NINA
|
|
||||||
cfg := machine.UARTConfig{
|
|
||||||
TX: machine.NINA_TX,
|
|
||||||
RX: machine.NINA_RX,
|
|
||||||
BaudRate: machine.NINA_BAUDRATE,
|
|
||||||
}
|
|
||||||
if !machine.NINA_SOFT_FLOWCONTROL {
|
|
||||||
cfg.CTS = machine.NINA_CTS
|
|
||||||
cfg.RTS = machine.NINA_RTS
|
|
||||||
}
|
|
||||||
|
|
||||||
uart.Configure(cfg)
|
|
||||||
|
|
||||||
transport := &hciUART{uart: uart}
|
|
||||||
if machine.NINA_SOFT_FLOWCONTROL {
|
|
||||||
machine.NINA_RTS.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
machine.NINA_RTS.High()
|
|
||||||
|
|
||||||
machine.NINA_CTS.Configure(machine.PinConfig{Mode: machine.PinInput})
|
|
||||||
}
|
|
||||||
|
|
||||||
a.hci, a.att = newBLEStack(transport)
|
|
||||||
return a.enable()
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetNINA() {
|
|
||||||
machine.NINA_RESETN.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
|
|
||||||
machine.NINA_RESETN.High()
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
machine.NINA_RESETN.Low()
|
|
||||||
time.Sleep(1000 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetNINAInverted() {
|
|
||||||
machine.NINA_RESETN.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
|
|
||||||
machine.NINA_RESETN.Low()
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
machine.NINA_RESETN.High()
|
|
||||||
time.Sleep(1000 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
type hciUART struct {
|
|
||||||
uart *machine.UART
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciUART) startRead() {
|
|
||||||
if machine.NINA_SOFT_FLOWCONTROL {
|
|
||||||
machine.NINA_RTS.Low()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciUART) endRead() {
|
|
||||||
if machine.NINA_SOFT_FLOWCONTROL {
|
|
||||||
machine.NINA_RTS.High()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciUART) Buffered() int {
|
|
||||||
return h.uart.Buffered()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciUART) ReadByte() (byte, error) {
|
|
||||||
return h.uart.ReadByte()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hciUART) Read(buf []byte) (int, error) {
|
|
||||||
return h.uart.Read(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
const writeAttempts = 200
|
|
||||||
|
|
||||||
func (h *hciUART) Write(buf []byte) (int, error) {
|
|
||||||
if machine.NINA_SOFT_FLOWCONTROL {
|
|
||||||
retries := writeAttempts
|
|
||||||
for machine.NINA_CTS.Get() {
|
|
||||||
retries--
|
|
||||||
if retries == 0 {
|
|
||||||
return 0, ErrHCITimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := h.uart.Write(buf)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build softdevice && s110v8
|
//go:build softdevice && s110v8
|
||||||
|
// +build softdevice,s110v8
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
@ -42,13 +43,8 @@ func handleEvent() {
|
||||||
gapEvent := eventBuf.evt.unionfield_gap_evt()
|
gapEvent := eventBuf.evt.unionfield_gap_evt()
|
||||||
switch id {
|
switch id {
|
||||||
case C.BLE_GAP_EVT_CONNECTED:
|
case C.BLE_GAP_EVT_CONNECTED:
|
||||||
currentConnection.handle.Reg = uint16(gapEvent.conn_handle)
|
currentConnection.Reg = gapEvent.conn_handle
|
||||||
connectEvent := gapEvent.params.unionfield_connected()
|
DefaultAdapter.connectHandler(Address{}, true)
|
||||||
device := Device{
|
|
||||||
Address: Address{makeMACAddress(connectEvent.peer_addr)},
|
|
||||||
connectionHandle: gapEvent.conn_handle,
|
|
||||||
}
|
|
||||||
DefaultAdapter.connectHandler(device, true)
|
|
||||||
case C.BLE_GAP_EVT_DISCONNECTED:
|
case C.BLE_GAP_EVT_DISCONNECTED:
|
||||||
if defaultAdvertisement.isAdvertising.Get() != 0 {
|
if defaultAdvertisement.isAdvertising.Get() != 0 {
|
||||||
// The advertisement was running but was automatically stopped
|
// The advertisement was running but was automatically stopped
|
||||||
|
@ -59,11 +55,8 @@ func handleEvent() {
|
||||||
// necessary.
|
// necessary.
|
||||||
defaultAdvertisement.start()
|
defaultAdvertisement.start()
|
||||||
}
|
}
|
||||||
currentConnection.handle.Reg = C.BLE_CONN_HANDLE_INVALID
|
currentConnection.Reg = C.BLE_CONN_HANDLE_INVALID
|
||||||
device := Device{
|
DefaultAdapter.connectHandler(Address{}, false)
|
||||||
connectionHandle: gapEvent.conn_handle,
|
|
||||||
}
|
|
||||||
DefaultAdapter.connectHandler(device, false)
|
|
||||||
case C.BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
|
case C.BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
|
||||||
// Respond with the default PPCP connection parameters by passing
|
// Respond with the default PPCP connection parameters by passing
|
||||||
// nil:
|
// nil:
|
||||||
|
@ -117,13 +110,5 @@ func (a *Adapter) Address() (MACAddress, error) {
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return MACAddress{}, Error(errCode)
|
return MACAddress{}, Error(errCode)
|
||||||
}
|
}
|
||||||
return MACAddress{MAC: makeAddress(addr.addr)}, nil
|
return MACAddress{MAC: addr.addr}, nil
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a C.ble_gap_addr_t to a MACAddress struct.
|
|
||||||
func makeMACAddress(addr C.ble_gap_addr_t) MACAddress {
|
|
||||||
return MACAddress{
|
|
||||||
MAC: makeAddress(addr.addr),
|
|
||||||
isRandom: addr.addr_type != 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
|
//go:build (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
|
||||||
|
// +build softdevice,s132v6 softdevice,s140v6 softdevice,s140v7
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
@ -25,24 +26,20 @@ func handleEvent() {
|
||||||
switch id {
|
switch id {
|
||||||
case C.BLE_GAP_EVT_CONNECTED:
|
case C.BLE_GAP_EVT_CONNECTED:
|
||||||
connectEvent := gapEvent.params.unionfield_connected()
|
connectEvent := gapEvent.params.unionfield_connected()
|
||||||
device := Device{
|
|
||||||
Address: Address{makeMACAddress(connectEvent.peer_addr)},
|
|
||||||
connectionHandle: gapEvent.conn_handle,
|
|
||||||
}
|
|
||||||
switch connectEvent.role {
|
switch connectEvent.role {
|
||||||
case C.BLE_GAP_ROLE_PERIPH:
|
case C.BLE_GAP_ROLE_PERIPH:
|
||||||
if debug {
|
if debug {
|
||||||
println("evt: connected in peripheral role")
|
println("evt: connected in peripheral role")
|
||||||
}
|
}
|
||||||
currentConnection.handle.Reg = uint16(gapEvent.conn_handle)
|
currentConnection.Reg = gapEvent.conn_handle
|
||||||
DefaultAdapter.connectHandler(device, true)
|
DefaultAdapter.connectHandler(Address{}, true)
|
||||||
case C.BLE_GAP_ROLE_CENTRAL:
|
case C.BLE_GAP_ROLE_CENTRAL:
|
||||||
if debug {
|
if debug {
|
||||||
println("evt: connected in central role")
|
println("evt: connected in central role")
|
||||||
}
|
}
|
||||||
connectionAttempt.connectionHandle = gapEvent.conn_handle
|
connectionAttempt.connectionHandle = gapEvent.conn_handle
|
||||||
connectionAttempt.state.Set(2) // connection was successful
|
connectionAttempt.state.Set(2) // connection was successful
|
||||||
DefaultAdapter.connectHandler(device, true)
|
DefaultAdapter.connectHandler(Address{}, true)
|
||||||
}
|
}
|
||||||
case C.BLE_GAP_EVT_DISCONNECTED:
|
case C.BLE_GAP_EVT_DISCONNECTED:
|
||||||
if debug {
|
if debug {
|
||||||
|
@ -50,11 +47,11 @@ func handleEvent() {
|
||||||
}
|
}
|
||||||
// Clean up state for this connection.
|
// Clean up state for this connection.
|
||||||
for i, cb := range gattcNotificationCallbacks {
|
for i, cb := range gattcNotificationCallbacks {
|
||||||
if uint16(cb.connectionHandle) == currentConnection.handle.Reg {
|
if cb.connectionHandle == currentConnection.Reg {
|
||||||
gattcNotificationCallbacks[i].valueHandle = 0 // 0 means invalid
|
gattcNotificationCallbacks[i].valueHandle = 0 // 0 means invalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentConnection.handle.Reg = C.BLE_CONN_HANDLE_INVALID
|
currentConnection.Reg = C.BLE_CONN_HANDLE_INVALID
|
||||||
// Auto-restart advertisement if needed.
|
// Auto-restart advertisement if needed.
|
||||||
if defaultAdvertisement.isAdvertising.Get() != 0 {
|
if defaultAdvertisement.isAdvertising.Get() != 0 {
|
||||||
// The advertisement was running but was automatically stopped
|
// The advertisement was running but was automatically stopped
|
||||||
|
@ -65,21 +62,10 @@ func handleEvent() {
|
||||||
// necessary.
|
// necessary.
|
||||||
C.sd_ble_gap_adv_start(defaultAdvertisement.handle, C.BLE_CONN_CFG_TAG_DEFAULT)
|
C.sd_ble_gap_adv_start(defaultAdvertisement.handle, C.BLE_CONN_CFG_TAG_DEFAULT)
|
||||||
}
|
}
|
||||||
device := Device{
|
DefaultAdapter.connectHandler(Address{}, false)
|
||||||
connectionHandle: gapEvent.conn_handle,
|
|
||||||
}
|
|
||||||
DefaultAdapter.connectHandler(device, false)
|
|
||||||
case C.BLE_GAP_EVT_CONN_PARAM_UPDATE:
|
|
||||||
if debug {
|
|
||||||
// Print connection parameters for easy debugging.
|
|
||||||
params := gapEvent.params.unionfield_conn_param_update().conn_params
|
|
||||||
interval_ms := params.min_conn_interval * 125 / 100 // min and max are the same here
|
|
||||||
print("conn param update interval=", interval_ms, "ms latency=", params.slave_latency, " timeout=", params.conn_sup_timeout*10, "ms")
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
case C.BLE_GAP_EVT_ADV_REPORT:
|
case C.BLE_GAP_EVT_ADV_REPORT:
|
||||||
advReport := gapEvent.params.unionfield_adv_report()
|
advReport := gapEvent.params.unionfield_adv_report()
|
||||||
if debug && &scanReportBuffer.data[0] != (*byte)(unsafe.Pointer(advReport.data.p_data)) {
|
if debug && &scanReportBuffer.data[0] != advReport.data.p_data {
|
||||||
// Sanity check.
|
// Sanity check.
|
||||||
panic("scanReportBuffer != advReport.p_data")
|
panic("scanReportBuffer != advReport.p_data")
|
||||||
}
|
}
|
||||||
|
@ -88,7 +74,8 @@ func handleEvent() {
|
||||||
scanReportBuffer.len = byte(advReport.data.len)
|
scanReportBuffer.len = byte(advReport.data.len)
|
||||||
globalScanResult.RSSI = int16(advReport.rssi)
|
globalScanResult.RSSI = int16(advReport.rssi)
|
||||||
globalScanResult.Address = Address{
|
globalScanResult.Address = Address{
|
||||||
makeMACAddress(advReport.peer_addr),
|
MACAddress{MAC: advReport.peer_addr.addr,
|
||||||
|
isRandom: advReport.peer_addr.bitfield_addr_type() != 0},
|
||||||
}
|
}
|
||||||
globalScanResult.AdvertisementPayload = &scanReportBuffer
|
globalScanResult.AdvertisementPayload = &scanReportBuffer
|
||||||
// Signal to the main thread that there was a scan report.
|
// Signal to the main thread that there was a scan report.
|
||||||
|
@ -115,21 +102,6 @@ func handleEvent() {
|
||||||
C.sd_ble_gap_phy_update(gapEvent.conn_handle, &phyUpdateRequest.peer_preferred_phys)
|
C.sd_ble_gap_phy_update(gapEvent.conn_handle, &phyUpdateRequest.peer_preferred_phys)
|
||||||
case C.BLE_GAP_EVT_PHY_UPDATE:
|
case C.BLE_GAP_EVT_PHY_UPDATE:
|
||||||
// ignore confirmation of phy successfully updated
|
// ignore confirmation of phy successfully updated
|
||||||
case C.BLE_GAP_EVT_TIMEOUT:
|
|
||||||
timeoutEvt := gapEvent.params.unionfield_timeout()
|
|
||||||
switch timeoutEvt.src {
|
|
||||||
case C.BLE_GAP_TIMEOUT_SRC_CONN:
|
|
||||||
// Failed to connect to a peripheral.
|
|
||||||
if debug {
|
|
||||||
println("gap timeout: conn")
|
|
||||||
}
|
|
||||||
connectionAttempt.state.Set(3) // connection timed out
|
|
||||||
default:
|
|
||||||
// For example a scan timeout.
|
|
||||||
if debug {
|
|
||||||
println("gap timeout: other")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
if debug {
|
if debug {
|
||||||
println("unknown GAP event:", id)
|
println("unknown GAP event:", id)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build softdevice && s113v7
|
//go:build softdevice && s113v7
|
||||||
|
// +build softdevice,s113v7
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
@ -27,18 +28,13 @@ func handleEvent() {
|
||||||
if debug {
|
if debug {
|
||||||
println("evt: connected in peripheral role")
|
println("evt: connected in peripheral role")
|
||||||
}
|
}
|
||||||
currentConnection.handle.Reg = uint16(gapEvent.conn_handle)
|
currentConnection.Reg = gapEvent.conn_handle
|
||||||
connectEvent := gapEvent.params.unionfield_connected()
|
DefaultAdapter.connectHandler(Address{}, true)
|
||||||
device := Device{
|
|
||||||
Address: Address{makeMACAddress(connectEvent.peer_addr)},
|
|
||||||
connectionHandle: gapEvent.conn_handle,
|
|
||||||
}
|
|
||||||
DefaultAdapter.connectHandler(device, true)
|
|
||||||
case C.BLE_GAP_EVT_DISCONNECTED:
|
case C.BLE_GAP_EVT_DISCONNECTED:
|
||||||
if debug {
|
if debug {
|
||||||
println("evt: disconnected")
|
println("evt: disconnected")
|
||||||
}
|
}
|
||||||
currentConnection.handle.Reg = C.BLE_CONN_HANDLE_INVALID
|
currentConnection.Reg = C.BLE_CONN_HANDLE_INVALID
|
||||||
// Auto-restart advertisement if needed.
|
// Auto-restart advertisement if needed.
|
||||||
if defaultAdvertisement.isAdvertising.Get() != 0 {
|
if defaultAdvertisement.isAdvertising.Get() != 0 {
|
||||||
// The advertisement was running but was automatically stopped
|
// The advertisement was running but was automatically stopped
|
||||||
|
@ -49,10 +45,7 @@ func handleEvent() {
|
||||||
// necessary.
|
// necessary.
|
||||||
C.sd_ble_gap_adv_start(defaultAdvertisement.handle, C.BLE_CONN_CFG_TAG_DEFAULT)
|
C.sd_ble_gap_adv_start(defaultAdvertisement.handle, C.BLE_CONN_CFG_TAG_DEFAULT)
|
||||||
}
|
}
|
||||||
device := Device{
|
DefaultAdapter.connectHandler(Address{}, false)
|
||||||
connectionHandle: gapEvent.conn_handle,
|
|
||||||
}
|
|
||||||
DefaultAdapter.connectHandler(device, false)
|
|
||||||
case C.BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST:
|
case C.BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST:
|
||||||
// We need to respond with sd_ble_gap_data_length_update. Setting
|
// We need to respond with sd_ble_gap_data_length_update. Setting
|
||||||
// both parameters to nil will make sure we send the default values.
|
// both parameters to nil will make sure we send the default values.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build (softdevice && s113v7) || (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
|
//go:build (softdevice && s113v7) || (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
|
||||||
|
// +build softdevice,s113v7 softdevice,s132v6 softdevice,s140v6 softdevice,s140v7
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ func (a *Adapter) enable() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable the BLE stack.
|
// Enable the BLE stack.
|
||||||
appRAMBase := C.uint32_t(uintptr(unsafe.Pointer(&appRAMBase)))
|
appRAMBase := uint32(uintptr(unsafe.Pointer(&appRAMBase)))
|
||||||
errCode = C.sd_ble_enable(&appRAMBase)
|
errCode = C.sd_ble_enable(&appRAMBase)
|
||||||
return makeError(errCode)
|
return makeError(errCode)
|
||||||
}
|
}
|
||||||
|
@ -57,13 +58,5 @@ func (a *Adapter) Address() (MACAddress, error) {
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return MACAddress{}, Error(errCode)
|
return MACAddress{}, Error(errCode)
|
||||||
}
|
}
|
||||||
return MACAddress{MAC: makeAddress(addr.addr)}, nil
|
return MACAddress{MAC: addr.addr}, nil
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a C.ble_gap_addr_t to a MACAddress struct.
|
|
||||||
func makeMACAddress(addr C.ble_gap_addr_t) MACAddress {
|
|
||||||
return MACAddress{
|
|
||||||
MAC: makeAddress(addr.addr),
|
|
||||||
isRandom: addr.bitfield_addr_type() != 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build softdevice && s110v8
|
//go:build softdevice && s110v8
|
||||||
|
// +build softdevice,s110v8
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build softdevice && s113v7
|
//go:build softdevice && s113v7
|
||||||
|
// +build softdevice,s113v7
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build softdevice && s132v6
|
//go:build softdevice && s132v6
|
||||||
|
// +build softdevice,s132v6
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build softdevice && s140v6
|
//go:build softdevice && s140v6
|
||||||
|
// +build softdevice,s140v6
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build softdevice && s140v7
|
//go:build softdevice && s140v7
|
||||||
|
// +build softdevice,s140v7
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build softdevice
|
//go:build softdevice
|
||||||
|
// +build softdevice
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// There can only be one connection at a time in the default configuration.
|
// There can only be one connection at a time in the default configuration.
|
||||||
var currentConnection = volatileHandle{handle: volatile.Register16{C.BLE_CONN_HANDLE_INVALID}}
|
var currentConnection = volatile.Register16{C.BLE_CONN_HANDLE_INVALID}
|
||||||
|
|
||||||
// Globally allocated buffer for incoming SoftDevice events.
|
// Globally allocated buffer for incoming SoftDevice events.
|
||||||
var eventBuf struct {
|
var eventBuf struct {
|
||||||
|
@ -48,7 +49,7 @@ type Adapter struct {
|
||||||
scanning bool
|
scanning bool
|
||||||
charWriteHandlers []charWriteHandler
|
charWriteHandlers []charWriteHandler
|
||||||
|
|
||||||
connectHandler func(device Device, connected bool)
|
connectHandler func(device Address, connected bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultAdapter is the default adapter on the current system. On Nordic chips,
|
// DefaultAdapter is the default adapter on the current system. On Nordic chips,
|
||||||
|
@ -56,11 +57,11 @@ type Adapter struct {
|
||||||
//
|
//
|
||||||
// Make sure to call Enable() before using it to initialize the adapter.
|
// Make sure to call Enable() before using it to initialize the adapter.
|
||||||
var DefaultAdapter = &Adapter{isDefault: true,
|
var DefaultAdapter = &Adapter{isDefault: true,
|
||||||
connectHandler: func(device Device, connected bool) {
|
connectHandler: func(device Address, connected bool) {
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
|
|
||||||
var eventBufLen C.uint16_t
|
var eventBufLen uint16
|
||||||
|
|
||||||
// Enable configures the BLE stack. It must be called before any
|
// Enable configures the BLE stack. It must be called before any
|
||||||
// Bluetooth-related calls (unless otherwise indicated).
|
// Bluetooth-related calls (unless otherwise indicated).
|
||||||
|
@ -72,8 +73,8 @@ func (a *Adapter) Enable() error {
|
||||||
// Enable the IRQ that handles all events.
|
// Enable the IRQ that handles all events.
|
||||||
intr := interrupt.New(nrf.IRQ_SWI2, func(interrupt.Interrupt) {
|
intr := interrupt.New(nrf.IRQ_SWI2, func(interrupt.Interrupt) {
|
||||||
for {
|
for {
|
||||||
eventBufLen = C.uint16_t(unsafe.Sizeof(eventBuf))
|
eventBufLen = uint16(unsafe.Sizeof(eventBuf))
|
||||||
errCode := C.sd_ble_evt_get((*C.uint8_t)(unsafe.Pointer(&eventBuf)), &eventBufLen)
|
errCode := C.sd_ble_evt_get((*uint8)(unsafe.Pointer(&eventBuf)), &eventBufLen)
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
// Possible error conditions:
|
// Possible error conditions:
|
||||||
// * NRF_ERROR_NOT_FOUND: no events left, break
|
// * NRF_ERROR_NOT_FOUND: no events left, break
|
||||||
|
@ -97,7 +98,7 @@ func (a *Adapter) Enable() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
errCode := C.sd_ble_gap_device_name_set(&secModeOpen, (*C.uint8_t)(unsafe.Pointer(&defaultDeviceName[0])), C.uint16_t(len(defaultDeviceName)))
|
errCode := C.sd_ble_gap_device_name_set(&secModeOpen, &defaultDeviceName[0], uint16(len(defaultDeviceName)))
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return Error(errCode)
|
return Error(errCode)
|
||||||
}
|
}
|
||||||
|
@ -116,7 +117,7 @@ func (a *Adapter) Enable() error {
|
||||||
// play well with the SoftDevice. Restore interrupts to the previous state with
|
// play well with the SoftDevice. Restore interrupts to the previous state with
|
||||||
// RestoreInterrupts.
|
// RestoreInterrupts.
|
||||||
func DisableInterrupts() uintptr {
|
func DisableInterrupts() uintptr {
|
||||||
var is_nested_critical_region C.uint8_t
|
var is_nested_critical_region uint8
|
||||||
C.sd_nvic_critical_region_enter(&is_nested_critical_region)
|
C.sd_nvic_critical_region_enter(&is_nested_critical_region)
|
||||||
return uintptr(is_nested_critical_region)
|
return uintptr(is_nested_critical_region)
|
||||||
}
|
}
|
||||||
|
@ -125,43 +126,5 @@ func DisableInterrupts() uintptr {
|
||||||
// DisableInterrupts. The mask parameter must be the value returned by
|
// DisableInterrupts. The mask parameter must be the value returned by
|
||||||
// DisableInterrupts.
|
// DisableInterrupts.
|
||||||
func RestoreInterrupts(mask uintptr) {
|
func RestoreInterrupts(mask uintptr) {
|
||||||
C.sd_nvic_critical_region_exit(C.uint8_t(mask))
|
C.sd_nvic_critical_region_exit(uint8(mask))
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper for volatile.Register16 that uses C.uint16_t instead of uint16, for
|
|
||||||
// easier interoperability with C.
|
|
||||||
type volatileHandle struct {
|
|
||||||
handle volatile.Register16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *volatileHandle) Set(handle C.uint16_t) {
|
|
||||||
a.handle.Set(uint16(handle))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *volatileHandle) Get() C.uint16_t {
|
|
||||||
return C.uint16_t(a.handle.Get())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a SoftDevice MAC address into a Go MAC address.
|
|
||||||
func makeAddress(mac [6]C.uint8_t) MAC {
|
|
||||||
return MAC{
|
|
||||||
uint8(mac[0]),
|
|
||||||
uint8(mac[1]),
|
|
||||||
uint8(mac[2]),
|
|
||||||
uint8(mac[3]),
|
|
||||||
uint8(mac[4]),
|
|
||||||
uint8(mac[5]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a Go MAC address into a SoftDevice MAC Address.
|
|
||||||
func makeSDAddress(mac MAC) [6]C.uint8_t {
|
|
||||||
return [6]C.uint8_t{
|
|
||||||
C.uint8_t(mac[0]),
|
|
||||||
C.uint8_t(mac[1]),
|
|
||||||
C.uint8_t(mac[2]),
|
|
||||||
C.uint8_t(mac[3]),
|
|
||||||
C.uint8_t(mac[4]),
|
|
||||||
C.uint8_t(mac[5]),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-ole/go-ole"
|
"github.com/go-ole/go-ole"
|
||||||
|
@ -13,16 +12,14 @@ import (
|
||||||
type Adapter struct {
|
type Adapter struct {
|
||||||
watcher *advertisement.BluetoothLEAdvertisementWatcher
|
watcher *advertisement.BluetoothLEAdvertisementWatcher
|
||||||
|
|
||||||
connectHandler func(device Device, connected bool)
|
connectHandler func(device Address, connected bool)
|
||||||
|
|
||||||
defaultAdvertisement *Advertisement
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultAdapter is the default adapter on the system.
|
// DefaultAdapter is the default adapter on the system.
|
||||||
//
|
//
|
||||||
// Make sure to call Enable() before using it to initialize the adapter.
|
// Make sure to call Enable() before using it to initialize the adapter.
|
||||||
var DefaultAdapter = &Adapter{
|
var DefaultAdapter = &Adapter{
|
||||||
connectHandler: func(device Device, connected bool) {
|
connectHandler: func(device Address, connected bool) {
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -43,14 +40,10 @@ func awaitAsyncOperation(asyncOperation *foundation.IAsyncOperation, genericPara
|
||||||
|
|
||||||
// Wait until the async operation completes.
|
// Wait until the async operation completes.
|
||||||
waitChan := make(chan struct{})
|
waitChan := make(chan struct{})
|
||||||
handler := foundation.NewAsyncOperationCompletedHandler(ole.NewGUID(iid), func(instance *foundation.AsyncOperationCompletedHandler, asyncInfo *foundation.IAsyncOperation, asyncStatus foundation.AsyncStatus) {
|
asyncOperation.SetCompleted(foundation.NewAsyncOperationCompletedHandler(ole.NewGUID(iid), func(instance *foundation.AsyncOperationCompletedHandler, asyncInfo *foundation.IAsyncOperation, asyncStatus foundation.AsyncStatus) {
|
||||||
status = asyncStatus
|
status = asyncStatus
|
||||||
close(waitChan)
|
close(waitChan)
|
||||||
})
|
}))
|
||||||
defer handler.Release()
|
|
||||||
|
|
||||||
asyncOperation.SetCompleted(handler)
|
|
||||||
|
|
||||||
// Wait until async operation has stopped, and finish.
|
// Wait until async operation has stopped, and finish.
|
||||||
<-waitChan
|
<-waitChan
|
||||||
|
|
||||||
|
@ -59,8 +52,3 @@ func awaitAsyncOperation(asyncOperation *foundation.IAsyncOperation, genericPara
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Adapter) Address() (MACAddress, error) {
|
|
||||||
// TODO: get mac address
|
|
||||||
return MACAddress{}, errors.New("not implemented")
|
|
||||||
}
|
|
||||||
|
|
1203
att_hci.go
1203
att_hci.go
File diff suppressed because it is too large
Load diff
|
@ -5,4 +5,5 @@
|
||||||
// those produced by Nordic Semiconductor.
|
// those produced by Nordic Semiconductor.
|
||||||
//
|
//
|
||||||
// This package can be use to create Bluetooth Low Energy centrals as well as peripherals.
|
// This package can be use to create Bluetooth Low Energy centrals as well as peripherals.
|
||||||
package bluetooth // import "gitrepo.ru/neonxp/bluetooth"
|
//
|
||||||
|
package bluetooth // import "tinygo.org/x/bluetooth"
|
||||||
|
|
File diff suppressed because it is too large
Load diff
5
debug.go
5
debug.go
|
@ -1,5 +0,0 @@
|
||||||
//go:build bledebug
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
var debug = true
|
|
|
@ -1,8 +1,8 @@
|
||||||
//go:build softdevice
|
//go:build softdevice
|
||||||
|
// +build softdevice
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
// #include <stdint.h>
|
|
||||||
// #include "nrf_error.h"
|
// #include "nrf_error.h"
|
||||||
// #include "nrf_error_sdm.h"
|
// #include "nrf_error_sdm.h"
|
||||||
import "C"
|
import "C"
|
||||||
|
@ -84,7 +84,7 @@ func (e Error) Error() string {
|
||||||
|
|
||||||
// makeError returns an error (using the Error type) if the error code is
|
// makeError returns an error (using the Error type) if the error code is
|
||||||
// non-zero, otherwise it returns nil. It is used with internal API calls.
|
// non-zero, otherwise it returns nil. It is used with internal API calls.
|
||||||
func makeError(code C.uint32_t) error {
|
func makeError(code uint32) error {
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
return Error(code)
|
return Error(code)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adapter = bluetooth.DefaultAdapter
|
var adapter = bluetooth.DefaultAdapter
|
||||||
|
@ -13,9 +13,6 @@ func main() {
|
||||||
adv := adapter.DefaultAdvertisement()
|
adv := adapter.DefaultAdvertisement()
|
||||||
must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
|
must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
|
||||||
LocalName: "Go Bluetooth",
|
LocalName: "Go Bluetooth",
|
||||||
ManufacturerData: []bluetooth.ManufacturerDataElement{
|
|
||||||
{CompanyID: 0xffff, Data: []byte{0x01, 0x02}},
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
must("start adv", adv.Start())
|
must("start adv", adv.Start())
|
||||||
|
|
||||||
|
|
71
examples/channelscan/main.go
Normal file
71
examples/channelscan/main.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// This example program shows using Go routines and channels to coordinate
|
||||||
|
// BLE scanning.
|
||||||
|
//
|
||||||
|
// The first Go routine starts scanning using the BLE adaptor. When it finds
|
||||||
|
// a new device, it puts the information into a channel so it can be displayed.
|
||||||
|
//
|
||||||
|
// The second Go routine is a ticker that puts a "true" value into a channel every 3 seconds.
|
||||||
|
//
|
||||||
|
// The main function uses a select{} statement to wait until one of the two channels is unblocked
|
||||||
|
// by receiving data. If a new device is found, the boolean variable named "found" will
|
||||||
|
// be set to true, so that the timeout is reset for each 3 second period.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/bluetooth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
adapter = bluetooth.DefaultAdapter
|
||||||
|
devices = make(chan bluetooth.ScanResult, 1)
|
||||||
|
ticker = make(chan bool, 1)
|
||||||
|
found = true
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Enable BLE interface.
|
||||||
|
if err := adapter.Enable(); err != nil {
|
||||||
|
panic("failed to enable adaptor:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start scanning
|
||||||
|
go performScan()
|
||||||
|
|
||||||
|
// Start timeout ticker
|
||||||
|
go startTicker()
|
||||||
|
|
||||||
|
// Wait for devices to be scanned
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case device := <-devices:
|
||||||
|
found = true
|
||||||
|
println("found device:", device.Address.String(), device.RSSI, device.LocalName())
|
||||||
|
case <-ticker:
|
||||||
|
if !found {
|
||||||
|
println("no devices found in last 3 seconds...")
|
||||||
|
}
|
||||||
|
found = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func performScan() {
|
||||||
|
println("scanning...")
|
||||||
|
|
||||||
|
err := adapter.Scan(func(adapter *bluetooth.Adapter, device bluetooth.ScanResult) {
|
||||||
|
devices <- device
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to scan:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTicker() {
|
||||||
|
for {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
ticker <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
// This example is intended to be used with the Adafruit Circuitplay Bluefruit board.
|
// This example is intended to be used with the Adafruit Circuitplay Bluefruit board.
|
||||||
// It allows you to control the color of the built-in NeoPixel LEDS while they animate
|
// It allows you to control the color of the built-in NeoPixel LEDS while they animate
|
||||||
// in a circular pattern.
|
// in a circular pattern.
|
||||||
|
//
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -8,7 +9,7 @@ import (
|
||||||
"machine"
|
"machine"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
"tinygo.org/x/drivers/ws2812"
|
"tinygo.org/x/drivers/ws2812"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ func main() {
|
||||||
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
ws = ws2812.New(neo)
|
ws = ws2812.New(neo)
|
||||||
|
|
||||||
adapter.SetConnectHandler(func(d bluetooth.Device, c bool) {
|
adapter.SetConnectHandler(func(d bluetooth.Address, c bool) {
|
||||||
connected = c
|
connected = c
|
||||||
|
|
||||||
if !connected && !disconnected {
|
if !connected && !disconnected {
|
||||||
|
@ -66,7 +67,7 @@ func main() {
|
||||||
Handle: &ledColorCharacteristic,
|
Handle: &ledColorCharacteristic,
|
||||||
UUID: bluetooth.NewUUID(charUUID),
|
UUID: bluetooth.NewUUID(charUUID),
|
||||||
Value: ledColor[:],
|
Value: ledColor[:],
|
||||||
Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
|
Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission,
|
||||||
WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
|
WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
|
||||||
if offset != 0 || len(value) != 3 {
|
if offset != 0 || len(value) != 3 {
|
||||||
return
|
return
|
||||||
|
|
87
examples/clue-scanner/main.go
Normal file
87
examples/clue-scanner/main.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/bluetooth"
|
||||||
|
"tinygo.org/x/drivers/st7789"
|
||||||
|
"tinygo.org/x/tinyterm"
|
||||||
|
"tinygo.org/x/tinyterm/fonts/proggy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
display st7789.Device
|
||||||
|
terminal = tinyterm.NewTerminal(&display)
|
||||||
|
|
||||||
|
black = color.RGBA{0, 0, 0, 255}
|
||||||
|
font = &proggy.TinySZ8pt7b
|
||||||
|
|
||||||
|
adapter = bluetooth.DefaultAdapter
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
initDisplay()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
fmt.Fprintf(terminal, "\nenable interface...")
|
||||||
|
println("enable interface...")
|
||||||
|
must("enable BLE interface", adapter.Enable())
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
println("start scan...")
|
||||||
|
fmt.Fprintf(terminal, "\nstart scan...")
|
||||||
|
|
||||||
|
must("start scan", adapter.Scan(scanHandler))
|
||||||
|
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
println("scanning...")
|
||||||
|
fmt.Fprintf(terminal, "\nscanning...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanHandler(adapter *bluetooth.Adapter, device bluetooth.ScanResult) {
|
||||||
|
println("device:", device.Address.String(), device.RSSI, device.LocalName())
|
||||||
|
fmt.Fprintf(terminal, "\n%s %d %s", device.Address.String(), device.RSSI, device.LocalName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func must(action string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
for {
|
||||||
|
println("failed to " + action + ": " + err.Error())
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDisplay() {
|
||||||
|
machine.SPI1.Configure(machine.SPIConfig{
|
||||||
|
Frequency: 8000000,
|
||||||
|
SCK: machine.TFT_SCK,
|
||||||
|
SDO: machine.TFT_SDO,
|
||||||
|
SDI: machine.TFT_SDO,
|
||||||
|
Mode: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
display = st7789.New(machine.SPI1,
|
||||||
|
machine.TFT_RESET,
|
||||||
|
machine.TFT_DC,
|
||||||
|
machine.TFT_CS,
|
||||||
|
machine.TFT_LITE)
|
||||||
|
display.Configure(st7789.Config{
|
||||||
|
Rotation: st7789.ROTATION_180,
|
||||||
|
Height: 320,
|
||||||
|
FrameRate: st7789.FRAMERATE_111,
|
||||||
|
VSyncLines: st7789.MAX_VSYNC_SCANLINES,
|
||||||
|
})
|
||||||
|
display.FillScreen(black)
|
||||||
|
|
||||||
|
terminal.Configure(&tinyterm.Config{
|
||||||
|
Font: font,
|
||||||
|
FontHeight: 10,
|
||||||
|
FontOffset: 6,
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,84 +0,0 @@
|
||||||
// Test for setting connection parameters.
|
|
||||||
//
|
|
||||||
// To test this feature, run this either on a desktop OS or by flashing it to a
|
|
||||||
// device with TinyGo. Then connect to it from a BLE connection debugger, for
|
|
||||||
// example nRF Connect on Android. After a second, you should see in the log of
|
|
||||||
// the BLE app that the connection latency has been updated. It might look
|
|
||||||
// something like this:
|
|
||||||
//
|
|
||||||
// Connection parameters updated (interval: 510.0ms, latency: 0, timeout: 10000ms)
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
adapter = bluetooth.DefaultAdapter
|
|
||||||
newDevice chan bluetooth.Device
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
must("enable BLE stack", adapter.Enable())
|
|
||||||
|
|
||||||
newDevice = make(chan bluetooth.Device, 1)
|
|
||||||
adapter.SetConnectHandler(func(device bluetooth.Device, connected bool) {
|
|
||||||
// If this is a new device, signal it to the separate goroutine.
|
|
||||||
if connected {
|
|
||||||
select {
|
|
||||||
case newDevice <- device:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Start advertising, so we can be found.
|
|
||||||
const name = "Go BLE test"
|
|
||||||
adv := adapter.DefaultAdvertisement()
|
|
||||||
adv.Configure(bluetooth.AdvertisementOptions{
|
|
||||||
LocalName: name,
|
|
||||||
})
|
|
||||||
adv.Start()
|
|
||||||
println("advertising:", name)
|
|
||||||
|
|
||||||
for device := range newDevice {
|
|
||||||
println("connection from device:", device.Address.String())
|
|
||||||
|
|
||||||
// Discover services and characteristics.
|
|
||||||
svcs, err := device.DiscoverServices(nil)
|
|
||||||
if err != nil {
|
|
||||||
println(" failed to resolve services:", err)
|
|
||||||
}
|
|
||||||
for _, svc := range svcs {
|
|
||||||
println(" service:", svc.UUID().String())
|
|
||||||
chars, err := svc.DiscoverCharacteristics(nil)
|
|
||||||
if err != nil {
|
|
||||||
println(" failed to resolve characteristics:", err)
|
|
||||||
}
|
|
||||||
for _, char := range chars {
|
|
||||||
println(" characteristic:", char.UUID().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update connection parameters (as a test).
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
err = device.RequestConnectionParams(bluetooth.ConnectionParams{
|
|
||||||
MinInterval: bluetooth.NewDuration(495 * time.Millisecond),
|
|
||||||
MaxInterval: bluetooth.NewDuration(510 * time.Millisecond),
|
|
||||||
Timeout: bluetooth.NewDuration(10 * time.Second),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
println(" failed to update connection parameters:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
println(" updated connection parameters")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func must(action string, err error) {
|
|
||||||
if err != nil {
|
|
||||||
panic("failed to " + action + ": " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,22 +3,23 @@
|
||||||
//
|
//
|
||||||
// To run this on a desktop system:
|
// To run this on a desktop system:
|
||||||
//
|
//
|
||||||
// go run ./examples/discover EE:74:7D:C9:2A:68
|
// go run ./examples/discover EE:74:7D:C9:2A:68
|
||||||
//
|
//
|
||||||
// To run this on a microcontroller, change the constant value in the file
|
// To run this on a microcontroller, change the constant value in the file
|
||||||
// "mcu.go" to set the MAC address of the device you want to discover.
|
// "mcu.go" to set the MAC address of the device you want to discover.
|
||||||
// Then, flash to the microcontroller board like this:
|
// Then, flash to the microcontroller board like this:
|
||||||
//
|
//
|
||||||
// tinygo flash -o circuitplay-bluefruit ./examples/discover
|
// tinygo flash -o circuitplay-bluefruit ./examples/discover
|
||||||
//
|
//
|
||||||
// Once the program is flashed to the board, connect to the USB port
|
// Once the program is flashed to the board, connect to the USB port
|
||||||
// via serial to view the output.
|
// via serial to view the output.
|
||||||
|
//
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adapter = bluetooth.DefaultAdapter
|
var adapter = bluetooth.DefaultAdapter
|
||||||
|
@ -43,7 +44,7 @@ func main() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var device bluetooth.Device
|
var device *bluetooth.Device
|
||||||
select {
|
select {
|
||||||
case result := <-ch:
|
case result := <-ch:
|
||||||
device, err = adapter.Connect(result.Address, bluetooth.ConnectionParams{})
|
device, err = adapter.Connect(result.Address, bluetooth.ConnectionParams{})
|
||||||
|
@ -72,12 +73,6 @@ func main() {
|
||||||
}
|
}
|
||||||
for _, char := range chars {
|
for _, char := range chars {
|
||||||
println("-- characteristic", char.UUID().String())
|
println("-- characteristic", char.UUID().String())
|
||||||
mtu, err := char.GetMTU()
|
|
||||||
if err != nil {
|
|
||||||
println(" mtu: error:", err.Error())
|
|
||||||
} else {
|
|
||||||
println(" mtu:", mtu)
|
|
||||||
}
|
|
||||||
n, err := char.Read(buf)
|
n, err := char.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println(" ", err.Error())
|
println(" ", err.Error())
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build baremetal
|
// +build baremetal
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -6,15 +6,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeviceAddress is the MAC address of the Bluetooth peripheral you want to connect to.
|
// replace this with the MAC address of the Bluetooth peripheral you want to connect to.
|
||||||
// Replace this by using -ldflags="-X main.DeviceAddress=[MAC ADDRESS]"
|
const deviceAddress = "E4:B7:F4:11:8D:33"
|
||||||
// where [MAC ADDRESS] is the actual MAC address of the peripheral.
|
|
||||||
// For example:
|
|
||||||
// tinygo flash -target circuitplay-bluefruit -ldflags="-X main.DeviceAddress=7B:36:98:8C:41:1C" ./examples/discover/
|
|
||||||
var DeviceAddress string
|
|
||||||
|
|
||||||
func connectAddress() string {
|
func connectAddress() string {
|
||||||
return DeviceAddress
|
return deviceAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait on baremetal, proceed immediately on desktop OS.
|
// wait on baremetal, proceed immediately on desktop OS.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !baremetal
|
// +build !baremetal
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
@ -3,28 +3,24 @@
|
||||||
//
|
//
|
||||||
// Once connected, it subscribes to notifications for the data value, and
|
// Once connected, it subscribes to notifications for the data value, and
|
||||||
// displays it.
|
// displays it.
|
||||||
// The Heart Rate Measurement characteristic is a variable-length structure (array) containing a Flags field, a Heart
|
|
||||||
// Rate Measurement Value field and, based on the contents of the Flags field, may contain additional fields
|
|
||||||
// such as Energy Expended or RR-Interval.
|
|
||||||
// More info can be found here: https://www.bluetooth.com/specifications/specs/gatt-specification-supplement-6/
|
|
||||||
// In this example only the heart rate is used, this is the second element in the array of bytes.
|
|
||||||
//
|
//
|
||||||
// To run this on a desktop system:
|
// To run this on a desktop system:
|
||||||
//
|
//
|
||||||
// go run ./examples/heartrate-monitor EE:74:7D:C9:2A:68
|
// go run ./examples/heartrate-monitor EE:74:7D:C9:2A:68
|
||||||
//
|
//
|
||||||
// To run this on a microcontroller, change the constant value in the file
|
// To run this on a microcontroller, change the constant value in the file
|
||||||
// "mcu.go" to set the MAC address of the device you want to discover.
|
// "mcu.go" to set the MAC address of the device you want to discover.
|
||||||
// Then, flash to the microcontroller board like this:
|
// Then, flash to the microcontroller board like this:
|
||||||
//
|
//
|
||||||
// tinygo flash -o circuitplay-bluefruit ./examples/heartrate-monitor
|
// tinygo flash -o circuitplay-bluefruit ./examples/heartrate-monitor
|
||||||
//
|
//
|
||||||
// Once the program is flashed to the board, connect to the USB port
|
// Once the program is flashed to the board, connect to the USB port
|
||||||
// via serial to view the output.
|
// via serial to view the output.
|
||||||
|
//
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -52,7 +48,7 @@ func main() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var device bluetooth.Device
|
var device *bluetooth.Device
|
||||||
select {
|
select {
|
||||||
case result := <-ch:
|
case result := <-ch:
|
||||||
device, err = adapter.Connect(result.Address, bluetooth.ConnectionParams{})
|
device, err = adapter.Connect(result.Address, bluetooth.ConnectionParams{})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build baremetal
|
// +build baremetal
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -6,15 +6,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeviceAddress is the MAC address of the Bluetooth peripheral you want to connect to.
|
// replace this with the MAC address of the Bluetooth peripheral you want to connect to.
|
||||||
// Replace this by using -ldflags="-X main.DeviceAddress=[MAC ADDRESS]"
|
const deviceAddress = "E4:B7:F4:11:8D:33"
|
||||||
// where [MAC ADDRESS] is the actual MAC address of the peripheral.
|
|
||||||
// For example:
|
|
||||||
// tinygo flash -target circuitplay-bluefruit -ldflags="-X main.DeviceAddress=7B:36:98:8C:41:1C" ./examples/heartrate-monitor/
|
|
||||||
var DeviceAddress string
|
|
||||||
|
|
||||||
func connectAddress() string {
|
func connectAddress() string {
|
||||||
return DeviceAddress
|
return deviceAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// done just blocks forever, allows USB CDC reset for flashing new software.
|
// done just blocks forever, allows USB CDC reset for flashing new software.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !baremetal
|
// +build !baremetal
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adapter = bluetooth.DefaultAdapter
|
var adapter = bluetooth.DefaultAdapter
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"machine"
|
"machine"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adapter = bluetooth.DefaultAdapter
|
var adapter = bluetooth.DefaultAdapter
|
||||||
|
@ -36,7 +36,7 @@ func main() {
|
||||||
Handle: &ledColorCharacteristic,
|
Handle: &ledColorCharacteristic,
|
||||||
UUID: bluetooth.NewUUID(charUUID),
|
UUID: bluetooth.NewUUID(charUUID),
|
||||||
Value: ledColor[:],
|
Value: ledColor[:],
|
||||||
Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
|
Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission,
|
||||||
WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
|
WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
|
||||||
if offset != 0 || len(value) != 3 {
|
if offset != 0 || len(value) != 3 {
|
||||||
return
|
return
|
||||||
|
|
|
@ -4,8 +4,8 @@ package main
|
||||||
// details.
|
// details.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
"gitrepo.ru/neonxp/bluetooth/rawterm"
|
"tinygo.org/x/bluetooth/rawterm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -8,8 +8,8 @@ package main
|
||||||
// Code to interact with a raw terminal is in separate files with build tags.
|
// Code to interact with a raw terminal is in separate files with build tags.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
"gitrepo.ru/neonxp/bluetooth/rawterm"
|
"tinygo.org/x/bluetooth/rawterm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adapter = bluetooth.DefaultAdapter
|
var adapter = bluetooth.DefaultAdapter
|
||||||
|
|
|
@ -7,7 +7,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adapter = bluetooth.DefaultAdapter
|
var adapter = bluetooth.DefaultAdapter
|
||||||
|
@ -21,7 +21,7 @@ func main() {
|
||||||
must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
|
must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
|
||||||
LocalName: "Go Bluetooth",
|
LocalName: "Go Bluetooth",
|
||||||
}))
|
}))
|
||||||
adapter.SetConnectHandler(func(device bluetooth.Device, connected bool) {
|
adapter.SetConnectHandler(func(device bluetooth.Address, connected bool) {
|
||||||
if connected {
|
if connected {
|
||||||
println("connected, not advertising...")
|
println("connected, not advertising...")
|
||||||
advState = false
|
advState = false
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
//go:build clue
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/st7789"
|
|
||||||
"tinygo.org/x/tinyfont/proggy"
|
|
||||||
"tinygo.org/x/tinyterm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
font = &proggy.TinySZ8pt7b
|
|
||||||
)
|
|
||||||
|
|
||||||
func initTerminal() {
|
|
||||||
machine.SPI1.Configure(machine.SPIConfig{
|
|
||||||
Frequency: 8000000,
|
|
||||||
SCK: machine.TFT_SCK,
|
|
||||||
SDO: machine.TFT_SDO,
|
|
||||||
SDI: machine.TFT_SDO,
|
|
||||||
Mode: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
display := st7789.New(machine.SPI1,
|
|
||||||
machine.TFT_RESET,
|
|
||||||
machine.TFT_DC,
|
|
||||||
machine.TFT_CS,
|
|
||||||
machine.TFT_LITE)
|
|
||||||
|
|
||||||
display.Configure(st7789.Config{
|
|
||||||
Rotation: st7789.ROTATION_90,
|
|
||||||
//Height: 320,
|
|
||||||
FrameRate: st7789.FRAMERATE_111,
|
|
||||||
VSyncLines: st7789.MAX_VSYNC_SCANLINES,
|
|
||||||
})
|
|
||||||
|
|
||||||
display.FillScreen(black)
|
|
||||||
|
|
||||||
terminal = tinyterm.NewTerminal(&display)
|
|
||||||
terminal.Configure(&tinyterm.Config{
|
|
||||||
Font: font,
|
|
||||||
FontHeight: 10,
|
|
||||||
FontOffset: 6,
|
|
||||||
UseSoftwareScroll: true,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image/color"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
|
||||||
"tinygo.org/x/tinyterm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
terminal *tinyterm.Terminal
|
|
||||||
|
|
||||||
black = color.RGBA{0, 0, 0, 255}
|
|
||||||
adapter = bluetooth.DefaultAdapter
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
initTerminal()
|
|
||||||
|
|
||||||
terminalOutput("enable interface...")
|
|
||||||
|
|
||||||
must("enable BLE interface", adapter.Enable())
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
|
|
||||||
terminalOutput("start scan...")
|
|
||||||
|
|
||||||
must("start scan", adapter.Scan(scanHandler))
|
|
||||||
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Minute)
|
|
||||||
terminalOutput("scanning...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func scanHandler(adapter *bluetooth.Adapter, device bluetooth.ScanResult) {
|
|
||||||
msg := fmt.Sprintf("%s %d %s", device.Address.String(), device.RSSI, device.LocalName())
|
|
||||||
terminalOutput(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func must(action string, err error) {
|
|
||||||
if err != nil {
|
|
||||||
for {
|
|
||||||
terminalOutput("failed to " + action + ": " + err.Error())
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func terminalOutput(s string) {
|
|
||||||
println(s)
|
|
||||||
fmt.Fprintf(terminal, "\n%s", s)
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
//go:build pybadge
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/st7735"
|
|
||||||
"tinygo.org/x/tinyfont"
|
|
||||||
"tinygo.org/x/tinyterm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
font = &tinyfont.Picopixel
|
|
||||||
)
|
|
||||||
|
|
||||||
func initTerminal() {
|
|
||||||
machine.SPI1.Configure(machine.SPIConfig{
|
|
||||||
SCK: machine.SPI1_SCK_PIN,
|
|
||||||
SDO: machine.SPI1_SDO_PIN,
|
|
||||||
SDI: machine.SPI1_SDI_PIN,
|
|
||||||
Frequency: 8000000,
|
|
||||||
})
|
|
||||||
|
|
||||||
display := st7735.New(machine.SPI1, machine.TFT_RST, machine.TFT_DC, machine.TFT_CS, machine.TFT_LITE)
|
|
||||||
display.Configure(st7735.Config{
|
|
||||||
Rotation: st7735.ROTATION_90,
|
|
||||||
})
|
|
||||||
|
|
||||||
display.FillScreen(black)
|
|
||||||
|
|
||||||
terminal = tinyterm.NewTerminal(&display)
|
|
||||||
terminal.Configure(&tinyterm.Config{
|
|
||||||
Font: font,
|
|
||||||
FontHeight: 8,
|
|
||||||
FontOffset: 4,
|
|
||||||
UseSoftwareScroll: true,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
//go:build pyportal
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/ili9341"
|
|
||||||
"tinygo.org/x/tinyfont/proggy"
|
|
||||||
"tinygo.org/x/tinyterm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
font = &proggy.TinySZ8pt7b
|
|
||||||
)
|
|
||||||
|
|
||||||
func initTerminal() {
|
|
||||||
display := ili9341.NewParallel(
|
|
||||||
machine.LCD_DATA0,
|
|
||||||
machine.TFT_WR,
|
|
||||||
machine.TFT_DC,
|
|
||||||
machine.TFT_CS,
|
|
||||||
machine.TFT_RESET,
|
|
||||||
machine.TFT_RD,
|
|
||||||
)
|
|
||||||
|
|
||||||
// configure backlight
|
|
||||||
backlight := machine.TFT_BACKLIGHT
|
|
||||||
backlight.Configure(machine.PinConfig{machine.PinOutput})
|
|
||||||
|
|
||||||
// configure display
|
|
||||||
display.Configure(ili9341.Config{})
|
|
||||||
display.SetRotation(ili9341.Rotation270)
|
|
||||||
display.FillScreen(black)
|
|
||||||
|
|
||||||
backlight.High()
|
|
||||||
|
|
||||||
terminal = tinyterm.NewTerminal(display)
|
|
||||||
terminal.Configure(&tinyterm.Config{
|
|
||||||
Font: font,
|
|
||||||
FontHeight: 10,
|
|
||||||
FontOffset: 6,
|
|
||||||
UseSoftwareScroll: true,
|
|
||||||
})
|
|
||||||
}
|
|
207
gap.go
207
gap.go
|
@ -53,36 +53,6 @@ type AdvertisementOptions struct {
|
||||||
|
|
||||||
// Interval in BLE-specific units. Create an interval by using NewDuration.
|
// Interval in BLE-specific units. Create an interval by using NewDuration.
|
||||||
Interval Duration
|
Interval Duration
|
||||||
|
|
||||||
// ManufacturerData stores Advertising Data.
|
|
||||||
ManufacturerData []ManufacturerDataElement
|
|
||||||
|
|
||||||
// ServiceData stores Advertising Data.
|
|
||||||
ServiceData []ServiceDataElement
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manufacturer data that's part of an advertisement packet.
|
|
||||||
type ManufacturerDataElement struct {
|
|
||||||
// The company ID, which must be one of the assigned company IDs.
|
|
||||||
// The full list is in here:
|
|
||||||
// https://www.bluetooth.com/specifications/assigned-numbers/
|
|
||||||
// The list can also be viewed here:
|
|
||||||
// https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/company_identifiers/company_identifiers.yaml
|
|
||||||
// The value 0xffff can also be used for testing.
|
|
||||||
CompanyID uint16
|
|
||||||
|
|
||||||
// The value, which can be any value but can't be very large.
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceDataElement strores a uuid/byte-array pair used as ServiceData advertisment elements
|
|
||||||
type ServiceDataElement struct {
|
|
||||||
// Service UUID.
|
|
||||||
// The list can also be viewed here:
|
|
||||||
// https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/uuids/service_uuids.yaml
|
|
||||||
UUID UUID
|
|
||||||
// the data byte array
|
|
||||||
Data []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duration is the unit of time used in BLE, in 0.625µs units. This unit of time
|
// Duration is the unit of time used in BLE, in 0.625µs units. This unit of time
|
||||||
|
@ -105,7 +75,7 @@ type ScanResult struct {
|
||||||
// Bluetooth address of the scanned device.
|
// Bluetooth address of the scanned device.
|
||||||
Address Address
|
Address Address
|
||||||
|
|
||||||
// Signal strength of the advertisement packet.
|
// RSSI the last time a packet from this device has been received.
|
||||||
RSSI int16
|
RSSI int16
|
||||||
|
|
||||||
// The data obtained from the advertisement data, which may contain many
|
// The data obtained from the advertisement data, which may contain many
|
||||||
|
@ -136,13 +106,9 @@ type AdvertisementPayload interface {
|
||||||
// if this data is not available.
|
// if this data is not available.
|
||||||
Bytes() []byte
|
Bytes() []byte
|
||||||
|
|
||||||
// ManufacturerData returns a slice with all the manufacturer data present in the
|
// ManufacturerData returns a map with all the manufacturer data present in the
|
||||||
// advertising. It may be empty.
|
//advertising. IT may be empty.
|
||||||
ManufacturerData() []ManufacturerDataElement
|
ManufacturerData() map[uint16][]byte
|
||||||
|
|
||||||
// ServiceData returns a slice with all the service data present in the
|
|
||||||
// advertising. It may be empty.
|
|
||||||
ServiceData() []ServiceDataElement
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdvertisementFields contains advertisement fields in structured form.
|
// AdvertisementFields contains advertisement fields in structured form.
|
||||||
|
@ -157,10 +123,7 @@ type AdvertisementFields struct {
|
||||||
ServiceUUIDs []UUID
|
ServiceUUIDs []UUID
|
||||||
|
|
||||||
// ManufacturerData is the manufacturer data of the advertisement.
|
// ManufacturerData is the manufacturer data of the advertisement.
|
||||||
ManufacturerData []ManufacturerDataElement
|
ManufacturerData map[uint16][]byte
|
||||||
|
|
||||||
// ServiceData is the service data of the advertisement.
|
|
||||||
ServiceData []ServiceDataElement
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// advertisementFields wraps AdvertisementFields to implement the
|
// advertisementFields wraps AdvertisementFields to implement the
|
||||||
|
@ -194,15 +157,10 @@ func (p *advertisementFields) Bytes() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ManufacturerData returns the underlying ManufacturerData field.
|
// ManufacturerData returns the underlying ManufacturerData field.
|
||||||
func (p *advertisementFields) ManufacturerData() []ManufacturerDataElement {
|
func (p *advertisementFields) ManufacturerData() map[uint16][]byte {
|
||||||
return p.AdvertisementFields.ManufacturerData
|
return p.AdvertisementFields.ManufacturerData
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceData returns the underlying ServiceData field.
|
|
||||||
func (p *advertisementFields) ServiceData() []ServiceDataElement {
|
|
||||||
return p.AdvertisementFields.ServiceData
|
|
||||||
}
|
|
||||||
|
|
||||||
// rawAdvertisementPayload encapsulates a raw advertisement packet. Methods to
|
// rawAdvertisementPayload encapsulates a raw advertisement packet. Methods to
|
||||||
// get the data (such as LocalName()) will parse just the needed field. Scanning
|
// get the data (such as LocalName()) will parse just the needed field. Scanning
|
||||||
// the data should be fast as most advertisement packets only have a very small
|
// the data should be fast as most advertisement packets only have a very small
|
||||||
|
@ -292,58 +250,22 @@ func (buf *rawAdvertisementPayload) HasServiceUUID(uuid UUID) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ManufacturerData returns the manufacturer data in the advertisement payload.
|
// ManufacturerData returns the manufacturer data in the advertisement payload.
|
||||||
func (buf *rawAdvertisementPayload) ManufacturerData() []ManufacturerDataElement {
|
func (buf *rawAdvertisementPayload) ManufacturerData() map[uint16][]byte {
|
||||||
var manufacturerData []ManufacturerDataElement
|
mData := make(map[uint16][]byte)
|
||||||
for index := 0; index < int(buf.len)+4; index += int(buf.data[index]) + 1 {
|
data := buf.Bytes()
|
||||||
fieldLength := int(buf.data[index+0])
|
for len(data) >= 2 {
|
||||||
if fieldLength < 3 {
|
fieldLength := data[0]
|
||||||
continue
|
if int(fieldLength)+1 > len(data) {
|
||||||
|
// Invalid field length.
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
fieldType := buf.data[index+1]
|
// If this is the manufacturer data
|
||||||
if fieldType != 0xff {
|
if byte(0xFF) == data[1] {
|
||||||
continue
|
mData[uint16(data[2])+(uint16(data[3])<<8)] = data[4 : fieldLength+1]
|
||||||
}
|
}
|
||||||
key := uint16(buf.data[index+2]) | uint16(buf.data[index+3])<<8
|
data = data[fieldLength+1:]
|
||||||
manufacturerData = append(manufacturerData, ManufacturerDataElement{
|
|
||||||
CompanyID: key,
|
|
||||||
Data: buf.data[index+4 : index+fieldLength+1],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return manufacturerData
|
return mData
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceData returns the service data in the advertisment payload
|
|
||||||
func (buf *rawAdvertisementPayload) ServiceData() []ServiceDataElement {
|
|
||||||
var serviceData []ServiceDataElement
|
|
||||||
for index := 0; index < int(buf.len)+4; index += int(buf.data[index]) + 1 {
|
|
||||||
fieldLength := int(buf.data[index+0])
|
|
||||||
if fieldLength < 3 { // field has only length and type and no data
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fieldType := buf.data[index+1]
|
|
||||||
switch fieldType {
|
|
||||||
case 0x16: // 16-bit uuid
|
|
||||||
serviceData = append(serviceData, ServiceDataElement{
|
|
||||||
UUID: New16BitUUID(uint16(buf.data[index+2]) + (uint16(buf.data[index+3]) << 8)),
|
|
||||||
Data: buf.data[index+4 : index+fieldLength+1],
|
|
||||||
})
|
|
||||||
case 0x20: // 32-bit uuid
|
|
||||||
serviceData = append(serviceData, ServiceDataElement{
|
|
||||||
UUID: New32BitUUID(uint32(buf.data[index+2]) + (uint32(buf.data[index+3]) << 8) + (uint32(buf.data[index+4]) << 16) + (uint32(buf.data[index+5]) << 24)),
|
|
||||||
Data: buf.data[index+6 : index+fieldLength+1],
|
|
||||||
})
|
|
||||||
case 0x21: // 128-bit uuid
|
|
||||||
var uuidArray [16]byte
|
|
||||||
copy(uuidArray[:], buf.data[index+2:index+18])
|
|
||||||
serviceData = append(serviceData, ServiceDataElement{
|
|
||||||
UUID: NewUUID(uuidArray),
|
|
||||||
Data: buf.data[index+18 : index+fieldLength+1],
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return serviceData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset restores this buffer to the original state.
|
// reset restores this buffer to the original state.
|
||||||
|
@ -373,89 +295,6 @@ func (buf *rawAdvertisementPayload) addFromOptions(options AdvertisementOptions)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, element := range options.ManufacturerData {
|
|
||||||
if !buf.addManufacturerData(element.CompanyID, element.Data) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, element := range options.ServiceData {
|
|
||||||
if !buf.addServiceData(element.UUID, element.Data) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// addManufacturerData adds manufacturer data ([]byte) entries to the advertisement payload.
|
|
||||||
func (buf *rawAdvertisementPayload) addManufacturerData(key uint16, value []byte) (ok bool) {
|
|
||||||
// Check whether the field can fit this manufacturer data.
|
|
||||||
fieldLength := len(value) + 4
|
|
||||||
if int(buf.len)+fieldLength > len(buf.data) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the data.
|
|
||||||
buf.data[buf.len+0] = uint8(fieldLength - 1)
|
|
||||||
buf.data[buf.len+1] = 0xff
|
|
||||||
buf.data[buf.len+2] = uint8(key)
|
|
||||||
buf.data[buf.len+3] = uint8(key >> 8)
|
|
||||||
copy(buf.data[buf.len+4:], value)
|
|
||||||
buf.len += uint8(fieldLength)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// addServiceData adds service data ([]byte) entries to the advertisement payload.
|
|
||||||
func (buf *rawAdvertisementPayload) addServiceData(uuid UUID, data []byte) (ok bool) {
|
|
||||||
switch {
|
|
||||||
case uuid.Is16Bit():
|
|
||||||
// check if it fits
|
|
||||||
fieldLength := 1 + 1 + 2 + len(data) // 1 byte length, 1 byte ad type, 2 bytes uuid, actual service data
|
|
||||||
if int(buf.len)+fieldLength > len(buf.data) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Add the data.
|
|
||||||
buf.data[buf.len+0] = byte(fieldLength - 1)
|
|
||||||
buf.data[buf.len+1] = 0x16
|
|
||||||
buf.data[buf.len+2] = byte(uuid.Get16Bit())
|
|
||||||
buf.data[buf.len+3] = byte(uuid.Get16Bit() >> 8)
|
|
||||||
copy(buf.data[buf.len+4:], data)
|
|
||||||
buf.len += uint8(fieldLength)
|
|
||||||
|
|
||||||
case uuid.Is32Bit():
|
|
||||||
// check if it fits
|
|
||||||
fieldLength := 1 + 1 + 4 + len(data) // 1 byte length, 1 byte ad type, 4 bytes uuid, actual service data
|
|
||||||
if int(buf.len)+fieldLength > len(buf.data) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Add the data.
|
|
||||||
buf.data[buf.len+0] = byte(fieldLength - 1)
|
|
||||||
buf.data[buf.len+1] = 0x20
|
|
||||||
buf.data[buf.len+2] = byte(uuid.Get32Bit())
|
|
||||||
buf.data[buf.len+3] = byte(uuid.Get32Bit() >> 8)
|
|
||||||
buf.data[buf.len+4] = byte(uuid.Get32Bit() >> 16)
|
|
||||||
buf.data[buf.len+5] = byte(uuid.Get32Bit() >> 24)
|
|
||||||
copy(buf.data[buf.len+6:], data)
|
|
||||||
buf.len += uint8(fieldLength)
|
|
||||||
|
|
||||||
default: // must be 128-bit uuid
|
|
||||||
// check if it fits
|
|
||||||
fieldLength := 1 + 1 + 16 + len(data) // 1 byte length, 1 byte ad type, 16 bytes uuid, actual service data
|
|
||||||
if int(buf.len)+fieldLength > len(buf.data) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Add the data.
|
|
||||||
buf.data[buf.len+0] = byte(fieldLength - 1)
|
|
||||||
buf.data[buf.len+1] = 0x21
|
|
||||||
uuid_bytes := uuid.Bytes()
|
|
||||||
copy(buf.data[buf.len+2:], uuid_bytes[:])
|
|
||||||
copy(buf.data[buf.len+2+16:], data)
|
|
||||||
buf.len += uint8(fieldLength)
|
|
||||||
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,8 +356,7 @@ func (buf *rawAdvertisementPayload) addServiceUUID(uuid UUID) (ok bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectionParams are used when connecting to a peripherals or when changing
|
// ConnectionParams are used when connecting to a peripherals.
|
||||||
// the parameters of an active connection.
|
|
||||||
type ConnectionParams struct {
|
type ConnectionParams struct {
|
||||||
// The timeout for the connection attempt. Not used during the rest of the
|
// The timeout for the connection attempt. Not used during the rest of the
|
||||||
// connection. If no duration is specified, a default timeout will be used.
|
// connection. If no duration is specified, a default timeout will be used.
|
||||||
|
@ -530,9 +368,4 @@ type ConnectionParams struct {
|
||||||
// will be used.
|
// will be used.
|
||||||
MinInterval Duration
|
MinInterval Duration
|
||||||
MaxInterval Duration
|
MaxInterval Duration
|
||||||
|
|
||||||
// Connection Supervision Timeout. After this time has passed with no
|
|
||||||
// communication, the connection is considered lost. If no timeout is
|
|
||||||
// specified, the timeout will be unchanged.
|
|
||||||
Timeout Duration
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,12 +85,6 @@ func (a *Adapter) StopScan() error {
|
||||||
|
|
||||||
// Device is a connection to a remote peripheral.
|
// Device is a connection to a remote peripheral.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
Address Address
|
|
||||||
|
|
||||||
*deviceInternal
|
|
||||||
}
|
|
||||||
|
|
||||||
type deviceInternal struct {
|
|
||||||
delegate *peripheralDelegate
|
delegate *peripheralDelegate
|
||||||
|
|
||||||
cm cbgo.CentralManager
|
cm cbgo.CentralManager
|
||||||
|
@ -103,14 +97,14 @@ type deviceInternal struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect starts a connection attempt to the given peripheral device address.
|
// Connect starts a connection attempt to the given peripheral device address.
|
||||||
func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
|
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) {
|
||||||
uuid, err := cbgo.ParseUUID(address.UUID.String())
|
uuid, err := cbgo.ParseUUID(address.UUID.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Device{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
prphs := a.cm.RetrievePeripheralsWithIdentifiers([]cbgo.UUID{uuid})
|
prphs := a.cm.RetrievePeripheralsWithIdentifiers([]cbgo.UUID{uuid})
|
||||||
if len(prphs) == 0 {
|
if len(prphs) == 0 {
|
||||||
return Device{}, fmt.Errorf("Connect failed: no peer with address: %s", address.UUID.String())
|
return nil, fmt.Errorf("Connect failed: no peer with address: %s", address.UUID.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout := defaultConnectionTimeout
|
timeout := defaultConnectionTimeout
|
||||||
|
@ -135,23 +129,20 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, err
|
||||||
|
|
||||||
// check if we have received a disconnected peripheral
|
// check if we have received a disconnected peripheral
|
||||||
if p.State() == cbgo.PeripheralStateDisconnected {
|
if p.State() == cbgo.PeripheralStateDisconnected {
|
||||||
return Device{}, connectionError
|
return nil, connectionError
|
||||||
}
|
}
|
||||||
|
|
||||||
d := Device{
|
d := &Device{
|
||||||
Address: address,
|
cm: a.cm,
|
||||||
deviceInternal: &deviceInternal{
|
prph: p,
|
||||||
cm: a.cm,
|
servicesChan: make(chan error),
|
||||||
prph: p,
|
charsChan: make(chan error),
|
||||||
servicesChan: make(chan error),
|
|
||||||
charsChan: make(chan error),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d.delegate = &peripheralDelegate{d: d}
|
d.delegate = &peripheralDelegate{d: d}
|
||||||
p.SetDelegate(d.delegate)
|
p.SetDelegate(d.delegate)
|
||||||
|
|
||||||
a.connectHandler(d, true)
|
a.connectHandler(address, true)
|
||||||
|
|
||||||
return d, nil
|
return d, nil
|
||||||
|
|
||||||
|
@ -171,29 +162,17 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, err
|
||||||
|
|
||||||
// Disconnect from the BLE device. This method is non-blocking and does not
|
// Disconnect from the BLE device. This method is non-blocking and does not
|
||||||
// wait until the connection is fully gone.
|
// wait until the connection is fully gone.
|
||||||
func (d Device) Disconnect() error {
|
func (d *Device) Disconnect() error {
|
||||||
d.cm.CancelConnect(d.prph)
|
d.cm.CancelConnect(d.prph)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestConnectionParams requests a different connection latency and timeout
|
|
||||||
// of the given device connection. Fields that are unset will be left alone.
|
|
||||||
// Whether or not the device will actually honor this, depends on the device and
|
|
||||||
// on the specific parameters.
|
|
||||||
//
|
|
||||||
// This call has not yet been implemented on macOS.
|
|
||||||
func (d Device) RequestConnectionParams(params ConnectionParams) error {
|
|
||||||
// TODO: implement this using setDesiredConnectionLatency, see:
|
|
||||||
// https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/1393277-setdesiredconnectionlatency
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peripheral delegate functions
|
// Peripheral delegate functions
|
||||||
|
|
||||||
type peripheralDelegate struct {
|
type peripheralDelegate struct {
|
||||||
cbgo.PeripheralDelegateBase
|
cbgo.PeripheralDelegateBase
|
||||||
|
|
||||||
d Device
|
d *Device
|
||||||
}
|
}
|
||||||
|
|
||||||
// DidDiscoverServices is called when the services for a Peripheral
|
// DidDiscoverServices is called when the services for a Peripheral
|
||||||
|
@ -216,35 +195,13 @@ func (pd *peripheralDelegate) DidUpdateValueForCharacteristic(prph cbgo.Peripher
|
||||||
svcuuid, _ := ParseUUID(chr.Service().UUID().String())
|
svcuuid, _ := ParseUUID(chr.Service().UUID().String())
|
||||||
|
|
||||||
if svc, ok := pd.d.services[svcuuid]; ok {
|
if svc, ok := pd.d.services[svcuuid]; ok {
|
||||||
for _, char := range svc.characteristics {
|
if char, ok := svc.characteristics[uuid]; ok {
|
||||||
|
if err == nil && char.callback != nil {
|
||||||
if char.characteristic == chr && uuid == char.UUID() { // compare pointers
|
go char.callback(chr.Value())
|
||||||
if err == nil && char.callback != nil {
|
|
||||||
go char.callback(chr.Value())
|
|
||||||
}
|
|
||||||
|
|
||||||
if char.readChan != nil {
|
|
||||||
char.readChan <- err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
if char.readChan != nil {
|
||||||
|
char.readChan <- err
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DidWriteValueForCharacteristic is called after the characteristic for a Service
|
|
||||||
// for a Peripheral trigger a write with response. It contains the returned error or nil.
|
|
||||||
func (pd *peripheralDelegate) DidWriteValueForCharacteristic(_ cbgo.Peripheral, chr cbgo.Characteristic, err error) {
|
|
||||||
uuid, _ := ParseUUID(chr.UUID().String())
|
|
||||||
svcuuid, _ := ParseUUID(chr.Service().UUID().String())
|
|
||||||
|
|
||||||
if svc, ok := pd.d.services[svcuuid]; ok {
|
|
||||||
for _, char := range svc.characteristics {
|
|
||||||
if char.characteristic == chr && uuid == char.UUID() { // compare pointers
|
|
||||||
if char.writeChan != nil {
|
|
||||||
char.writeChan <- err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
422
gap_hci.go
422
gap_hci.go
|
@ -1,422 +0,0 @@
|
||||||
//go:build hci || ninafw || cyw43439
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"slices"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultMTU = 23
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrConnect = errors.New("bluetooth: could not connect")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Scan starts a BLE scan.
|
|
||||||
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
|
||||||
if a.scanning {
|
|
||||||
return errScanning
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.hci.leSetScanEnable(false, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// passive scanning, every 40ms, for 30ms
|
|
||||||
if err := a.hci.leSetScanParameters(0x00, 0x0080, 0x0030, 0x00, 0x00); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.scanning = true
|
|
||||||
|
|
||||||
// scan with duplicates
|
|
||||||
if err := a.hci.leSetScanEnable(true, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
lastUpdate := time.Now().UnixNano()
|
|
||||||
|
|
||||||
for {
|
|
||||||
if err := a.hci.poll(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case a.hci.advData.reported:
|
|
||||||
adf := AdvertisementFields{}
|
|
||||||
if a.hci.advData.eirLength > 31 {
|
|
||||||
if debug {
|
|
||||||
println("eirLength too long")
|
|
||||||
}
|
|
||||||
|
|
||||||
a.hci.clearAdvData()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < int(a.hci.advData.eirLength); {
|
|
||||||
l, t := int(a.hci.advData.eirData[i]), a.hci.advData.eirData[i+1]
|
|
||||||
if l < 1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t {
|
|
||||||
case 0x02, 0x03:
|
|
||||||
// 16-bit Service Class UUID
|
|
||||||
adf.ServiceUUIDs = append(adf.ServiceUUIDs, New16BitUUID(binary.LittleEndian.Uint16(a.hci.advData.eirData[i+2:i+4])))
|
|
||||||
case 0x06, 0x07:
|
|
||||||
// 128-bit Service Class UUID
|
|
||||||
var uuid [16]byte
|
|
||||||
copy(uuid[:], a.hci.advData.eirData[i+2:i+18])
|
|
||||||
adf.ServiceUUIDs = append(adf.ServiceUUIDs, NewUUID(uuid))
|
|
||||||
case 0x08, 0x09:
|
|
||||||
if debug {
|
|
||||||
println("local name", string(a.hci.advData.eirData[i+2:i+1+l]))
|
|
||||||
}
|
|
||||||
|
|
||||||
adf.LocalName = string(a.hci.advData.eirData[i+2 : i+1+l])
|
|
||||||
case 0xFF:
|
|
||||||
// Manufacturer Specific Data
|
|
||||||
}
|
|
||||||
|
|
||||||
i += l + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
random := a.hci.advData.peerBdaddrType == 0x01
|
|
||||||
|
|
||||||
callback(a, ScanResult{
|
|
||||||
Address: Address{
|
|
||||||
MACAddress{
|
|
||||||
MAC: makeAddress(a.hci.advData.peerBdaddr),
|
|
||||||
isRandom: random,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RSSI: int16(a.hci.advData.rssi),
|
|
||||||
AdvertisementPayload: &advertisementFields{
|
|
||||||
AdvertisementFields: adf,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
a.hci.clearAdvData()
|
|
||||||
time.Sleep(5 * time.Millisecond)
|
|
||||||
|
|
||||||
default:
|
|
||||||
if !a.scanning {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug && (time.Now().UnixNano()-lastUpdate)/int64(time.Second) > 1 {
|
|
||||||
println("still scanning...")
|
|
||||||
lastUpdate = time.Now().UnixNano()
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) StopScan() error {
|
|
||||||
if !a.scanning {
|
|
||||||
return errNotScanning
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.hci.leSetScanEnable(false, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.scanning = false
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Address contains a Bluetooth MAC address.
|
|
||||||
type Address struct {
|
|
||||||
MACAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect starts a connection attempt to the given peripheral device address.
|
|
||||||
func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
|
|
||||||
if debug {
|
|
||||||
println("Connect")
|
|
||||||
}
|
|
||||||
|
|
||||||
random := uint8(0)
|
|
||||||
if address.isRandom {
|
|
||||||
random = 1
|
|
||||||
}
|
|
||||||
if err := a.hci.leCreateConn(0x0060, 0x0030, 0x00,
|
|
||||||
random, makeNINAAddress(address.MAC),
|
|
||||||
0x00, 0x0006, 0x000c, 0x0000, 0x00c8, 0x0004, 0x0006); err != nil {
|
|
||||||
return Device{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// are we connected?
|
|
||||||
start := time.Now().UnixNano()
|
|
||||||
for {
|
|
||||||
if err := a.hci.poll(); err != nil {
|
|
||||||
return Device{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.hci.connectData.connected {
|
|
||||||
defer a.hci.clearConnectData()
|
|
||||||
|
|
||||||
random := false
|
|
||||||
if address.isRandom {
|
|
||||||
random = true
|
|
||||||
}
|
|
||||||
|
|
||||||
d := Device{
|
|
||||||
Address: Address{
|
|
||||||
MACAddress{
|
|
||||||
MAC: makeAddress(a.hci.connectData.peerBdaddr),
|
|
||||||
isRandom: random},
|
|
||||||
},
|
|
||||||
deviceInternal: &deviceInternal{
|
|
||||||
adapter: a,
|
|
||||||
handle: a.hci.connectData.handle,
|
|
||||||
mtu: defaultMTU,
|
|
||||||
notificationRegistrations: make([]notificationRegistration, 0),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
a.addConnection(d)
|
|
||||||
|
|
||||||
return d, nil
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// check for timeout
|
|
||||||
if (time.Now().UnixNano()-start)/int64(time.Second) > 5 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancel connection attempt that failed
|
|
||||||
if err := a.hci.leCancelConn(); err != nil {
|
|
||||||
return Device{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return Device{}, ErrConnect
|
|
||||||
}
|
|
||||||
|
|
||||||
type notificationRegistration struct {
|
|
||||||
handle uint16
|
|
||||||
callback func([]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Device is a connection to a remote peripheral.
|
|
||||||
type Device struct {
|
|
||||||
Address Address
|
|
||||||
*deviceInternal
|
|
||||||
}
|
|
||||||
|
|
||||||
type deviceInternal struct {
|
|
||||||
adapter *Adapter
|
|
||||||
handle uint16
|
|
||||||
mtu uint16
|
|
||||||
|
|
||||||
notificationRegistrations []notificationRegistration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disconnect from the BLE device.
|
|
||||||
func (d Device) Disconnect() error {
|
|
||||||
if debug {
|
|
||||||
println("Disconnect")
|
|
||||||
}
|
|
||||||
if err := d.adapter.hci.disconnect(d.handle); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.adapter.removeConnection(d)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestConnectionParams requests a different connection latency and timeout
|
|
||||||
// of the given device connection. Fields that are unset will be left alone.
|
|
||||||
// Whether or not the device will actually honor this, depends on the device and
|
|
||||||
// on the specific parameters.
|
|
||||||
//
|
|
||||||
// On NINA, this call hasn't been implemented yet.
|
|
||||||
func (d Device) RequestConnectionParams(params ConnectionParams) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Device) findNotificationRegistration(handle uint16) *notificationRegistration {
|
|
||||||
for _, n := range d.notificationRegistrations {
|
|
||||||
if n.handle == handle {
|
|
||||||
return &n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Device) addNotificationRegistration(handle uint16, callback func([]byte)) {
|
|
||||||
d.notificationRegistrations = append(d.notificationRegistrations,
|
|
||||||
notificationRegistration{
|
|
||||||
handle: handle,
|
|
||||||
callback: callback,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Device) startNotifications() {
|
|
||||||
d.adapter.startNotifications()
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultAdvertisement Advertisement
|
|
||||||
|
|
||||||
// Advertisement encapsulates a single advertisement instance.
|
|
||||||
type Advertisement struct {
|
|
||||||
adapter *Adapter
|
|
||||||
|
|
||||||
localName []byte
|
|
||||||
serviceUUIDs []UUID
|
|
||||||
interval uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultAdvertisement returns the default advertisement instance but does not
|
|
||||||
// configure it.
|
|
||||||
func (a *Adapter) DefaultAdvertisement() *Advertisement {
|
|
||||||
if defaultAdvertisement.adapter == nil {
|
|
||||||
defaultAdvertisement.adapter = a
|
|
||||||
}
|
|
||||||
|
|
||||||
return &defaultAdvertisement
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure this advertisement.
|
|
||||||
func (a *Advertisement) Configure(options AdvertisementOptions) error {
|
|
||||||
switch {
|
|
||||||
case options.LocalName != "":
|
|
||||||
a.localName = []byte(options.LocalName)
|
|
||||||
default:
|
|
||||||
a.localName = []byte("TinyGo")
|
|
||||||
}
|
|
||||||
|
|
||||||
a.serviceUUIDs = append([]UUID{}, options.ServiceUUIDs...)
|
|
||||||
a.interval = uint16(options.Interval)
|
|
||||||
|
|
||||||
a.adapter.AddService(
|
|
||||||
&Service{
|
|
||||||
UUID: ServiceUUIDGenericAccess,
|
|
||||||
Characteristics: []CharacteristicConfig{
|
|
||||||
{
|
|
||||||
UUID: CharacteristicUUIDDeviceName,
|
|
||||||
Flags: CharacteristicReadPermission,
|
|
||||||
Value: a.localName,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
UUID: CharacteristicUUIDAppearance,
|
|
||||||
Flags: CharacteristicReadPermission,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
a.adapter.AddService(
|
|
||||||
&Service{
|
|
||||||
UUID: ServiceUUIDGenericAttribute,
|
|
||||||
Characteristics: []CharacteristicConfig{
|
|
||||||
{
|
|
||||||
UUID: CharacteristicUUIDServiceChanged,
|
|
||||||
Flags: CharacteristicIndicatePermission,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start advertisement. May only be called after it has been configured.
|
|
||||||
func (a *Advertisement) Start() error {
|
|
||||||
// uint8_t type = (_connectable) ? 0x00 : (_localName ? 0x02 : 0x03);
|
|
||||||
typ := uint8(0x00)
|
|
||||||
|
|
||||||
if err := a.adapter.hci.leSetAdvertisingParameters(a.interval, a.interval,
|
|
||||||
typ, 0x00, 0x00, [6]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 0x07, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var advertisingData [31]byte
|
|
||||||
advertisingDataLen := uint8(0)
|
|
||||||
|
|
||||||
advertisingData[0] = 0x02
|
|
||||||
advertisingData[1] = 0x01
|
|
||||||
advertisingData[2] = 0x06
|
|
||||||
advertisingDataLen += 3
|
|
||||||
|
|
||||||
// TODO: handle multiple service UUIDs
|
|
||||||
if len(a.serviceUUIDs) > 0 {
|
|
||||||
uuid := a.serviceUUIDs[0]
|
|
||||||
var sz uint8
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case uuid.Is16Bit():
|
|
||||||
sz = 2
|
|
||||||
binary.LittleEndian.PutUint16(advertisingData[5:], uuid.Get16Bit())
|
|
||||||
case uuid.Is32Bit():
|
|
||||||
sz = 6
|
|
||||||
data := uuid.Bytes()
|
|
||||||
slices.Reverse(data[:])
|
|
||||||
copy(advertisingData[5:], data[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
advertisingData[3] = sz + 1
|
|
||||||
advertisingData[4] = sz
|
|
||||||
advertisingDataLen += sz + 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: handle manufacturer data
|
|
||||||
|
|
||||||
if err := a.adapter.hci.leSetAdvertisingData(advertisingData[:advertisingDataLen]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var scanResponseData [31]byte
|
|
||||||
scanResponseDataLen := uint8(0)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(a.localName) > 29:
|
|
||||||
scanResponseData[1] = 0x08
|
|
||||||
scanResponseData[0] = 1 + 29
|
|
||||||
copy(scanResponseData[2:], a.localName[:29])
|
|
||||||
scanResponseDataLen = 31
|
|
||||||
case len(a.localName) > 0:
|
|
||||||
scanResponseData[1] = 0x09
|
|
||||||
scanResponseData[0] = uint8(1 + len(a.localName))
|
|
||||||
copy(scanResponseData[2:], a.localName)
|
|
||||||
scanResponseDataLen = uint8(2 + len(a.localName))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.adapter.hci.leSetScanResponseData(scanResponseData[:scanResponseDataLen]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.adapter.hci.leSetAdvertiseEnable(true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// go routine to poll for HCI events while advertising
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
if err := a.adapter.att.poll(); err != nil {
|
|
||||||
// TODO: handle error
|
|
||||||
if debug {
|
|
||||||
println("error polling while advertising:", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop advertisement. May only be called after it has been started.
|
|
||||||
func (a *Advertisement) Stop() error {
|
|
||||||
return a.adapter.hci.leSetAdvertiseEnable(false)
|
|
||||||
}
|
|
359
gap_linux.go
359
gap_linux.go
|
@ -1,22 +1,21 @@
|
||||||
//go:build !baremetal
|
//go:build !baremetal
|
||||||
|
// +build !baremetal
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
"github.com/godbus/dbus/v5/prop"
|
"github.com/muka/go-bluetooth/api"
|
||||||
|
"github.com/muka/go-bluetooth/bluez"
|
||||||
|
"github.com/muka/go-bluetooth/bluez/profile/advertising"
|
||||||
|
"github.com/muka/go-bluetooth/bluez/profile/device"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errAdvertisementNotStarted = errors.New("bluetooth: stop advertisement that was not started")
|
var errAdvertisementNotStarted = errors.New("bluetooth: stop advertisement that was not started")
|
||||||
var errAdvertisementAlreadyStarted = errors.New("bluetooth: start advertisement that was already started")
|
|
||||||
|
|
||||||
// Unique ID per advertisement (to generate a unique object path).
|
|
||||||
var advertisementID uint64
|
|
||||||
|
|
||||||
// Address contains a Bluetooth MAC address.
|
// Address contains a Bluetooth MAC address.
|
||||||
type Address struct {
|
type Address struct {
|
||||||
|
@ -25,9 +24,10 @@ type Address struct {
|
||||||
|
|
||||||
// Advertisement encapsulates a single advertisement instance.
|
// Advertisement encapsulates a single advertisement instance.
|
||||||
type Advertisement struct {
|
type Advertisement struct {
|
||||||
adapter *Adapter
|
adapter *Adapter
|
||||||
properties *prop.Properties
|
advertisement *api.Advertisement
|
||||||
path dbus.ObjectPath
|
properties *advertising.LEAdvertisement1Properties
|
||||||
|
cancel func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultAdvertisement returns the default advertisement instance but does not
|
// DefaultAdvertisement returns the default advertisement instance but does not
|
||||||
|
@ -45,83 +45,41 @@ func (a *Adapter) DefaultAdvertisement() *Advertisement {
|
||||||
//
|
//
|
||||||
// On Linux with BlueZ, it is not possible to set the advertisement interval.
|
// On Linux with BlueZ, it is not possible to set the advertisement interval.
|
||||||
func (a *Advertisement) Configure(options AdvertisementOptions) error {
|
func (a *Advertisement) Configure(options AdvertisementOptions) error {
|
||||||
if a.properties != nil {
|
if a.advertisement != nil {
|
||||||
panic("todo: configure advertisement a second time")
|
panic("todo: configure advertisement a second time")
|
||||||
}
|
}
|
||||||
|
|
||||||
var serviceUUIDs []string
|
a.properties = &advertising.LEAdvertisement1Properties{
|
||||||
|
Type: advertising.AdvertisementTypeBroadcast,
|
||||||
|
Timeout: 1<<16 - 1,
|
||||||
|
LocalName: options.LocalName,
|
||||||
|
}
|
||||||
for _, uuid := range options.ServiceUUIDs {
|
for _, uuid := range options.ServiceUUIDs {
|
||||||
serviceUUIDs = append(serviceUUIDs, uuid.String())
|
a.properties.ServiceUUIDs = append(a.properties.ServiceUUIDs, uuid.String())
|
||||||
}
|
}
|
||||||
var serviceData = make(map[string]interface{})
|
|
||||||
for _, element := range options.ServiceData {
|
|
||||||
serviceData[element.UUID.String()] = element.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert map[uint16][]byte to map[uint16]any because that's what BlueZ needs.
|
|
||||||
manufacturerData := map[uint16]any{}
|
|
||||||
for _, element := range options.ManufacturerData {
|
|
||||||
manufacturerData[element.CompanyID] = element.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build an org.bluez.LEAdvertisement1 object, to be exported over DBus.
|
|
||||||
// See:
|
|
||||||
// https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/org.bluez.LEAdvertisement.rst
|
|
||||||
id := atomic.AddUint64(&advertisementID, 1)
|
|
||||||
a.path = dbus.ObjectPath(fmt.Sprintf("/org/tinygo/bluetooth/advertisement%d", id))
|
|
||||||
propsSpec := map[string]map[string]*prop.Prop{
|
|
||||||
"org.bluez.LEAdvertisement1": {
|
|
||||||
"Type": {Value: "broadcast"},
|
|
||||||
"ServiceUUIDs": {Value: serviceUUIDs},
|
|
||||||
"ManufacturerData": {Value: manufacturerData},
|
|
||||||
"LocalName": {Value: options.LocalName},
|
|
||||||
"ServiceData": {Value: serviceData},
|
|
||||||
// The documentation states:
|
|
||||||
// > Timeout of the advertisement in seconds. This defines the
|
|
||||||
// > lifetime of the advertisement.
|
|
||||||
// however, the value 0 also works, and presumably means "no
|
|
||||||
// timeout".
|
|
||||||
"Timeout": {Value: uint16(0)},
|
|
||||||
// TODO: MinInterval and MaxInterval (experimental as of BlueZ 5.71)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
props, err := prop.Export(a.adapter.bus, a.path, propsSpec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.properties = props
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start advertisement. May only be called after it has been configured.
|
// Start advertisement. May only be called after it has been configured.
|
||||||
func (a *Advertisement) Start() error {
|
func (a *Advertisement) Start() error {
|
||||||
// Register our advertisement object to start advertising.
|
if a.advertisement != nil {
|
||||||
err := a.adapter.adapter.Call("org.bluez.LEAdvertisingManager1.RegisterAdvertisement", 0, a.path, map[string]interface{}{}).Err
|
panic("todo: start advertisement a second time")
|
||||||
if err != nil {
|
|
||||||
if err, ok := err.(dbus.Error); ok && err.Name == "org.bluez.Error.AlreadyExists" {
|
|
||||||
return errAdvertisementAlreadyStarted
|
|
||||||
}
|
|
||||||
return fmt.Errorf("bluetooth: could not start advertisement: %w", err)
|
|
||||||
}
|
}
|
||||||
|
cancel, err := api.ExposeAdvertisement(a.adapter.id, a.properties, uint32(a.properties.Timeout))
|
||||||
// Make us discoverable.
|
|
||||||
err = a.adapter.adapter.SetProperty("org.bluez.Adapter1.Discoverable", dbus.MakeVariant(true))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("bluetooth: could not start advertisement: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
a.cancel = cancel
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop advertisement. May only be called after it has been started.
|
// Stop advertisement. May only be called after it has been started.
|
||||||
func (a *Advertisement) Stop() error {
|
func (a *Advertisement) Stop() error {
|
||||||
err := a.adapter.adapter.Call("org.bluez.LEAdvertisingManager1.UnregisterAdvertisement", 0, a.path).Err
|
if a.cancel == nil {
|
||||||
if err != nil {
|
return errAdvertisementNotStarted
|
||||||
if err, ok := err.(dbus.Error); ok && err.Name == "org.bluez.Error.DoesNotExist" {
|
|
||||||
return errAdvertisementNotStarted
|
|
||||||
}
|
|
||||||
return fmt.Errorf("bluetooth: could not stop advertisement: %w", err)
|
|
||||||
}
|
}
|
||||||
|
a.cancel()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +92,7 @@ func (a *Advertisement) Stop() error {
|
||||||
// possible some events are missed and perhaps even possible that some events
|
// possible some events are missed and perhaps even possible that some events
|
||||||
// are duplicated.
|
// are duplicated.
|
||||||
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
||||||
if a.scanCancelChan != nil {
|
if a.cancelChan != nil {
|
||||||
return errScanning
|
return errScanning
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,61 +100,58 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
||||||
// Detecting whether the scan is stopped can be done by doing a non-blocking
|
// Detecting whether the scan is stopped can be done by doing a non-blocking
|
||||||
// read from it. If it succeeds, the scan is stopped.
|
// read from it. If it succeeds, the scan is stopped.
|
||||||
cancelChan := make(chan struct{})
|
cancelChan := make(chan struct{})
|
||||||
a.scanCancelChan = cancelChan
|
a.cancelChan = cancelChan
|
||||||
|
|
||||||
// This appears to be necessary to receive any BLE discovery results at all.
|
// This appears to be necessary to receive any BLE discovery results at all.
|
||||||
defer a.adapter.Call("org.bluez.Adapter1.SetDiscoveryFilter", 0)
|
defer a.adapter.SetDiscoveryFilter(nil)
|
||||||
err := a.adapter.Call("org.bluez.Adapter1.SetDiscoveryFilter", 0, map[string]interface{}{
|
err := a.adapter.SetDiscoveryFilter(map[string]interface{}{
|
||||||
"Transport": "le",
|
"Transport": "le",
|
||||||
}).Err
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bus, err := dbus.SystemBus()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
signal := make(chan *dbus.Signal)
|
signal := make(chan *dbus.Signal)
|
||||||
a.bus.Signal(signal)
|
bus.Signal(signal)
|
||||||
defer a.bus.RemoveSignal(signal)
|
defer bus.RemoveSignal(signal)
|
||||||
|
|
||||||
propertiesChangedMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.Properties")}
|
propertiesChangedMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.Properties")}
|
||||||
a.bus.AddMatchSignal(propertiesChangedMatchOptions...)
|
bus.AddMatchSignal(propertiesChangedMatchOptions...)
|
||||||
defer a.bus.RemoveMatchSignal(propertiesChangedMatchOptions...)
|
defer bus.RemoveMatchSignal(propertiesChangedMatchOptions...)
|
||||||
|
|
||||||
newObjectMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.ObjectManager")}
|
newObjectMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.ObjectManager")}
|
||||||
a.bus.AddMatchSignal(newObjectMatchOptions...)
|
bus.AddMatchSignal(newObjectMatchOptions...)
|
||||||
defer a.bus.RemoveMatchSignal(newObjectMatchOptions...)
|
defer bus.RemoveMatchSignal(newObjectMatchOptions...)
|
||||||
|
|
||||||
// Go through all connected devices and present the connected devices as
|
// Go through all connected devices and present the connected devices as
|
||||||
// scan results. Also save the properties so that the full list of
|
// scan results. Also save the properties so that the full list of
|
||||||
// properties is known on a PropertiesChanged signal. We can't present the
|
// properties is known on a PropertiesChanged signal. We can't present the
|
||||||
// list of cached devices as scan results as devices may be cached for a
|
// list of cached devices as scan results as devices may be cached for a
|
||||||
// long time, long after they have moved out of range.
|
// long time, long after they have moved out of range.
|
||||||
var deviceList map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
deviceList, err := a.adapter.GetDevices()
|
||||||
err = a.bluez.Call("org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&deviceList)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
devices := make(map[dbus.ObjectPath]map[string]dbus.Variant)
|
devices := make(map[dbus.ObjectPath]*device.Device1Properties)
|
||||||
for path, v := range deviceList {
|
for _, dev := range deviceList {
|
||||||
device, ok := v["org.bluez.Device1"]
|
if dev.Properties.Connected {
|
||||||
if !ok {
|
callback(a, makeScanResult(dev.Properties))
|
||||||
continue // not a device
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(string(path), string(a.adapter.Path())) {
|
|
||||||
continue // not part of our adapter
|
|
||||||
}
|
|
||||||
if device["Connected"].Value().(bool) {
|
|
||||||
callback(a, makeScanResult(device))
|
|
||||||
select {
|
select {
|
||||||
case <-cancelChan:
|
case <-cancelChan:
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
devices[path] = device
|
devices[dev.Path()] = dev.Properties
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instruct BlueZ to start discovering.
|
// Instruct BlueZ to start discovering.
|
||||||
err = a.adapter.Call("org.bluez.Adapter1.StartDiscovery", 0).Err
|
err = a.adapter.StartDiscovery()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -208,7 +163,8 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
||||||
// StopScan is called).
|
// StopScan is called).
|
||||||
select {
|
select {
|
||||||
case <-cancelChan:
|
case <-cancelChan:
|
||||||
return a.adapter.Call("org.bluez.Adapter1.StopDiscovery", 0).Err
|
a.adapter.StopDiscovery()
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,24 +180,35 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
devices[objectPath] = rawprops
|
var props *device.Device1Properties
|
||||||
callback(a, makeScanResult(rawprops))
|
props, _ = props.FromDBusMap(rawprops)
|
||||||
|
devices[objectPath] = props
|
||||||
|
callback(a, makeScanResult(props))
|
||||||
case "org.freedesktop.DBus.Properties.PropertiesChanged":
|
case "org.freedesktop.DBus.Properties.PropertiesChanged":
|
||||||
interfaceName := sig.Body[0].(string)
|
interfaceName := sig.Body[0].(string)
|
||||||
if interfaceName != "org.bluez.Device1" {
|
if interfaceName != "org.bluez.Device1" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
changes := sig.Body[1].(map[string]dbus.Variant)
|
changes := sig.Body[1].(map[string]dbus.Variant)
|
||||||
device, ok := devices[sig.Path]
|
props := devices[sig.Path]
|
||||||
if !ok {
|
for field, val := range changes {
|
||||||
// This shouldn't happen, but protect against it just in
|
switch field {
|
||||||
// case.
|
case "RSSI":
|
||||||
continue
|
props.RSSI = val.Value().(int16)
|
||||||
|
case "Name":
|
||||||
|
props.Name = val.Value().(string)
|
||||||
|
case "UUIDs":
|
||||||
|
props.UUIDs = val.Value().([]string)
|
||||||
|
case "ManufacturerData":
|
||||||
|
// work around for https://github.com/muka/go-bluetooth/issues/163
|
||||||
|
mData := make(map[uint16]interface{})
|
||||||
|
for k, v := range val.Value().(map[uint16]dbus.Variant) {
|
||||||
|
mData[k] = v.Value().(interface{})
|
||||||
|
}
|
||||||
|
props.ManufacturerData = mData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for k, v := range changes {
|
callback(a, makeScanResult(props))
|
||||||
device[k] = v
|
|
||||||
}
|
|
||||||
callback(a, makeScanResult(device))
|
|
||||||
}
|
}
|
||||||
case <-cancelChan:
|
case <-cancelChan:
|
||||||
continue
|
continue
|
||||||
|
@ -255,67 +222,49 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
||||||
// callback to stop the current scan. If no scan is in progress, an error will
|
// callback to stop the current scan. If no scan is in progress, an error will
|
||||||
// be returned.
|
// be returned.
|
||||||
func (a *Adapter) StopScan() error {
|
func (a *Adapter) StopScan() error {
|
||||||
if a.scanCancelChan == nil {
|
if a.cancelChan == nil {
|
||||||
return errNotScanning
|
return errNotScanning
|
||||||
}
|
}
|
||||||
close(a.scanCancelChan)
|
close(a.cancelChan)
|
||||||
a.scanCancelChan = nil
|
a.cancelChan = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeScanResult creates a ScanResult from a raw DBus device.
|
// makeScanResult creates a ScanResult from a Device1 object.
|
||||||
func makeScanResult(props map[string]dbus.Variant) ScanResult {
|
func makeScanResult(props *device.Device1Properties) ScanResult {
|
||||||
// Assume the Address property is well-formed.
|
// Assume the Address property is well-formed.
|
||||||
addr, _ := ParseMAC(props["Address"].Value().(string))
|
addr, _ := ParseMAC(props.Address)
|
||||||
|
|
||||||
// Create a list of UUIDs.
|
// Create a list of UUIDs.
|
||||||
var serviceUUIDs []UUID
|
var serviceUUIDs []UUID
|
||||||
for _, uuid := range props["UUIDs"].Value().([]string) {
|
for _, uuid := range props.UUIDs {
|
||||||
// Assume the UUID is well-formed.
|
// Assume the UUID is well-formed.
|
||||||
parsedUUID, _ := ParseUUID(uuid)
|
parsedUUID, _ := ParseUUID(uuid)
|
||||||
serviceUUIDs = append(serviceUUIDs, parsedUUID)
|
serviceUUIDs = append(serviceUUIDs, parsedUUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
a := Address{MACAddress{MAC: addr}}
|
a := Address{MACAddress{MAC: addr}}
|
||||||
a.SetRandom(props["AddressType"].Value().(string) == "random")
|
a.SetRandom(props.AddressType == "random")
|
||||||
|
|
||||||
var manufacturerData []ManufacturerDataElement
|
mData := make(map[uint16][]byte)
|
||||||
if mdata, ok := props["ManufacturerData"].Value().(map[uint16]dbus.Variant); ok {
|
for k, v := range props.ManufacturerData {
|
||||||
for k, v := range mdata {
|
// can be either variant or just byte value
|
||||||
manufacturerData = append(manufacturerData, ManufacturerDataElement{
|
switch val := v.(type) {
|
||||||
CompanyID: k,
|
case dbus.Variant:
|
||||||
Data: v.Value().([]byte),
|
mData[k] = val.Value().([]byte)
|
||||||
})
|
case []byte:
|
||||||
}
|
mData[k] = val
|
||||||
}
|
|
||||||
|
|
||||||
// Get optional properties.
|
|
||||||
localName, _ := props["Name"].Value().(string)
|
|
||||||
rssi, _ := props["RSSI"].Value().(int16)
|
|
||||||
|
|
||||||
var serviceData []ServiceDataElement
|
|
||||||
if sdata, ok := props["ServiceData"].Value().(map[string]dbus.Variant); ok {
|
|
||||||
for k, v := range sdata {
|
|
||||||
uuid, err := ParseUUID(k)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
serviceData = append(serviceData, ServiceDataElement{
|
|
||||||
UUID: uuid,
|
|
||||||
Data: v.Value().([]byte),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ScanResult{
|
return ScanResult{
|
||||||
RSSI: rssi,
|
RSSI: props.RSSI,
|
||||||
Address: a,
|
Address: a,
|
||||||
AdvertisementPayload: &advertisementFields{
|
AdvertisementPayload: &advertisementFields{
|
||||||
AdvertisementFields{
|
AdvertisementFields{
|
||||||
LocalName: localName,
|
LocalName: props.Name,
|
||||||
ServiceUUIDs: serviceUUIDs,
|
ServiceUUIDs: serviceUUIDs,
|
||||||
ManufacturerData: manufacturerData,
|
ManufacturerData: mData,
|
||||||
ServiceData: serviceData,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -323,68 +272,40 @@ func makeScanResult(props map[string]dbus.Variant) ScanResult {
|
||||||
|
|
||||||
// Device is a connection to a remote peripheral.
|
// Device is a connection to a remote peripheral.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
Address Address // the MAC address of the device
|
device *device.Device1 // bluez device interface
|
||||||
|
ctx context.Context // context for our event watcher, canceled on disconnect event
|
||||||
device dbus.BusObject // bluez device interface
|
cancel context.CancelFunc // cancel function to halt our event watcher context
|
||||||
adapter *Adapter // the adapter that was used to form this device connection
|
propchanged chan *bluez.PropertyChanged // channel that device property changes will show up on
|
||||||
|
adapter *Adapter // the adapter that was used to form this device connection
|
||||||
|
address Address // the address of the device
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect starts a connection attempt to the given peripheral device address.
|
// Connect starts a connection attempt to the given peripheral device address.
|
||||||
//
|
//
|
||||||
// On Linux and Windows, the IsRandom part of the address is ignored.
|
// On Linux and Windows, the IsRandom part of the address is ignored.
|
||||||
func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
|
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) {
|
||||||
devicePath := dbus.ObjectPath(string(a.adapter.Path()) + "/dev_" + strings.Replace(address.MAC.String(), ":", "_", -1))
|
devicePath := dbus.ObjectPath(string(a.adapter.Path()) + "/dev_" + strings.Replace(address.MAC.String(), ":", "_", -1))
|
||||||
device := Device{
|
dev, err := device.NewDevice1(devicePath)
|
||||||
Address: address,
|
|
||||||
device: a.bus.Object("org.bluez", devicePath),
|
|
||||||
adapter: a,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Already start watching for property changes. We do this before reading
|
|
||||||
// the Connected property below to avoid a race condition: if the device
|
|
||||||
// were connected between the two calls the signal wouldn't be picked up.
|
|
||||||
signal := make(chan *dbus.Signal)
|
|
||||||
a.bus.Signal(signal)
|
|
||||||
defer a.bus.RemoveSignal(signal)
|
|
||||||
propertiesChangedMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.Properties")}
|
|
||||||
a.bus.AddMatchSignal(propertiesChangedMatchOptions...)
|
|
||||||
defer a.bus.RemoveMatchSignal(propertiesChangedMatchOptions...)
|
|
||||||
|
|
||||||
// Read whether this device is already connected.
|
|
||||||
connected, err := device.device.GetProperty("org.bluez.Device1.Connected")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Device{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to the device, if not already connected.
|
device := &Device{
|
||||||
if !connected.Value().(bool) {
|
device: dev,
|
||||||
// Start connecting (async).
|
adapter: a,
|
||||||
err := device.device.Call("org.bluez.Device1.Connect", 0).Err
|
address: address,
|
||||||
if err != nil {
|
}
|
||||||
return Device{}, fmt.Errorf("bluetooth: failed to connect: %w", err)
|
device.ctx, device.cancel = context.WithCancel(context.Background())
|
||||||
}
|
device.watchForConnect() // Set this up before we trigger a connection so we can capture the connect event
|
||||||
|
|
||||||
// Wait until the device has connected.
|
if !dev.Properties.Connected {
|
||||||
connectChan := make(chan struct{})
|
// Not yet connected, so do it now.
|
||||||
go func() {
|
// The properties have just been read so this is fresh data.
|
||||||
for sig := range signal {
|
err := dev.Connect()
|
||||||
switch sig.Name {
|
if err != nil {
|
||||||
case "org.freedesktop.DBus.Properties.PropertiesChanged":
|
device.cancel() // cancel our watcher routine
|
||||||
interfaceName := sig.Body[0].(string)
|
return nil, err
|
||||||
if interfaceName != "org.bluez.Device1" {
|
}
|
||||||
continue
|
|
||||||
}
|
|
||||||
if sig.Path != device.device.Path() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
changes := sig.Body[1].(map[string]dbus.Variant)
|
|
||||||
if connected, ok := changes["Connected"].Value().(bool); ok && connected {
|
|
||||||
close(connectChan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
<-connectChan
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return device, nil
|
return device, nil
|
||||||
|
@ -392,19 +313,51 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, err
|
||||||
|
|
||||||
// Disconnect from the BLE device. This method is non-blocking and does not
|
// Disconnect from the BLE device. This method is non-blocking and does not
|
||||||
// wait until the connection is fully gone.
|
// wait until the connection is fully gone.
|
||||||
func (d Device) Disconnect() error {
|
func (d *Device) Disconnect() error {
|
||||||
// we don't call our cancel function here, instead we wait for the
|
// we don't call our cancel function here, instead we wait for the
|
||||||
// property change in `watchForConnect` and cancel things then
|
// property change in `watchForConnect` and cancel things then
|
||||||
return d.device.Call("org.bluez.Device1.Disconnect", 0).Err
|
return d.device.Disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestConnectionParams requests a different connection latency and timeout
|
// watchForConnect watches for a signal from the bluez device interface that indicates a Connection/Disconnection.
|
||||||
// of the given device connection. Fields that are unset will be left alone.
|
|
||||||
// Whether or not the device will actually honor this, depends on the device and
|
|
||||||
// on the specific parameters.
|
|
||||||
//
|
//
|
||||||
// On Linux, this call doesn't do anything because BlueZ doesn't support
|
// We can add extra signals to watch for here,
|
||||||
// changing the connection latency.
|
// see https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt, for a full list
|
||||||
func (d Device) RequestConnectionParams(params ConnectionParams) error {
|
func (d *Device) watchForConnect() error {
|
||||||
|
var err error
|
||||||
|
d.propchanged, err = d.device.WatchProperties()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case changed := <-d.propchanged:
|
||||||
|
|
||||||
|
// we will receive a nil if bluez.UnwatchProperties(a, ch) is called, if so we can stop watching
|
||||||
|
if changed == nil {
|
||||||
|
d.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch changed.Name {
|
||||||
|
case "Connected":
|
||||||
|
// Send off a notification indicating we have connected or disconnected
|
||||||
|
d.adapter.connectHandler(d.address, d.device.Properties.Connected)
|
||||||
|
|
||||||
|
if !d.device.Properties.Connected {
|
||||||
|
d.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
case <-d.ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
15
gap_nrf51.go
15
gap_nrf51.go
|
@ -1,21 +1,16 @@
|
||||||
//go:build softdevice && s110v8
|
//go:build softdevice && s110v8
|
||||||
|
// +build softdevice,s110v8
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#include "ble_gap.h"
|
#include "ble_gap.h"
|
||||||
|
|
||||||
// Workaround wrapper function to avoid pointer arguments escaping to heap
|
|
||||||
static inline uint32_t sd_ble_gap_adv_start_noescape(ble_gap_adv_params_t const p_adv_params) {
|
|
||||||
return sd_ble_gap_adv_start(&p_adv_params);
|
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Address contains a Bluetooth MAC address.
|
// Address contains a Bluetooth MAC address.
|
||||||
|
@ -53,7 +48,7 @@ func (a *Advertisement) Configure(options AdvertisementOptions) error {
|
||||||
return errAdvertisementPacketTooBig
|
return errAdvertisementPacketTooBig
|
||||||
}
|
}
|
||||||
|
|
||||||
errCode := C.sd_ble_gap_adv_data_set((*C.uint8_t)(unsafe.Pointer(&payload.data[0])), C.uint8_t(payload.len), nil, 0)
|
errCode := C.sd_ble_gap_adv_data_set(&payload.data[0], payload.len, nil, 0)
|
||||||
a.interval = options.Interval
|
a.interval = options.Interval
|
||||||
return makeError(errCode)
|
return makeError(errCode)
|
||||||
}
|
}
|
||||||
|
@ -74,12 +69,12 @@ func (a *Advertisement) Stop() error {
|
||||||
|
|
||||||
// Low-level version of Start. Used to restart advertisement when a connection
|
// Low-level version of Start. Used to restart advertisement when a connection
|
||||||
// is lost.
|
// is lost.
|
||||||
func (a *Advertisement) start() C.uint32_t {
|
func (a *Advertisement) start() uint32 {
|
||||||
params := C.ble_gap_adv_params_t{
|
params := C.ble_gap_adv_params_t{
|
||||||
_type: C.BLE_GAP_ADV_TYPE_ADV_IND,
|
_type: C.BLE_GAP_ADV_TYPE_ADV_IND,
|
||||||
fp: C.BLE_GAP_ADV_FP_ANY,
|
fp: C.BLE_GAP_ADV_FP_ANY,
|
||||||
interval: C.uint16_t(a.interval),
|
interval: uint16(a.interval),
|
||||||
timeout: 0, // no timeout
|
timeout: 0, // no timeout
|
||||||
}
|
}
|
||||||
return C.sd_ble_gap_adv_start_noescape(params)
|
return C.sd_ble_gap_adv_start(¶ms)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
//go:build (softdevice && s113v7) || (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
|
//go:build (softdevice && s113v7) || (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
|
||||||
|
// +build softdevice,s113v7 softdevice,s132v6 softdevice,s140v6 softdevice,s140v7
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -20,7 +20,7 @@ type Address struct {
|
||||||
|
|
||||||
// Advertisement encapsulates a single advertisement instance.
|
// Advertisement encapsulates a single advertisement instance.
|
||||||
type Advertisement struct {
|
type Advertisement struct {
|
||||||
handle C.uint8_t
|
handle uint8
|
||||||
isAdvertising volatile.Register8
|
isAdvertising volatile.Register8
|
||||||
payload rawAdvertisementPayload
|
payload rawAdvertisementPayload
|
||||||
}
|
}
|
||||||
|
@ -58,14 +58,14 @@ func (a *Advertisement) Configure(options AdvertisementOptions) error {
|
||||||
|
|
||||||
data := C.ble_gap_adv_data_t{}
|
data := C.ble_gap_adv_data_t{}
|
||||||
data.adv_data = C.ble_data_t{
|
data.adv_data = C.ble_data_t{
|
||||||
p_data: (*C.uint8_t)(unsafe.Pointer(&a.payload.data[0])),
|
p_data: &a.payload.data[0],
|
||||||
len: C.uint16_t(a.payload.len),
|
len: uint16(a.payload.len),
|
||||||
}
|
}
|
||||||
params := C.ble_gap_adv_params_t{
|
params := C.ble_gap_adv_params_t{
|
||||||
properties: C.ble_gap_adv_properties_t{
|
properties: C.ble_gap_adv_properties_t{
|
||||||
_type: C.BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED,
|
_type: C.BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED,
|
||||||
},
|
},
|
||||||
interval: C.uint32_t(options.Interval),
|
interval: uint32(options.Interval),
|
||||||
}
|
}
|
||||||
errCode := C.sd_ble_gap_adv_set_configure(&a.handle, &data, ¶ms)
|
errCode := C.sd_ble_gap_adv_set_configure(&a.handle, &data, ¶ms)
|
||||||
return makeError(errCode)
|
return makeError(errCode)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
|
//go:build (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
|
||||||
|
// +build softdevice,s132v6 softdevice,s140v6 softdevice,s140v7
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
@ -7,7 +8,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -16,7 +16,6 @@ import (
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
var errAlreadyConnecting = errors.New("bluetooth: already in a connection attempt")
|
var errAlreadyConnecting = errors.New("bluetooth: already in a connection attempt")
|
||||||
var errConnectionTimeout = errors.New("bluetooth: timeout while connecting")
|
|
||||||
|
|
||||||
// Memory buffers needed by sd_ble_gap_scan_start.
|
// Memory buffers needed by sd_ble_gap_scan_start.
|
||||||
var (
|
var (
|
||||||
|
@ -42,12 +41,12 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
||||||
scanParams := C.ble_gap_scan_params_t{}
|
scanParams := C.ble_gap_scan_params_t{}
|
||||||
scanParams.set_bitfield_extended(0)
|
scanParams.set_bitfield_extended(0)
|
||||||
scanParams.set_bitfield_active(0)
|
scanParams.set_bitfield_active(0)
|
||||||
scanParams.interval = C.uint16_t(NewDuration(40 * time.Millisecond))
|
scanParams.interval = uint16(NewDuration(40 * time.Millisecond))
|
||||||
scanParams.window = C.uint16_t(NewDuration(30 * time.Millisecond))
|
scanParams.window = uint16(NewDuration(30 * time.Millisecond))
|
||||||
scanParams.timeout = C.BLE_GAP_SCAN_TIMEOUT_UNLIMITED
|
scanParams.timeout = C.BLE_GAP_SCAN_TIMEOUT_UNLIMITED
|
||||||
scanReportBufferInfo := C.ble_data_t{
|
scanReportBufferInfo := C.ble_data_t{
|
||||||
p_data: (*C.uint8_t)(unsafe.Pointer(&scanReportBuffer.data[0])),
|
p_data: &scanReportBuffer.data[0],
|
||||||
len: C.uint16_t(len(scanReportBuffer.data)),
|
len: uint16(len(scanReportBuffer.data)),
|
||||||
}
|
}
|
||||||
errCode := C.sd_ble_gap_scan_start(&scanParams, &scanReportBufferInfo)
|
errCode := C.sd_ble_gap_scan_start(&scanParams, &scanReportBufferInfo)
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
|
@ -93,10 +92,15 @@ func (a *Adapter) StopScan() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Device is a connection to a remote peripheral.
|
||||||
|
type Device struct {
|
||||||
|
connectionHandle uint16
|
||||||
|
}
|
||||||
|
|
||||||
// In-progress connection attempt.
|
// In-progress connection attempt.
|
||||||
var connectionAttempt struct {
|
var connectionAttempt struct {
|
||||||
state volatile.Register8 // 0 means unused, 1 means connecting, 2 means connected, 3 means timeout
|
state volatile.Register8 // 0 means unused, 1 means connecting, 2 means ready (connected or timeout)
|
||||||
connectionHandle C.uint16_t
|
connectionHandle uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect starts a connection attempt to the given peripheral device address.
|
// Connect starts a connection attempt to the given peripheral device address.
|
||||||
|
@ -105,10 +109,10 @@ var connectionAttempt struct {
|
||||||
// connection attempt at once and that the address parameter must have the
|
// connection attempt at once and that the address parameter must have the
|
||||||
// IsRandom bit set correctly. This bit is set correctly for scan results, so
|
// IsRandom bit set correctly. This bit is set correctly for scan results, so
|
||||||
// you can reuse that address directly.
|
// you can reuse that address directly.
|
||||||
func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
|
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) {
|
||||||
// Construct an address object as used in the SoftDevice.
|
// Construct an address object as used in the SoftDevice.
|
||||||
var addr C.ble_gap_addr_t
|
var addr C.ble_gap_addr_t
|
||||||
addr.addr = makeSDAddress(address.MAC)
|
addr.addr = address.MAC
|
||||||
if address.IsRandom() {
|
if address.IsRandom() {
|
||||||
switch address.MAC[5] >> 6 {
|
switch address.MAC[5] >> 6 {
|
||||||
case 0b11:
|
case 0b11:
|
||||||
|
@ -139,25 +143,22 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, err
|
||||||
scanParams := C.ble_gap_scan_params_t{}
|
scanParams := C.ble_gap_scan_params_t{}
|
||||||
scanParams.set_bitfield_extended(0)
|
scanParams.set_bitfield_extended(0)
|
||||||
scanParams.set_bitfield_active(0)
|
scanParams.set_bitfield_active(0)
|
||||||
scanParams.interval = C.uint16_t(NewDuration(40 * time.Millisecond))
|
scanParams.interval = uint16(NewDuration(40 * time.Millisecond))
|
||||||
scanParams.window = C.uint16_t(NewDuration(30 * time.Millisecond))
|
scanParams.window = uint16(NewDuration(30 * time.Millisecond))
|
||||||
scanParams.timeout = C.uint16_t(params.ConnectionTimeout / 16) // timeout in 10ms units
|
scanParams.timeout = uint16(params.ConnectionTimeout)
|
||||||
|
|
||||||
connectionParams := C.ble_gap_conn_params_t{
|
connectionParams := C.ble_gap_conn_params_t{
|
||||||
min_conn_interval: C.uint16_t(params.MinInterval) / 2,
|
min_conn_interval: uint16(params.MinInterval) / 2,
|
||||||
max_conn_interval: C.uint16_t(params.MaxInterval) / 2,
|
max_conn_interval: uint16(params.MaxInterval) / 2,
|
||||||
slave_latency: 0, // mostly relevant to connected keyboards etc
|
slave_latency: 0, // mostly relevant to connected keyboards etc
|
||||||
conn_sup_timeout: 200, // 2 seconds (in 10ms units), the minimum recommended by Apple
|
conn_sup_timeout: 200, // 2 seconds (in 10ms units), the minimum recommended by Apple
|
||||||
}
|
}
|
||||||
if params.Timeout != 0 {
|
|
||||||
connectionParams.conn_sup_timeout = uint16(params.Timeout / 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flag to the event handler that we are waiting for incoming connections.
|
// Flag to the event handler that we are waiting for incoming connections.
|
||||||
// This should be safe as long as Connect is not called concurrently. And
|
// This should be safe as long as Connect is not called concurrently. And
|
||||||
// even then, it should catch most such race conditions.
|
// even then, it should catch most such race conditions.
|
||||||
if connectionAttempt.state.Get() != 0 {
|
if connectionAttempt.state.Get() != 0 {
|
||||||
return Device{}, errAlreadyConnecting
|
return nil, errAlreadyConnecting
|
||||||
}
|
}
|
||||||
connectionAttempt.state.Set(1)
|
connectionAttempt.state.Set(1)
|
||||||
|
|
||||||
|
@ -165,33 +166,26 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, err
|
||||||
errCode := C.sd_ble_gap_connect(&addr, &scanParams, &connectionParams, C.BLE_CONN_CFG_TAG_DEFAULT)
|
errCode := C.sd_ble_gap_connect(&addr, &scanParams, &connectionParams, C.BLE_CONN_CFG_TAG_DEFAULT)
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
connectionAttempt.state.Set(0)
|
connectionAttempt.state.Set(0)
|
||||||
return Device{}, Error(errCode)
|
return nil, Error(errCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until the connection is established.
|
// Wait until the connection is established.
|
||||||
for {
|
// TODO: use some sort of condition variable once the scheduler supports
|
||||||
state := connectionAttempt.state.Get()
|
// them.
|
||||||
if state == 2 {
|
for connectionAttempt.state.Get() != 2 {
|
||||||
// Successfully connected.
|
arm.Asm("wfe")
|
||||||
connectionAttempt.state.Set(0)
|
|
||||||
connectionHandle := connectionAttempt.connectionHandle
|
|
||||||
return Device{
|
|
||||||
connectionHandle: connectionHandle,
|
|
||||||
}, nil
|
|
||||||
} else if state == 3 {
|
|
||||||
// Timeout while connecting.
|
|
||||||
connectionAttempt.state.Set(0)
|
|
||||||
return Device{}, errConnectionTimeout
|
|
||||||
} else {
|
|
||||||
// TODO: use some sort of condition variable once the scheduler
|
|
||||||
// supports them.
|
|
||||||
arm.Asm("wfe")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
connectionHandle := connectionAttempt.connectionHandle
|
||||||
|
connectionAttempt.state.Set(0)
|
||||||
|
|
||||||
|
// Connection has been established.
|
||||||
|
return &Device{
|
||||||
|
connectionHandle: connectionHandle,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect from the BLE device.
|
// Disconnect from the BLE device.
|
||||||
func (d Device) Disconnect() error {
|
func (d *Device) Disconnect() error {
|
||||||
errCode := C.sd_ble_gap_disconnect(d.connectionHandle, C.BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION)
|
errCode := C.sd_ble_gap_disconnect(d.connectionHandle, C.BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION)
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return Error(errCode)
|
return Error(errCode)
|
||||||
|
@ -199,34 +193,3 @@ func (d Device) Disconnect() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestConnectionParams requests a different connection latency and timeout
|
|
||||||
// of the given device connection. Fields that are unset will be left alone.
|
|
||||||
// Whether or not the device will actually honor this, depends on the device and
|
|
||||||
// on the specific parameters.
|
|
||||||
//
|
|
||||||
// On the Nordic SoftDevice, this call will also set the slave latency to 0.
|
|
||||||
func (d Device) RequestConnectionParams(params ConnectionParams) error {
|
|
||||||
// The default parameters if no specific parameters are picked.
|
|
||||||
connParams := C.ble_gap_conn_params_t{
|
|
||||||
min_conn_interval: C.BLE_GAP_CP_MIN_CONN_INTVL_NONE,
|
|
||||||
max_conn_interval: C.BLE_GAP_CP_MAX_CONN_INTVL_NONE,
|
|
||||||
slave_latency: 0,
|
|
||||||
conn_sup_timeout: C.BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use specified parameters if available.
|
|
||||||
if params.MinInterval != 0 {
|
|
||||||
connParams.min_conn_interval = C.uint16_t(params.MinInterval) / 2
|
|
||||||
}
|
|
||||||
if params.MaxInterval != 0 {
|
|
||||||
connParams.max_conn_interval = C.uint16_t(params.MaxInterval) / 2
|
|
||||||
}
|
|
||||||
if params.Timeout != 0 {
|
|
||||||
connParams.conn_sup_timeout = C.uint16_t(params.Timeout) / 16
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send them to peer device.
|
|
||||||
errCode := C.sd_ble_gap_conn_param_update(d.connectionHandle, &connParams)
|
|
||||||
return makeError(errCode)
|
|
||||||
}
|
|
||||||
|
|
15
gap_sd.go
15
gap_sd.go
|
@ -1,15 +0,0 @@
|
||||||
//go:build softdevice
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include "ble_gap.h"
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// Device is a connection to a remote peripheral or central.
|
|
||||||
type Device struct {
|
|
||||||
Address Address
|
|
||||||
|
|
||||||
connectionHandle C.uint16_t
|
|
||||||
}
|
|
133
gap_test.go
133
gap_test.go
|
@ -1,133 +0,0 @@
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCreateAdvertisementPayload(t *testing.T) {
|
|
||||||
type testCase struct {
|
|
||||||
raw string
|
|
||||||
parsed AdvertisementOptions
|
|
||||||
}
|
|
||||||
tests := []testCase{
|
|
||||||
{
|
|
||||||
raw: "\x02\x01\x06", // flags
|
|
||||||
parsed: AdvertisementOptions{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
raw: "\x02\x01\x06", // flags
|
|
||||||
parsed: AdvertisementOptions{
|
|
||||||
// Interval doesn't affect the advertisement payload.
|
|
||||||
Interval: NewDuration(100 * time.Millisecond),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
raw: "\x02\x01\x06" + // flags
|
|
||||||
"\x07\x09foobar", // local name
|
|
||||||
parsed: AdvertisementOptions{
|
|
||||||
LocalName: "foobar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
raw: "\x02\x01\x06" + // flags
|
|
||||||
"\x0b\x09Heart rate" + // local name
|
|
||||||
"\x03\x03\x0d\x18", // service UUID
|
|
||||||
parsed: AdvertisementOptions{
|
|
||||||
LocalName: "Heart rate",
|
|
||||||
ServiceUUIDs: []UUID{
|
|
||||||
ServiceUUIDHeartRate,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Note: the two service UUIDs should really be merged into one to
|
|
||||||
// save space.
|
|
||||||
raw: "\x02\x01\x06" + // flags
|
|
||||||
"\x0b\x09Heart rate" + // local name
|
|
||||||
"\x03\x03\x0d\x18" + // heart rate service UUID
|
|
||||||
"\x03\x03\x0f\x18", // battery service UUID
|
|
||||||
parsed: AdvertisementOptions{
|
|
||||||
LocalName: "Heart rate",
|
|
||||||
ServiceUUIDs: []UUID{
|
|
||||||
ServiceUUIDHeartRate,
|
|
||||||
ServiceUUIDBattery,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
raw: "\x02\x01\x06" + // flags
|
|
||||||
"\a\xff\x34\x12asdf", // manufacturer data
|
|
||||||
parsed: AdvertisementOptions{
|
|
||||||
ManufacturerData: []ManufacturerDataElement{
|
|
||||||
{0x1234, []byte("asdf")},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
raw: "\x02\x01\x06" + // flags
|
|
||||||
"\x04\xff\x34\x12\x05" + // manufacturer data 1
|
|
||||||
"\x05\xff\xff\xff\x03\x07" + // manufacturer data 2
|
|
||||||
"\x03\xff\x11\x00", // manufacturer data 3
|
|
||||||
parsed: AdvertisementOptions{
|
|
||||||
ManufacturerData: []ManufacturerDataElement{
|
|
||||||
{0x1234, []byte{5}},
|
|
||||||
{0xffff, []byte{3, 7}},
|
|
||||||
{0x0011, []byte{}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
raw: "\x02\x01\x06" + // flags
|
|
||||||
"\x05\x16\xD2\xFC\x40\x02" + // service data 16-Bit UUID
|
|
||||||
"\x06\x20\xD2\xFC\x40\x02\xC4", // service data 32-Bit UUID
|
|
||||||
parsed: AdvertisementOptions{
|
|
||||||
ServiceData: []ServiceDataElement{
|
|
||||||
{UUID: New16BitUUID(0xFCD2), Data: []byte{0x40, 0x02}},
|
|
||||||
{UUID: New32BitUUID(0x0240FCD2), Data: []byte{0xC4}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
raw: "\x02\x01\x06" + // flags
|
|
||||||
"\x05\x16\xD2\xFC\x40\x02" + // service data 16-Bit UUID
|
|
||||||
"\x05\x16\xD3\xFC\x40\x02", // service data 16-Bit UUID
|
|
||||||
parsed: AdvertisementOptions{
|
|
||||||
ServiceData: []ServiceDataElement{
|
|
||||||
{UUID: New16BitUUID(0xFCD2), Data: []byte{0x40, 0x02}},
|
|
||||||
{UUID: New16BitUUID(0xFCD3), Data: []byte{0x40, 0x02}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
raw: "\x02\x01\x06" + // flags
|
|
||||||
"\x04\x16\xD2\xFC\x40" + // service data 16-Bit UUID
|
|
||||||
"\x12\x21\xB8\x6C\x75\x05\xE9\x25\xBD\x93\xA8\x42\x32\xC3\x00\x01\xAF\xAD\x09", // service data 128-Bit UUID
|
|
||||||
parsed: AdvertisementOptions{
|
|
||||||
ServiceData: []ServiceDataElement{
|
|
||||||
{UUID: New16BitUUID(0xFCD2), Data: []byte{0x40}},
|
|
||||||
{
|
|
||||||
UUID: NewUUID([16]byte{0xad, 0xaf, 0x01, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8}),
|
|
||||||
Data: []byte{0x09},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tests {
|
|
||||||
var expectedRaw rawAdvertisementPayload
|
|
||||||
expectedRaw.len = uint8(len(tc.raw))
|
|
||||||
copy(expectedRaw.data[:], tc.raw)
|
|
||||||
|
|
||||||
var raw rawAdvertisementPayload
|
|
||||||
raw.addFromOptions(tc.parsed)
|
|
||||||
if raw != expectedRaw {
|
|
||||||
t.Errorf("error when serializing options: %#v\nexpected: %#v\nactual: %#v\n", tc.parsed, tc.raw, string(raw.data[:raw.len]))
|
|
||||||
}
|
|
||||||
mdata := raw.ManufacturerData()
|
|
||||||
if !reflect.DeepEqual(mdata, tc.parsed.ManufacturerData) {
|
|
||||||
t.Errorf("ManufacturerData was not parsed as expected:\nexpected: %#v\nactual: %#v", tc.parsed.ManufacturerData, mdata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
175
gap_windows.go
175
gap_windows.go
|
@ -18,108 +18,6 @@ type Address struct {
|
||||||
MACAddress
|
MACAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
type Advertisement struct {
|
|
||||||
advertisement *advertisement.BluetoothLEAdvertisement
|
|
||||||
publisher *advertisement.BluetoothLEAdvertisementPublisher
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultAdvertisement returns the default advertisement instance but does not
|
|
||||||
// configure it.
|
|
||||||
func (a *Adapter) DefaultAdvertisement() *Advertisement {
|
|
||||||
if a.defaultAdvertisement == nil {
|
|
||||||
a.defaultAdvertisement = &Advertisement{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.defaultAdvertisement
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure this advertisement.
|
|
||||||
// on Windows we're only able to set "Manufacturer Data" for advertisements.
|
|
||||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisementpublisher?view=winrt-22621#remarks
|
|
||||||
// following this c# source for this implementation: https://github.com/microsoft/Windows-universal-samples/blob/main/Samples/BluetoothAdvertisement/cs/Scenario2_Publisher.xaml.cs
|
|
||||||
// adding service data / localname leads to errors when starting the advertisement.
|
|
||||||
func (a *Advertisement) Configure(options AdvertisementOptions) error {
|
|
||||||
// we can only advertise manufacturer / company data on windows, so no need to continue if we have none
|
|
||||||
if len(options.ManufacturerData) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.publisher != nil {
|
|
||||||
a.publisher.Release()
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.advertisement != nil {
|
|
||||||
a.advertisement.Release()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub, err := advertisement.NewBluetoothLEAdvertisementPublisher()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.publisher = pub
|
|
||||||
|
|
||||||
ad, err := a.publisher.GetAdvertisement()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.advertisement = ad
|
|
||||||
|
|
||||||
vec, err := ad.GetManufacturerData()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, optManData := range options.ManufacturerData {
|
|
||||||
writer, err := streams.NewDataWriter()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer writer.Release()
|
|
||||||
|
|
||||||
err = writer.WriteBytes(uint32(len(optManData.Data)), optManData.Data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := writer.DetachBuffer()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
manData, err := advertisement.BluetoothLEManufacturerDataCreate(optManData.CompanyID, buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = vec.Append(unsafe.Pointer(&manData.IUnknown.RawVTable)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start advertisement. May only be called after it has been configured.
|
|
||||||
func (a *Advertisement) Start() error {
|
|
||||||
// publisher will be present if we actually have manufacturer data to advertise.
|
|
||||||
if a.publisher != nil {
|
|
||||||
return a.publisher.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop advertisement. May only be called after it has been started.
|
|
||||||
func (a *Advertisement) Stop() error {
|
|
||||||
if a.publisher != nil {
|
|
||||||
return a.publisher.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan starts a BLE scan. It is stopped by a call to StopScan. A common pattern
|
// Scan starts a BLE scan. It is stopped by a call to StopScan. A common pattern
|
||||||
// is to cancel the scan when a particular device has been found.
|
// is to cancel the scan when a particular device has been found.
|
||||||
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
|
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
|
||||||
|
@ -170,25 +68,18 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
|
||||||
// Wait for when advertisement has stopped by a call to StopScan().
|
// Wait for when advertisement has stopped by a call to StopScan().
|
||||||
// Advertisement doesn't seem to stop right away, there is an
|
// Advertisement doesn't seem to stop right away, there is an
|
||||||
// intermediate Stopping state.
|
// intermediate Stopping state.
|
||||||
stoppingChan := make(chan error)
|
stoppingChan := make(chan struct{})
|
||||||
// TypedEventHandler<BluetoothLEAdvertisementWatcher, BluetoothLEAdvertisementWatcherStoppedEventArgs>
|
// TypedEventHandler<BluetoothLEAdvertisementWatcher, BluetoothLEAdvertisementWatcherStoppedEventArgs>
|
||||||
eventStoppedGuid := winrt.ParameterizedInstanceGUID(
|
eventStoppedGuid := winrt.ParameterizedInstanceGUID(
|
||||||
foundation.GUIDTypedEventHandler,
|
foundation.GUIDTypedEventHandler,
|
||||||
advertisement.SignatureBluetoothLEAdvertisementWatcher,
|
advertisement.SignatureBluetoothLEAdvertisementWatcher,
|
||||||
advertisement.SignatureBluetoothLEAdvertisementWatcherStoppedEventArgs,
|
advertisement.SignatureBluetoothLEAdvertisementWatcherStoppedEventArgs,
|
||||||
)
|
)
|
||||||
stoppedHandler := foundation.NewTypedEventHandler(ole.NewGUID(eventStoppedGuid), func(_ *foundation.TypedEventHandler, _, arg unsafe.Pointer) {
|
stoppedHandler := foundation.NewTypedEventHandler(ole.NewGUID(eventStoppedGuid), func(_ *foundation.TypedEventHandler, _, _ unsafe.Pointer) {
|
||||||
args := (*advertisement.BluetoothLEAdvertisementWatcherStoppedEventArgs)(arg)
|
// Note: the args parameter has an Error property that should
|
||||||
errCode, err := args.GetError()
|
// probably be checked, but I'm not sure when stopping the
|
||||||
if err != nil {
|
// advertisement watcher could ever result in an error (except
|
||||||
// Got an error while getting the error value, that shouldn't
|
// for bugs).
|
||||||
// happen.
|
|
||||||
stoppingChan <- fmt.Errorf("failed to get stopping error value: %w", err)
|
|
||||||
} else if errCode != bluetooth.BluetoothErrorSuccess {
|
|
||||||
// Could not stop the scan? I'm not sure when this would actually
|
|
||||||
// happen.
|
|
||||||
stoppingChan <- fmt.Errorf("failed to stop scanning (error code %d)", errCode)
|
|
||||||
}
|
|
||||||
close(stoppingChan)
|
close(stoppingChan)
|
||||||
})
|
})
|
||||||
defer stoppedHandler.Release()
|
defer stoppedHandler.Release()
|
||||||
|
@ -205,7 +96,8 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until advertisement has stopped, and finish.
|
// Wait until advertisement has stopped, and finish.
|
||||||
return <-stoppingChan
|
<-stoppingChan
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getScanResultFromArgs(args *advertisement.BluetoothLEAdvertisementReceivedEventArgs) ScanResult {
|
func getScanResultFromArgs(args *advertisement.BluetoothLEAdvertisementReceivedEventArgs) ScanResult {
|
||||||
|
@ -222,7 +114,7 @@ func getScanResultFromArgs(args *advertisement.BluetoothLEAdvertisementReceivedE
|
||||||
Address: adr,
|
Address: adr,
|
||||||
}
|
}
|
||||||
|
|
||||||
var manufacturerData []ManufacturerDataElement
|
var manufacturerData map[uint16][]byte = make(map[uint16][]byte)
|
||||||
if winAdv, err := args.GetAdvertisement(); err == nil && winAdv != nil {
|
if winAdv, err := args.GetAdvertisement(); err == nil && winAdv != nil {
|
||||||
vector, _ := winAdv.GetManufacturerData()
|
vector, _ := winAdv.GetManufacturerData()
|
||||||
size, _ := vector.GetSize()
|
size, _ := vector.GetSize()
|
||||||
|
@ -231,10 +123,7 @@ func getScanResultFromArgs(args *advertisement.BluetoothLEAdvertisementReceivedE
|
||||||
manData := (*advertisement.BluetoothLEManufacturerData)(element)
|
manData := (*advertisement.BluetoothLEManufacturerData)(element)
|
||||||
companyID, _ := manData.GetCompanyId()
|
companyID, _ := manData.GetCompanyId()
|
||||||
buffer, _ := manData.GetData()
|
buffer, _ := manData.GetData()
|
||||||
manufacturerData = append(manufacturerData, ManufacturerDataElement{
|
manufacturerData[companyID] = bufferToSlice(buffer)
|
||||||
CompanyID: companyID,
|
|
||||||
Data: bufferToSlice(buffer),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +141,7 @@ func getScanResultFromArgs(args *advertisement.BluetoothLEAdvertisementReceivedE
|
||||||
}
|
}
|
||||||
|
|
||||||
func bufferToSlice(buffer *streams.IBuffer) []byte {
|
func bufferToSlice(buffer *streams.IBuffer) []byte {
|
||||||
dataReader, _ := streams.DataReaderFromBuffer(buffer)
|
dataReader, _ := streams.FromBuffer(buffer)
|
||||||
defer dataReader.Release()
|
defer dataReader.Release()
|
||||||
bufferSize, _ := buffer.GetLength()
|
bufferSize, _ := buffer.GetLength()
|
||||||
if bufferSize == 0 {
|
if bufferSize == 0 {
|
||||||
|
@ -274,8 +163,6 @@ func (a *Adapter) StopScan() error {
|
||||||
|
|
||||||
// Device is a connection to a remote peripheral.
|
// Device is a connection to a remote peripheral.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
Address Address // the MAC address of the device
|
|
||||||
|
|
||||||
device *bluetooth.BluetoothLEDevice
|
device *bluetooth.BluetoothLEDevice
|
||||||
session *genericattributeprofile.GattSession
|
session *genericattributeprofile.GattSession
|
||||||
}
|
}
|
||||||
|
@ -283,32 +170,32 @@ type Device struct {
|
||||||
// Connect starts a connection attempt to the given peripheral device address.
|
// Connect starts a connection attempt to the given peripheral device address.
|
||||||
//
|
//
|
||||||
// On Linux and Windows, the IsRandom part of the address is ignored.
|
// On Linux and Windows, the IsRandom part of the address is ignored.
|
||||||
func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
|
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) {
|
||||||
var winAddr uint64
|
var winAddr uint64
|
||||||
for i := range address.MAC {
|
for i := range address.MAC {
|
||||||
winAddr += uint64(address.MAC[i]) << (8 * i)
|
winAddr += uint64(address.MAC[i]) << (8 * i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IAsyncOperation<BluetoothLEDevice>
|
// IAsyncOperation<BluetoothLEDevice>
|
||||||
bleDeviceOp, err := bluetooth.BluetoothLEDeviceFromBluetoothAddressAsync(winAddr)
|
bleDeviceOp, err := bluetooth.FromBluetoothAddressAsync(winAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Device{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to pass the signature of the parameter returned by the async operation:
|
// We need to pass the signature of the parameter returned by the async operation:
|
||||||
// IAsyncOperation<BluetoothLEDevice>
|
// IAsyncOperation<BluetoothLEDevice>
|
||||||
if err := awaitAsyncOperation(bleDeviceOp, bluetooth.SignatureBluetoothLEDevice); err != nil {
|
if err := awaitAsyncOperation(bleDeviceOp, bluetooth.SignatureBluetoothLEDevice); err != nil {
|
||||||
return Device{}, fmt.Errorf("error connecting to device: %w", err)
|
return nil, fmt.Errorf("error connecting to device: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := bleDeviceOp.GetResults()
|
res, err := bleDeviceOp.GetResults()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Device{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// The returned BluetoothLEDevice is set to null if FromBluetoothAddressAsync can't find the device identified by bluetoothAddress
|
// The returned BluetoothLEDevice is set to null if FromBluetoothAddressAsync can't find the device identified by bluetoothAddress
|
||||||
if uintptr(res) == 0x0 {
|
if uintptr(res) == 0x0 {
|
||||||
return Device{}, fmt.Errorf("device with the given address was not found")
|
return nil, fmt.Errorf("device with the given address was not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
bleDevice := (*bluetooth.BluetoothLEDevice)(res)
|
bleDevice := (*bluetooth.BluetoothLEDevice)(res)
|
||||||
|
@ -317,37 +204,37 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, err
|
||||||
// To initiate a connection, we need to set GattSession.MaintainConnection to true.
|
// To initiate a connection, we need to set GattSession.MaintainConnection to true.
|
||||||
dID, err := bleDevice.GetBluetoothDeviceId()
|
dID, err := bleDevice.GetBluetoothDeviceId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Device{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windows does not support explicitly connecting to a device.
|
// Windows does not support explicitly connecting to a device.
|
||||||
// Instead it has the concept of a GATT session that is owned
|
// Instead it has the concept of a GATT session that is owned
|
||||||
// by the calling program.
|
// by the calling program.
|
||||||
gattSessionOp, err := genericattributeprofile.GattSessionFromDeviceIdAsync(dID) // IAsyncOperation<GattSession>
|
gattSessionOp, err := genericattributeprofile.FromDeviceIdAsync(dID) // IAsyncOperation<GattSession>
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Device{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := awaitAsyncOperation(gattSessionOp, genericattributeprofile.SignatureGattSession); err != nil {
|
if err := awaitAsyncOperation(gattSessionOp, genericattributeprofile.SignatureGattSession); err != nil {
|
||||||
return Device{}, fmt.Errorf("error getting gatt session: %w", err)
|
return nil, fmt.Errorf("error getting gatt session: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gattRes, err := gattSessionOp.GetResults()
|
gattRes, err := gattSessionOp.GetResults()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Device{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newSession := (*genericattributeprofile.GattSession)(gattRes)
|
newSession := (*genericattributeprofile.GattSession)(gattRes)
|
||||||
// This keeps the device connected until we set maintain_connection = False.
|
// This keeps the device connected until we set maintain_connection = False.
|
||||||
if err := newSession.SetMaintainConnection(true); err != nil {
|
if err := newSession.SetMaintainConnection(true); err != nil {
|
||||||
return Device{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Device{address, bleDevice, newSession}, nil
|
return &Device{bleDevice, newSession}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect from the BLE device. This method is non-blocking and does not
|
// Disconnect from the BLE device. This method is non-blocking and does not
|
||||||
// wait until the connection is fully gone.
|
// wait until the connection is fully gone.
|
||||||
func (d Device) Disconnect() error {
|
func (d *Device) Disconnect() error {
|
||||||
defer d.device.Release()
|
defer d.device.Release()
|
||||||
defer d.session.Release()
|
defer d.session.Release()
|
||||||
|
|
||||||
|
@ -360,15 +247,3 @@ func (d Device) Disconnect() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestConnectionParams requests a different connection latency and timeout
|
|
||||||
// of the given device connection. Fields that are unset will be left alone.
|
|
||||||
// Whether or not the device will actually honor this, depends on the device and
|
|
||||||
// on the specific parameters.
|
|
||||||
//
|
|
||||||
// On Windows, this call doesn't do anything.
|
|
||||||
func (d Device) RequestConnectionParams(params ConnectionParams) error {
|
|
||||||
// TODO: implement this using
|
|
||||||
// BluetoothLEDevice.RequestPreferredConnectionParameters.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
//
|
//
|
||||||
// Passing a nil slice of UUIDs will return a complete list of
|
// Passing a nil slice of UUIDs will return a complete list of
|
||||||
// services.
|
// services.
|
||||||
func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
d.prph.DiscoverServices([]cbgo.UUID{})
|
d.prph.DiscoverServices([]cbgo.UUID{})
|
||||||
|
|
||||||
// clear cache of services
|
// clear cache of services
|
||||||
|
@ -69,14 +69,14 @@ type DeviceService struct {
|
||||||
type deviceService struct {
|
type deviceService struct {
|
||||||
uuidWrapper
|
uuidWrapper
|
||||||
|
|
||||||
device Device
|
device *Device
|
||||||
|
|
||||||
service cbgo.Service
|
service cbgo.Service
|
||||||
characteristics []DeviceCharacteristic
|
characteristics map[UUID]DeviceCharacteristic
|
||||||
}
|
}
|
||||||
|
|
||||||
// UUID returns the UUID for this DeviceService.
|
// UUID returns the UUID for this DeviceService.
|
||||||
func (s DeviceService) UUID() UUID {
|
func (s *DeviceService) UUID() UUID {
|
||||||
return s.uuidWrapper
|
return s.uuidWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,13 +89,13 @@ func (s DeviceService) UUID() UUID {
|
||||||
//
|
//
|
||||||
// Passing a nil slice of UUIDs will return a complete list of
|
// Passing a nil slice of UUIDs will return a complete list of
|
||||||
// characteristics.
|
// characteristics.
|
||||||
func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
|
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
|
||||||
cbuuids := []cbgo.UUID{}
|
cbuuids := []cbgo.UUID{}
|
||||||
|
|
||||||
s.device.prph.DiscoverCharacteristics(cbuuids, s.service)
|
s.device.prph.DiscoverCharacteristics(cbuuids, s.service)
|
||||||
|
|
||||||
// clear cache of characteristics
|
// clear cache of characteristics
|
||||||
s.characteristics = make([]DeviceCharacteristic, 0)
|
s.characteristics = make(map[UUID]DeviceCharacteristic)
|
||||||
|
|
||||||
// wait on channel for characteristic discovery
|
// wait on channel for characteristic discovery
|
||||||
select {
|
select {
|
||||||
|
@ -142,7 +142,7 @@ func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteri
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small helper to create a DeviceCharacteristic object.
|
// Small helper to create a DeviceCharacteristic object.
|
||||||
func (s DeviceService) makeCharacteristic(uuid UUID, dchar cbgo.Characteristic) DeviceCharacteristic {
|
func (s *DeviceService) makeCharacteristic(uuid UUID, dchar cbgo.Characteristic) DeviceCharacteristic {
|
||||||
char := DeviceCharacteristic{
|
char := DeviceCharacteristic{
|
||||||
deviceCharacteristic: &deviceCharacteristic{
|
deviceCharacteristic: &deviceCharacteristic{
|
||||||
uuidWrapper: uuid,
|
uuidWrapper: uuid,
|
||||||
|
@ -150,7 +150,7 @@ func (s DeviceService) makeCharacteristic(uuid UUID, dchar cbgo.Characteristic)
|
||||||
characteristic: dchar,
|
characteristic: dchar,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
s.characteristics = append(s.characteristics, char)
|
s.characteristics[char.uuidWrapper] = char
|
||||||
return char
|
return char
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,40 +163,18 @@ type DeviceCharacteristic struct {
|
||||||
type deviceCharacteristic struct {
|
type deviceCharacteristic struct {
|
||||||
uuidWrapper
|
uuidWrapper
|
||||||
|
|
||||||
service DeviceService
|
service *DeviceService
|
||||||
|
|
||||||
characteristic cbgo.Characteristic
|
characteristic cbgo.Characteristic
|
||||||
callback func(buf []byte)
|
callback func(buf []byte)
|
||||||
readChan chan error
|
readChan chan error
|
||||||
writeChan chan error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UUID returns the UUID for this DeviceCharacteristic.
|
// UUID returns the UUID for this DeviceCharacteristic.
|
||||||
func (c DeviceCharacteristic) UUID() UUID {
|
func (c *DeviceCharacteristic) UUID() UUID {
|
||||||
return c.uuidWrapper
|
return c.uuidWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write replaces the characteristic value with a new value. The
|
|
||||||
// call will return after all data has been written.
|
|
||||||
func (c DeviceCharacteristic) Write(p []byte) (n int, err error) {
|
|
||||||
c.writeChan = make(chan error)
|
|
||||||
c.service.device.prph.WriteCharacteristic(p, c.characteristic, true)
|
|
||||||
|
|
||||||
// wait for result
|
|
||||||
select {
|
|
||||||
case <-time.NewTimer(10 * time.Second).C:
|
|
||||||
err = errors.New("timeout on Write()")
|
|
||||||
case err = <-c.writeChan:
|
|
||||||
}
|
|
||||||
|
|
||||||
c.writeChan = nil
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteWithoutResponse replaces the characteristic value with a new value. The
|
// WriteWithoutResponse replaces the characteristic value with a new value. The
|
||||||
// call will return before all data has been written. A limited number of such
|
// call will return before all data has been written. A limited number of such
|
||||||
// writes can be in flight at any given time. This call is also known as a
|
// writes can be in flight at any given time. This call is also known as a
|
||||||
|
|
330
gattc_hci.go
330
gattc_hci.go
|
@ -1,330 +0,0 @@
|
||||||
//go:build hci || ninafw || cyw43439
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
errNotYetImplemented = errors.New("bluetooth: not yet implemented")
|
|
||||||
errNoWrite = errors.New("bluetooth: write not permitted")
|
|
||||||
errNoWriteWithoutResponse = errors.New("bluetooth: write without response not permitted")
|
|
||||||
errWriteFailed = errors.New("bluetooth: write failed")
|
|
||||||
errNoRead = errors.New("bluetooth: read not permitted")
|
|
||||||
errReadFailed = errors.New("bluetooth: read failed")
|
|
||||||
errNoNotify = errors.New("bluetooth: notify/indicate not permitted")
|
|
||||||
errEnableNotificationsFailed = errors.New("bluetooth: enable notifications failed")
|
|
||||||
errServiceNotFound = errors.New("bluetooth: service not found")
|
|
||||||
errCharacteristicNotFound = errors.New("bluetooth: characteristic not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxDefaultServicesToDiscover = 8
|
|
||||||
maxDefaultCharacteristicsToDiscover = 16
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
charPropertyBroadcast = 0x01
|
|
||||||
charPropertyRead = 0x02
|
|
||||||
charPropertyWriteWithoutResponse = 0x04
|
|
||||||
charPropertyWrite = 0x08
|
|
||||||
charPropertyNotify = 0x10
|
|
||||||
charPropertyIndicate = 0x20
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeviceService is a BLE service on a connected peripheral device.
|
|
||||||
type DeviceService struct {
|
|
||||||
uuid UUID
|
|
||||||
|
|
||||||
device Device
|
|
||||||
startHandle, endHandle uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// UUID returns the UUID for this DeviceService.
|
|
||||||
func (s DeviceService) UUID() UUID {
|
|
||||||
return s.uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscoverServices starts a service discovery procedure. Pass a list of service
|
|
||||||
// UUIDs you are interested in to this function. Either a slice of all services
|
|
||||||
// is returned (of the same length as the requested UUIDs and in the same
|
|
||||||
// order), or if some services could not be discovered an error is returned.
|
|
||||||
//
|
|
||||||
// Passing a nil slice of UUIDs will return a complete list of
|
|
||||||
// services.
|
|
||||||
func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
|
||||||
if debug {
|
|
||||||
println("DiscoverServices")
|
|
||||||
}
|
|
||||||
|
|
||||||
services := make([]DeviceService, 0, maxDefaultServicesToDiscover)
|
|
||||||
foundServices := make(map[UUID]DeviceService)
|
|
||||||
|
|
||||||
cd, err := d.adapter.att.findConnectionData(d.handle)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
startHandle := uint16(0x0001)
|
|
||||||
endHandle := uint16(0xffff)
|
|
||||||
for endHandle == uint16(0xffff) {
|
|
||||||
err := d.adapter.att.readByGroupReq(d.handle, startHandle, endHandle, gattServiceUUID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println("found services", len(cd.services))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cd.services) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rawService := range cd.services {
|
|
||||||
if len(uuids) == 0 || rawService.uuid.isIn(uuids) {
|
|
||||||
foundServices[rawService.uuid] =
|
|
||||||
DeviceService{
|
|
||||||
device: d,
|
|
||||||
uuid: rawService.uuid,
|
|
||||||
startHandle: rawService.startHandle,
|
|
||||||
endHandle: rawService.endHandle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startHandle = rawService.endHandle + 1
|
|
||||||
if startHandle == 0x0000 {
|
|
||||||
endHandle = 0x0000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset raw services
|
|
||||||
cd.services = []rawService{}
|
|
||||||
|
|
||||||
// did we find them all?
|
|
||||||
if len(foundServices) == len(uuids) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(uuids) > 0:
|
|
||||||
// put into correct order
|
|
||||||
for _, uuid := range uuids {
|
|
||||||
s, ok := foundServices[uuid]
|
|
||||||
if !ok {
|
|
||||||
return nil, errServiceNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
services = append(services, s)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
for _, s := range foundServices {
|
|
||||||
services = append(services, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return services, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeviceCharacteristic is a BLE characteristic on a connected peripheral
|
|
||||||
// device.
|
|
||||||
type DeviceCharacteristic struct {
|
|
||||||
uuid UUID
|
|
||||||
|
|
||||||
service *DeviceService
|
|
||||||
permissions CharacteristicPermissions
|
|
||||||
handle uint16
|
|
||||||
properties uint8
|
|
||||||
callback func(buf []byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UUID returns the UUID for this DeviceCharacteristic.
|
|
||||||
func (c DeviceCharacteristic) UUID() UUID {
|
|
||||||
return c.uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscoverCharacteristics discovers characteristics in this service. Pass a
|
|
||||||
// list of characteristic UUIDs you are interested in to this function. Either a
|
|
||||||
// list of all requested services is returned, or if some services could not be
|
|
||||||
// discovered an error is returned. If there is no error, the characteristics
|
|
||||||
// slice has the same length as the UUID slice with characteristics in the same
|
|
||||||
// order in the slice as in the requested UUID list.
|
|
||||||
//
|
|
||||||
// Passing a nil slice of UUIDs will return a complete
|
|
||||||
// list of characteristics.
|
|
||||||
func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
|
|
||||||
if debug {
|
|
||||||
println("DiscoverCharacteristics")
|
|
||||||
}
|
|
||||||
|
|
||||||
characteristics := make([]DeviceCharacteristic, 0, maxDefaultCharacteristicsToDiscover)
|
|
||||||
foundCharacteristics := make(map[UUID]DeviceCharacteristic)
|
|
||||||
|
|
||||||
cd, err := s.device.adapter.att.findConnectionData(s.device.handle)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
startHandle := s.startHandle
|
|
||||||
endHandle := s.endHandle
|
|
||||||
for startHandle < endHandle {
|
|
||||||
err := s.device.adapter.att.readByTypeReq(s.device.handle, startHandle, endHandle, gattCharacteristicUUID)
|
|
||||||
switch {
|
|
||||||
case err == ErrATTOp:
|
|
||||||
opcode, _, errcode := s.device.adapter.att.lastError(s.device.handle)
|
|
||||||
if opcode == attOpReadByTypeReq && errcode == attErrorAttrNotFound {
|
|
||||||
// no characteristics found
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case err != nil:
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println("found characteristics", len(cd.characteristics))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cd.characteristics) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rawCharacteristic := range cd.characteristics {
|
|
||||||
if len(uuids) == 0 || rawCharacteristic.uuid.isIn(uuids) {
|
|
||||||
dc := DeviceCharacteristic{
|
|
||||||
service: &s,
|
|
||||||
uuid: rawCharacteristic.uuid,
|
|
||||||
handle: rawCharacteristic.valueHandle,
|
|
||||||
properties: rawCharacteristic.properties,
|
|
||||||
permissions: CharacteristicPermissions(rawCharacteristic.properties),
|
|
||||||
}
|
|
||||||
|
|
||||||
foundCharacteristics[rawCharacteristic.uuid] = dc
|
|
||||||
}
|
|
||||||
|
|
||||||
startHandle = rawCharacteristic.valueHandle + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset raw characteristics
|
|
||||||
cd.characteristics = []rawCharacteristic{}
|
|
||||||
|
|
||||||
// did we find them all?
|
|
||||||
if len(foundCharacteristics) == len(uuids) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(uuids) > 0:
|
|
||||||
// put into correct order
|
|
||||||
for _, uuid := range uuids {
|
|
||||||
c, ok := foundCharacteristics[uuid]
|
|
||||||
if !ok {
|
|
||||||
return nil, errCharacteristicNotFound
|
|
||||||
}
|
|
||||||
characteristics = append(characteristics, c)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
for _, c := range foundCharacteristics {
|
|
||||||
characteristics = append(characteristics, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return characteristics, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteWithoutResponse replaces the characteristic value with a new value. The
|
|
||||||
// call will return before all data has been written. A limited number of such
|
|
||||||
// writes can be in flight at any given time. This call is also known as a
|
|
||||||
// "write command" (as opposed to a write request).
|
|
||||||
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
|
|
||||||
if !c.permissions.WriteWithoutResponse() {
|
|
||||||
return 0, errNoWriteWithoutResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.service.device.adapter.att.writeCmd(c.service.device.handle, c.handle, p)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableNotifications enables notifications in the Client Characteristic
|
|
||||||
// Configuration Descriptor (CCCD). This means that most peripherals will send a
|
|
||||||
// notification with a new value every time the value of the characteristic
|
|
||||||
// changes.
|
|
||||||
//
|
|
||||||
// Users may call EnableNotifications with a nil callback to disable notifications.
|
|
||||||
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
|
|
||||||
if !c.permissions.Notify() {
|
|
||||||
return errNoNotify
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case callback == nil:
|
|
||||||
// disable notifications
|
|
||||||
if debug {
|
|
||||||
println("disabling notifications")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.service.device.adapter.att.writeReq(c.service.device.handle, c.handle+1, []byte{0x00, 0x00})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// enable notifications
|
|
||||||
if debug {
|
|
||||||
println("enabling notifications")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.service.device.adapter.att.writeReq(c.service.device.handle, c.handle+1, []byte{0x01, 0x00})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.callback = callback
|
|
||||||
|
|
||||||
c.service.device.startNotifications()
|
|
||||||
c.service.device.addNotificationRegistration(c.handle, c.callback)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMTU returns the MTU for the characteristic.
|
|
||||||
func (c DeviceCharacteristic) GetMTU() (uint16, error) {
|
|
||||||
err := c.service.device.adapter.att.mtuReq(c.service.device.handle)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.service.device.mtu = c.service.device.adapter.att.mtu
|
|
||||||
|
|
||||||
return c.service.device.mtu, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads the current characteristic value.
|
|
||||||
func (c DeviceCharacteristic) Read(data []byte) (int, error) {
|
|
||||||
if !c.permissions.Read() {
|
|
||||||
return 0, errNoRead
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.service.device.adapter.att.readReq(c.service.device.handle, c.handle)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cd, err := c.service.device.adapter.att.findConnectionData(c.service.device.handle)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cd.value) == 0 {
|
|
||||||
return 0, errReadFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(data, cd.value)
|
|
||||||
|
|
||||||
return len(cd.value), nil
|
|
||||||
}
|
|
143
gattc_linux.go
143
gattc_linux.go
|
@ -1,4 +1,5 @@
|
||||||
//go:build !baremetal
|
//go:build !baremetal
|
||||||
|
// +build !baremetal
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
@ -9,6 +10,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
|
"github.com/muka/go-bluetooth/bluez"
|
||||||
|
"github.com/muka/go-bluetooth/bluez/profile/gatt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -22,12 +25,12 @@ type uuidWrapper = UUID
|
||||||
// DeviceService is a BLE service on a connected peripheral device.
|
// DeviceService is a BLE service on a connected peripheral device.
|
||||||
type DeviceService struct {
|
type DeviceService struct {
|
||||||
uuidWrapper
|
uuidWrapper
|
||||||
adapter *Adapter
|
|
||||||
servicePath string
|
service *gatt.GattService1
|
||||||
}
|
}
|
||||||
|
|
||||||
// UUID returns the UUID for this DeviceService.
|
// UUID returns the UUID for this DeviceService.
|
||||||
func (s DeviceService) UUID() UUID {
|
func (s *DeviceService) UUID() UUID {
|
||||||
return s.uuidWrapper
|
return s.uuidWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,20 +44,18 @@ func (s DeviceService) UUID() UUID {
|
||||||
//
|
//
|
||||||
// On Linux with BlueZ, this just waits for the ServicesResolved signal (if
|
// On Linux with BlueZ, this just waits for the ServicesResolved signal (if
|
||||||
// services haven't been resolved yet) and uses this list of cached services.
|
// services haven't been resolved yet) and uses this list of cached services.
|
||||||
func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
resolved, err := d.device.GetProperty("org.bluez.Device1.ServicesResolved")
|
resolved, err := d.device.GetServicesResolved()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resolved.Value().(bool) {
|
if resolved {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// This is a terrible hack, but I couldn't find another way.
|
// This is a terrible hack, but I couldn't find another way.
|
||||||
// TODO: actually there is, by waiting for a property change event of
|
|
||||||
// ServicesResolved.
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
if time.Since(start) > 10*time.Second {
|
if time.Since(start) > 10*time.Second {
|
||||||
return nil, errors.New("timeout on DiscoverServices")
|
return nil, errors.New("timeout on DiscoverServices")
|
||||||
|
@ -62,13 +63,16 @@ func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
services := []DeviceService{}
|
services := []DeviceService{}
|
||||||
uuidServices := make(map[UUID]struct{})
|
uuidServices := make(map[string]string)
|
||||||
servicesFound := 0
|
servicesFound := 0
|
||||||
|
|
||||||
// Iterate through all objects managed by BlueZ, hoping to find the services
|
// Iterate through all objects managed by BlueZ, hoping to find the services
|
||||||
// we're looking for.
|
// we're looking for.
|
||||||
var list map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
om, err := bluez.GetObjectManager()
|
||||||
err := d.adapter.bluez.Call("org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&list)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list, err := om.GetManagedObjects()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -81,17 +85,19 @@ func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
if !strings.HasPrefix(objectPath, string(d.device.Path())+"/service") {
|
if !strings.HasPrefix(objectPath, string(d.device.Path())+"/service") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
properties, ok := list[dbus.ObjectPath(objectPath)]["org.bluez.GattService1"]
|
suffix := objectPath[len(d.device.Path()+"/"):]
|
||||||
if !ok {
|
if len(strings.Split(suffix, "/")) != 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
service, err := gatt.NewGattService1(dbus.ObjectPath(objectPath))
|
||||||
serviceUUID, _ := ParseUUID(properties["UUID"].Value().(string))
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if len(uuids) > 0 {
|
if len(uuids) > 0 {
|
||||||
found := false
|
found := false
|
||||||
for _, uuid := range uuids {
|
for _, uuid := range uuids {
|
||||||
if uuid == serviceUUID {
|
if service.Properties.UUID == uuid.String() {
|
||||||
// One of the services we're looking for.
|
// One of the services we're looking for.
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
|
@ -102,21 +108,20 @@ func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := uuidServices[serviceUUID]; ok {
|
if _, ok := uuidServices[service.Properties.UUID]; ok {
|
||||||
// There is more than one service with the same UUID?
|
// There is more than one service with the same UUID?
|
||||||
// Don't overwrite it, to keep the servicesFound count correct.
|
// Don't overwrite it, to keep the servicesFound count correct.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ds := DeviceService{
|
uuid, _ := ParseUUID(service.Properties.UUID)
|
||||||
uuidWrapper: serviceUUID,
|
ds := DeviceService{uuidWrapper: uuid,
|
||||||
adapter: d.adapter,
|
service: service,
|
||||||
servicePath: objectPath,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
services = append(services, ds)
|
services = append(services, ds)
|
||||||
servicesFound++
|
servicesFound++
|
||||||
uuidServices[serviceUUID] = struct{}{}
|
uuidServices[service.Properties.UUID] = service.Properties.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
if servicesFound < len(uuids) {
|
if servicesFound < len(uuids) {
|
||||||
|
@ -130,14 +135,13 @@ func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
// device.
|
// device.
|
||||||
type DeviceCharacteristic struct {
|
type DeviceCharacteristic struct {
|
||||||
uuidWrapper
|
uuidWrapper
|
||||||
adapter *Adapter
|
|
||||||
characteristic dbus.BusObject
|
characteristic *gatt.GattCharacteristic1
|
||||||
property chan *dbus.Signal // channel where notifications are reported
|
property chan *bluez.PropertyChanged // channel where notifications are reported
|
||||||
propertiesChangedMatchOption dbus.MatchOption // the same value must be passed to RemoveMatchSignal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UUID returns the UUID for this DeviceCharacteristic.
|
// UUID returns the UUID for this DeviceCharacteristic.
|
||||||
func (c DeviceCharacteristic) UUID() UUID {
|
func (c *DeviceCharacteristic) UUID() UUID {
|
||||||
return c.uuidWrapper
|
return c.uuidWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +154,7 @@ func (c DeviceCharacteristic) UUID() UUID {
|
||||||
//
|
//
|
||||||
// Passing a nil slice of UUIDs will return a complete
|
// Passing a nil slice of UUIDs will return a complete
|
||||||
// list of characteristics.
|
// list of characteristics.
|
||||||
func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
|
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
|
||||||
var chars []DeviceCharacteristic
|
var chars []DeviceCharacteristic
|
||||||
if len(uuids) > 0 {
|
if len(uuids) > 0 {
|
||||||
// The caller wants to get a list of characteristics in a specific
|
// The caller wants to get a list of characteristics in a specific
|
||||||
|
@ -160,8 +164,11 @@ func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteri
|
||||||
|
|
||||||
// Iterate through all objects managed by BlueZ, hoping to find the
|
// Iterate through all objects managed by BlueZ, hoping to find the
|
||||||
// characteristic we're looking for.
|
// characteristic we're looking for.
|
||||||
var list map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
om, err := bluez.GetObjectManager()
|
||||||
err := s.adapter.bluez.Call("org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&list)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list, err := om.GetManagedObjects()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -171,18 +178,21 @@ func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteri
|
||||||
}
|
}
|
||||||
sort.Strings(objects)
|
sort.Strings(objects)
|
||||||
for _, objectPath := range objects {
|
for _, objectPath := range objects {
|
||||||
if !strings.HasPrefix(objectPath, s.servicePath+"/char") {
|
if !strings.HasPrefix(objectPath, string(s.service.Path())+"/char") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
properties, ok := list[dbus.ObjectPath(objectPath)]["org.bluez.GattCharacteristic1"]
|
suffix := objectPath[len(s.service.Path()+"/"):]
|
||||||
if !ok {
|
if len(strings.Split(suffix, "/")) != 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cuuid, _ := ParseUUID(properties["UUID"].Value().(string))
|
characteristic, err := gatt.NewGattCharacteristic1(dbus.ObjectPath(objectPath))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cuuid, _ := ParseUUID(characteristic.Properties.UUID)
|
||||||
char := DeviceCharacteristic{
|
char := DeviceCharacteristic{
|
||||||
uuidWrapper: cuuid,
|
uuidWrapper: cuuid,
|
||||||
adapter: s.adapter,
|
characteristic: characteristic,
|
||||||
characteristic: s.adapter.bus.Object("org.bluez", dbus.ObjectPath(objectPath)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(uuids) > 0 {
|
if len(uuids) > 0 {
|
||||||
|
@ -222,7 +232,7 @@ func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteri
|
||||||
// writes can be in flight at any given time. This call is also known as a
|
// writes can be in flight at any given time. This call is also known as a
|
||||||
// "write command" (as opposed to a write request).
|
// "write command" (as opposed to a write request).
|
||||||
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
|
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
|
||||||
err = c.characteristic.Call("org.bluez.GattCharacteristic1.WriteValue", 0, p, map[string]dbus.Variant(nil)).Err
|
err = c.characteristic.WriteValue(p, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -235,38 +245,32 @@ func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error)
|
||||||
// changes.
|
// changes.
|
||||||
//
|
//
|
||||||
// Users may call EnableNotifications with a nil callback to disable notifications.
|
// Users may call EnableNotifications with a nil callback to disable notifications.
|
||||||
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
|
func (c *DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
|
||||||
switch callback {
|
switch callback {
|
||||||
default:
|
default:
|
||||||
if c.property != nil {
|
if c.property != nil {
|
||||||
return errDupNotif
|
return errDupNotif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start watching for changes in the Value property.
|
ch, err := c.characteristic.WatchProperties()
|
||||||
c.property = make(chan *dbus.Signal)
|
|
||||||
c.adapter.bus.Signal(c.property)
|
|
||||||
c.propertiesChangedMatchOption = dbus.WithMatchInterface("org.freedesktop.DBus.Properties")
|
|
||||||
c.adapter.bus.AddMatchSignal(c.propertiesChangedMatchOption)
|
|
||||||
|
|
||||||
err := c.characteristic.Call("org.bluez.GattCharacteristic1.StartNotify", 0).Err
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = c.characteristic.StartNotify()
|
||||||
|
if err != nil {
|
||||||
|
_ = c.characteristic.UnwatchProperties(ch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.property = ch
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for sig := range c.property {
|
for update := range ch {
|
||||||
if sig.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" {
|
if update == nil {
|
||||||
interfaceName := sig.Body[0].(string)
|
continue
|
||||||
if interfaceName != "org.bluez.GattCharacteristic1" {
|
}
|
||||||
continue
|
if update.Interface == "org.bluez.GattCharacteristic1" && update.Name == "Value" {
|
||||||
}
|
callback(update.Value.([]byte))
|
||||||
if sig.Path != c.characteristic.Path() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
changes := sig.Body[1].(map[string]dbus.Variant)
|
|
||||||
if value, ok := changes["Value"].Value().([]byte); ok {
|
|
||||||
callback(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -278,16 +282,26 @@ func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.adapter.bus.RemoveMatchSignal(c.propertiesChangedMatchOption)
|
e1 := c.characteristic.StopNotify()
|
||||||
c.adapter.bus.RemoveSignal(c.property)
|
e2 := c.characteristic.UnwatchProperties(c.property)
|
||||||
c.property = nil
|
c.property = nil
|
||||||
return err
|
|
||||||
|
// FIXME(sbinet): use errors.Join(e1, e2)
|
||||||
|
if e1 != nil {
|
||||||
|
return e1
|
||||||
|
}
|
||||||
|
|
||||||
|
if e2 != nil {
|
||||||
|
return e2
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMTU returns the MTU for the characteristic.
|
// GetMTU returns the MTU for the characteristic.
|
||||||
func (c DeviceCharacteristic) GetMTU() (uint16, error) {
|
func (c DeviceCharacteristic) GetMTU() (uint16, error) {
|
||||||
mtu, err := c.characteristic.GetProperty("org.bluez.GattCharacteristic1.MTU")
|
mtu, err := c.characteristic.GetProperty("MTU")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return uint16(0), err
|
return uint16(0), err
|
||||||
}
|
}
|
||||||
|
@ -295,10 +309,9 @@ func (c DeviceCharacteristic) GetMTU() (uint16, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read reads the current characteristic value.
|
// Read reads the current characteristic value.
|
||||||
func (c DeviceCharacteristic) Read(data []byte) (int, error) {
|
func (c *DeviceCharacteristic) Read(data []byte) (int, error) {
|
||||||
options := make(map[string]interface{})
|
options := make(map[string]interface{})
|
||||||
var result []byte
|
result, err := c.characteristic.ReadValue(options)
|
||||||
err := c.characteristic.Call("org.bluez.GattCharacteristic1.ReadValue", 0, options).Store(&result)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
54
gattc_sd.go
54
gattc_sd.go
|
@ -1,4 +1,5 @@
|
||||||
//go:build (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
|
//go:build (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
|
||||||
|
// +build softdevice,s132v6 softdevice,s140v6 softdevice,s140v7
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
@ -11,7 +12,6 @@ import (
|
||||||
"device/arm"
|
"device/arm"
|
||||||
"errors"
|
"errors"
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -29,8 +29,8 @@ var (
|
||||||
// program and the event handler.
|
// program and the event handler.
|
||||||
var discoveringService struct {
|
var discoveringService struct {
|
||||||
state volatile.Register8 // 0 means nothing happening, 1 means in progress, 2 means found something
|
state volatile.Register8 // 0 means nothing happening, 1 means in progress, 2 means found something
|
||||||
startHandle volatileHandle
|
startHandle volatile.Register16
|
||||||
endHandle volatileHandle
|
endHandle volatile.Register16
|
||||||
uuid C.ble_uuid_t
|
uuid C.ble_uuid_t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,13 +39,13 @@ var discoveringService struct {
|
||||||
type DeviceService struct {
|
type DeviceService struct {
|
||||||
uuid shortUUID
|
uuid shortUUID
|
||||||
|
|
||||||
connectionHandle C.uint16_t
|
connectionHandle uint16
|
||||||
startHandle C.uint16_t
|
startHandle uint16
|
||||||
endHandle C.uint16_t
|
endHandle uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// UUID returns the UUID for this DeviceService.
|
// UUID returns the UUID for this DeviceService.
|
||||||
func (s DeviceService) UUID() UUID {
|
func (s *DeviceService) UUID() UUID {
|
||||||
return s.uuid.UUID()
|
return s.uuid.UUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ func (s DeviceService) UUID() UUID {
|
||||||
//
|
//
|
||||||
// On the Nordic SoftDevice, only one service discovery procedure may be done at
|
// On the Nordic SoftDevice, only one service discovery procedure may be done at
|
||||||
// a time.
|
// a time.
|
||||||
func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
if discoveringService.state.Get() != 0 {
|
if discoveringService.state.Get() != 0 {
|
||||||
// Not concurrency safe, but should catch most concurrency misuses.
|
// Not concurrency safe, but should catch most concurrency misuses.
|
||||||
return nil, errAlreadyDiscovering
|
return nil, errAlreadyDiscovering
|
||||||
|
@ -77,7 +77,7 @@ func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
if len(uuids) > 0 {
|
if len(uuids) > 0 {
|
||||||
shortUUIDs = make([]C.ble_uuid_t, sz)
|
shortUUIDs = make([]C.ble_uuid_t, sz)
|
||||||
for i, uuid := range uuids {
|
for i, uuid := range uuids {
|
||||||
var errCode C.uint32_t
|
var errCode uint32
|
||||||
shortUUIDs[i], errCode = uuid.shortUUID()
|
shortUUIDs[i], errCode = uuid.shortUUID()
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return nil, Error(errCode)
|
return nil, Error(errCode)
|
||||||
|
@ -87,7 +87,7 @@ func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
|
|
||||||
numFound := 0
|
numFound := 0
|
||||||
|
|
||||||
var startHandle C.uint16_t = 1
|
var startHandle uint16 = 1
|
||||||
|
|
||||||
for i := 0; i < sz; i++ {
|
for i := 0; i < sz; i++ {
|
||||||
var suuid C.ble_uuid_t
|
var suuid C.ble_uuid_t
|
||||||
|
@ -97,7 +97,7 @@ func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
|
|
||||||
// Start discovery of this service.
|
// Start discovery of this service.
|
||||||
discoveringService.state.Set(1)
|
discoveringService.state.Set(1)
|
||||||
var errCode C.uint32_t
|
var errCode uint32
|
||||||
if len(uuids) > 0 {
|
if len(uuids) > 0 {
|
||||||
errCode = C.sd_ble_gattc_primary_services_discover(d.connectionHandle, startHandle, &suuid)
|
errCode = C.sd_ble_gattc_primary_services_discover(d.connectionHandle, startHandle, &suuid)
|
||||||
} else {
|
} else {
|
||||||
|
@ -160,14 +160,14 @@ func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
type DeviceCharacteristic struct {
|
type DeviceCharacteristic struct {
|
||||||
uuid shortUUID
|
uuid shortUUID
|
||||||
|
|
||||||
connectionHandle C.uint16_t
|
connectionHandle uint16
|
||||||
valueHandle C.uint16_t
|
valueHandle uint16
|
||||||
cccdHandle C.uint16_t
|
cccdHandle uint16
|
||||||
permissions CharacteristicPermissions
|
permissions CharacteristicPermissions
|
||||||
}
|
}
|
||||||
|
|
||||||
// UUID returns the UUID for this DeviceCharacteristic.
|
// UUID returns the UUID for this DeviceCharacteristic.
|
||||||
func (c DeviceCharacteristic) UUID() UUID {
|
func (c *DeviceCharacteristic) UUID() UUID {
|
||||||
return c.uuid.UUID()
|
return c.uuid.UUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ func (c DeviceCharacteristic) UUID() UUID {
|
||||||
var discoveringCharacteristic struct {
|
var discoveringCharacteristic struct {
|
||||||
uuid C.ble_uuid_t
|
uuid C.ble_uuid_t
|
||||||
char_props C.ble_gatt_char_props_t
|
char_props C.ble_gatt_char_props_t
|
||||||
handle_value volatileHandle
|
handle_value volatile.Register16
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscoverCharacteristics discovers characteristics in this service. Pass a
|
// DiscoverCharacteristics discovers characteristics in this service. Pass a
|
||||||
|
@ -188,7 +188,7 @@ var discoveringCharacteristic struct {
|
||||||
//
|
//
|
||||||
// Passing a nil slice of UUIDs will return a complete
|
// Passing a nil slice of UUIDs will return a complete
|
||||||
// list of characteristics.
|
// list of characteristics.
|
||||||
func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
|
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
|
||||||
if discoveringCharacteristic.handle_value.Get() != 0 {
|
if discoveringCharacteristic.handle_value.Get() != 0 {
|
||||||
return nil, errAlreadyDiscovering
|
return nil, errAlreadyDiscovering
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteri
|
||||||
if len(uuids) > 0 {
|
if len(uuids) > 0 {
|
||||||
shortUUIDs = make([]C.ble_uuid_t, sz)
|
shortUUIDs = make([]C.ble_uuid_t, sz)
|
||||||
for i, uuid := range uuids {
|
for i, uuid := range uuids {
|
||||||
var errCode C.uint32_t
|
var errCode uint32
|
||||||
shortUUIDs[i], errCode = uuid.shortUUID()
|
shortUUIDs[i], errCode = uuid.shortUUID()
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return nil, Error(errCode)
|
return nil, Error(errCode)
|
||||||
|
@ -325,8 +325,8 @@ func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error)
|
||||||
write_op: C.BLE_GATT_OP_WRITE_CMD,
|
write_op: C.BLE_GATT_OP_WRITE_CMD,
|
||||||
handle: c.valueHandle,
|
handle: c.valueHandle,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
len: C.uint16_t(len(p)),
|
len: uint16(len(p)),
|
||||||
p_value: (*C.uint8_t)(unsafe.Pointer(&p[0])),
|
p_value: &p[0],
|
||||||
})
|
})
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return 0, Error(errCode)
|
return 0, Error(errCode)
|
||||||
|
@ -335,8 +335,8 @@ func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type gattcNotificationCallback struct {
|
type gattcNotificationCallback struct {
|
||||||
connectionHandle C.uint16_t
|
connectionHandle uint16
|
||||||
valueHandle C.uint16_t // may be 0 if the slot is empty
|
valueHandle uint16 // may be 0 if the slot is empty
|
||||||
callback func([]byte)
|
callback func([]byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +401,7 @@ func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to the CCCD to enable notifications. Don't wait for a response.
|
// Write to the CCCD to enable notifications. Don't wait for a response.
|
||||||
value := [2]C.uint8_t{0x01, 0x00} // 0x0001 enables notifications (and disables indications)
|
value := [2]byte{0x01, 0x00} // 0x0001 enables notifications (and disables indications)
|
||||||
errCode := C.sd_ble_gattc_write(c.connectionHandle, &C.ble_gattc_write_params_t{
|
errCode := C.sd_ble_gattc_write(c.connectionHandle, &C.ble_gattc_write_params_t{
|
||||||
write_op: C.BLE_GATT_OP_WRITE_CMD,
|
write_op: C.BLE_GATT_OP_WRITE_CMD,
|
||||||
handle: c.cccdHandle,
|
handle: c.cccdHandle,
|
||||||
|
@ -415,16 +415,16 @@ func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) err
|
||||||
// A global used to pass information from the event handler back to the
|
// A global used to pass information from the event handler back to the
|
||||||
// Read function below.
|
// Read function below.
|
||||||
var readingCharacteristic struct {
|
var readingCharacteristic struct {
|
||||||
handle_value volatileHandle
|
handle_value volatile.Register16
|
||||||
offset C.uint16_t
|
offset uint16
|
||||||
length C.uint16_t
|
length uint16
|
||||||
value []byte
|
value []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read reads the current characteristic value up to MTU length.
|
// Read reads the current characteristic value up to MTU length.
|
||||||
// A future enhancement would be to be able to retrieve a longer
|
// A future enhancement would be to be able to retrieve a longer
|
||||||
// value by making multiple calls.
|
// value by making multiple calls.
|
||||||
func (c DeviceCharacteristic) Read(data []byte) (n int, err error) {
|
func (c *DeviceCharacteristic) Read(data []byte) (n int, err error) {
|
||||||
// global will copy bytes from read operation into data slice
|
// global will copy bytes from read operation into data slice
|
||||||
readingCharacteristic.value = data
|
readingCharacteristic.value = data
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ var (
|
||||||
//
|
//
|
||||||
// Passing a nil slice of UUIDs will return a complete list of
|
// Passing a nil slice of UUIDs will return a complete list of
|
||||||
// services.
|
// services.
|
||||||
func (d Device) DiscoverServices(filterUUIDs []UUID) ([]DeviceService, error) {
|
func (d *Device) DiscoverServices(filterUUIDs []UUID) ([]DeviceService, error) {
|
||||||
// IAsyncOperation<GattDeviceServicesResult>
|
// IAsyncOperation<GattDeviceServicesResult>
|
||||||
getServicesOperation, err := d.device.GetGattServicesWithCacheModeAsync(bluetooth.BluetoothCacheModeUncached)
|
getServicesOperation, err := d.device.GetGattServicesWithCacheModeAsync(bluetooth.BluetoothCacheModeUncached)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -133,11 +133,11 @@ type DeviceService struct {
|
||||||
uuidWrapper
|
uuidWrapper
|
||||||
|
|
||||||
service *genericattributeprofile.GattDeviceService
|
service *genericattributeprofile.GattDeviceService
|
||||||
device Device
|
device *Device
|
||||||
}
|
}
|
||||||
|
|
||||||
// UUID returns the UUID for this DeviceService.
|
// UUID returns the UUID for this DeviceService.
|
||||||
func (s DeviceService) UUID() UUID {
|
func (s *DeviceService) UUID() UUID {
|
||||||
return s.uuidWrapper
|
return s.uuidWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ func (s DeviceService) UUID() UUID {
|
||||||
//
|
//
|
||||||
// Passing a nil slice of UUIDs will return a complete
|
// Passing a nil slice of UUIDs will return a complete
|
||||||
// list of characteristics.
|
// list of characteristics.
|
||||||
func (s DeviceService) DiscoverCharacteristics(filterUUIDs []UUID) ([]DeviceCharacteristic, error) {
|
func (s *DeviceService) DiscoverCharacteristics(filterUUIDs []UUID) ([]DeviceCharacteristic, error) {
|
||||||
getCharacteristicsOp, err := s.service.GetCharacteristicsWithCacheModeAsync(bluetooth.BluetoothCacheModeUncached)
|
getCharacteristicsOp, err := s.service.GetCharacteristicsWithCacheModeAsync(bluetooth.BluetoothCacheModeUncached)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -234,20 +234,20 @@ type DeviceCharacteristic struct {
|
||||||
characteristic *genericattributeprofile.GattCharacteristic
|
characteristic *genericattributeprofile.GattCharacteristic
|
||||||
properties genericattributeprofile.GattCharacteristicProperties
|
properties genericattributeprofile.GattCharacteristicProperties
|
||||||
|
|
||||||
service DeviceService
|
service *DeviceService
|
||||||
}
|
}
|
||||||
|
|
||||||
// UUID returns the UUID for this DeviceCharacteristic.
|
// UUID returns the UUID for this DeviceCharacteristic.
|
||||||
func (c DeviceCharacteristic) UUID() UUID {
|
func (c *DeviceCharacteristic) UUID() UUID {
|
||||||
return c.uuidWrapper
|
return c.uuidWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c DeviceCharacteristic) Properties() uint32 {
|
func (c *DeviceCharacteristic) Properties() uint32 {
|
||||||
return uint32(c.properties)
|
return uint32(c.properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMTU returns the MTU for the characteristic.
|
// GetMTU returns the MTU for the characteristic.
|
||||||
func (c DeviceCharacteristic) GetMTU() (uint16, error) {
|
func (c *DeviceCharacteristic) GetMTU() (uint16, error) {
|
||||||
return c.service.device.session.GetMaxPduSize()
|
return c.service.device.session.GetMaxPduSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +314,7 @@ func (c DeviceCharacteristic) write(p []byte, mode genericattributeprofile.GattW
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read reads the current characteristic value.
|
// Read reads the current characteristic value.
|
||||||
func (c DeviceCharacteristic) Read(data []byte) (int, error) {
|
func (c *DeviceCharacteristic) Read(data []byte) (int, error) {
|
||||||
if c.properties&genericattributeprofile.GattCharacteristicPropertiesRead == 0 {
|
if c.properties&genericattributeprofile.GattCharacteristicPropertiesRead == 0 {
|
||||||
return 0, errNoRead
|
return 0, errNoRead
|
||||||
}
|
}
|
||||||
|
@ -341,7 +341,7 @@ func (c DeviceCharacteristic) Read(data []byte) (int, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
datareader, err := streams.DataReaderFromBuffer(buffer)
|
datareader, err := streams.FromBuffer(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -366,7 +366,7 @@ func (c DeviceCharacteristic) Read(data []byte) (int, error) {
|
||||||
// changes.
|
// changes.
|
||||||
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
|
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
|
||||||
if (c.properties&genericattributeprofile.GattCharacteristicPropertiesNotify == 0) &&
|
if (c.properties&genericattributeprofile.GattCharacteristicPropertiesNotify == 0) &&
|
||||||
(c.properties&genericattributeprofile.GattCharacteristicPropertiesIndicate == 0) {
|
(c.properties&genericattributeprofile.GattCharacteristicPropertiesIndicate == 0) {
|
||||||
return errNoNotify
|
return errNoNotify
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,7 +381,7 @@ func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, err := streams.DataReaderFromBuffer(buf)
|
reader, err := streams.FromBuffer(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
14
gatts.go
14
gatts.go
|
@ -7,8 +7,6 @@ type Service struct {
|
||||||
Characteristics []CharacteristicConfig
|
Characteristics []CharacteristicConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type WriteEvent = func(client Connection, offset int, value []byte)
|
|
||||||
|
|
||||||
// CharacteristicConfig contains some parameters for the configuration of a
|
// CharacteristicConfig contains some parameters for the configuration of a
|
||||||
// single characteristic.
|
// single characteristic.
|
||||||
//
|
//
|
||||||
|
@ -19,7 +17,7 @@ type CharacteristicConfig struct {
|
||||||
UUID
|
UUID
|
||||||
Value []byte
|
Value []byte
|
||||||
Flags CharacteristicPermissions
|
Flags CharacteristicPermissions
|
||||||
WriteEvent WriteEvent
|
WriteEvent func(client Connection, offset int, value []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CharacteristicPermissions lists a number of basic permissions/capabilities
|
// CharacteristicPermissions lists a number of basic permissions/capabilities
|
||||||
|
@ -58,13 +56,3 @@ func (p CharacteristicPermissions) Write() bool {
|
||||||
func (p CharacteristicPermissions) WriteWithoutResponse() bool {
|
func (p CharacteristicPermissions) WriteWithoutResponse() bool {
|
||||||
return p&CharacteristicWriteWithoutResponsePermission != 0
|
return p&CharacteristicWriteWithoutResponsePermission != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify returns whether notifications are permitted.
|
|
||||||
func (p CharacteristicPermissions) Notify() bool {
|
|
||||||
return p&CharacteristicNotifyPermission != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indicate returns whether indications are permitted.
|
|
||||||
func (p CharacteristicPermissions) Indicate() bool {
|
|
||||||
return p&CharacteristicIndicatePermission != 0
|
|
||||||
}
|
|
||||||
|
|
128
gatts_hci.go
128
gatts_hci.go
|
@ -1,128 +0,0 @@
|
||||||
//go:build hci || ninafw || cyw43439
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
type Characteristic struct {
|
|
||||||
adapter *Adapter
|
|
||||||
handle uint16
|
|
||||||
permissions CharacteristicPermissions
|
|
||||||
value []byte
|
|
||||||
cccd uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddService creates a new service with the characteristics listed in the
|
|
||||||
// Service struct.
|
|
||||||
func (a *Adapter) AddService(service *Service) error {
|
|
||||||
uuid := service.UUID.Bytes()
|
|
||||||
serviceHandle := a.att.addLocalAttribute(attributeTypeService, 0, shortUUID(gattServiceUUID).UUID(), 0, uuid[:])
|
|
||||||
valueHandle := serviceHandle
|
|
||||||
endHandle := serviceHandle
|
|
||||||
|
|
||||||
for i := range service.Characteristics {
|
|
||||||
data := service.Characteristics[i].UUID.Bytes()
|
|
||||||
cuuid := append([]byte{}, data[:]...)
|
|
||||||
|
|
||||||
// add characteristic declaration
|
|
||||||
charHandle := a.att.addLocalAttribute(attributeTypeCharacteristic, serviceHandle, shortUUID(gattCharacteristicUUID).UUID(), CharacteristicReadPermission, cuuid[:])
|
|
||||||
|
|
||||||
// add characteristic value
|
|
||||||
vf := CharacteristicPermissions(0)
|
|
||||||
if service.Characteristics[i].Flags.Read() {
|
|
||||||
vf |= CharacteristicReadPermission
|
|
||||||
}
|
|
||||||
if service.Characteristics[i].Flags.Write() {
|
|
||||||
vf |= CharacteristicWritePermission
|
|
||||||
}
|
|
||||||
valueHandle = a.att.addLocalAttribute(attributeTypeCharacteristicValue, charHandle, service.Characteristics[i].UUID, vf, service.Characteristics[i].Value)
|
|
||||||
endHandle = valueHandle
|
|
||||||
|
|
||||||
// add characteristic descriptor
|
|
||||||
if service.Characteristics[i].Flags.Notify() ||
|
|
||||||
service.Characteristics[i].Flags.Indicate() {
|
|
||||||
endHandle = a.att.addLocalAttribute(attributeTypeDescriptor, charHandle, shortUUID(gattClientCharacteristicConfigUUID).UUID(), CharacteristicReadPermission|CharacteristicWritePermission, []byte{0, 0})
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.Characteristics[i].Handle == nil {
|
|
||||||
service.Characteristics[i].Handle = &Characteristic{}
|
|
||||||
}
|
|
||||||
|
|
||||||
service.Characteristics[i].Handle.adapter = a
|
|
||||||
service.Characteristics[i].Handle.handle = valueHandle
|
|
||||||
service.Characteristics[i].Handle.permissions = service.Characteristics[i].Flags
|
|
||||||
if len(service.Characteristics[i].Value) > 0 {
|
|
||||||
service.Characteristics[i].Handle.value = service.Characteristics[i].Value
|
|
||||||
}
|
|
||||||
|
|
||||||
if (service.Characteristics[i].Flags.Write() ||
|
|
||||||
service.Characteristics[i].Flags.WriteWithoutResponse()) &&
|
|
||||||
service.Characteristics[i].WriteEvent != nil {
|
|
||||||
handlers := append(a.charWriteHandlers, charWriteHandler{
|
|
||||||
handle: valueHandle,
|
|
||||||
callback: service.Characteristics[i].WriteEvent,
|
|
||||||
})
|
|
||||||
a.charWriteHandlers = handlers
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println("added characteristic", charHandle, valueHandle, service.Characteristics[i].UUID.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
a.att.addLocalCharacteristic(charHandle, service.Characteristics[i].Flags, valueHandle, service.Characteristics[i].UUID, service.Characteristics[i].Handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println("added service", serviceHandle, endHandle, service.UUID.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
a.att.addLocalService(serviceHandle, endHandle, service.UUID)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write replaces the characteristic value with a new value.
|
|
||||||
func (c *Characteristic) Write(p []byte) (n int, err error) {
|
|
||||||
if !(c.permissions.Write() || c.permissions.WriteWithoutResponse() ||
|
|
||||||
c.permissions.Notify() || c.permissions.Indicate()) {
|
|
||||||
return 0, errNoWrite
|
|
||||||
}
|
|
||||||
|
|
||||||
hdl := c.adapter.getCharWriteHandler(c.handle)
|
|
||||||
if hdl != nil {
|
|
||||||
hdl.callback(Connection(c.handle), 0, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(c.value, p)
|
|
||||||
|
|
||||||
if c.cccd&0x01 != 0 {
|
|
||||||
// send notification
|
|
||||||
c.adapter.att.sendNotification(c.handle, c.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(c.value), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Characteristic) readCCCD() (uint16, error) {
|
|
||||||
if !c.permissions.Notify() {
|
|
||||||
return 0, errNoNotify
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.cccd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Characteristic) writeCCCD(val uint16) error {
|
|
||||||
if !c.permissions.Notify() {
|
|
||||||
return errNoNotify
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cccd = val
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Characteristic) readValue() ([]byte, error) {
|
|
||||||
if !c.permissions.Read() {
|
|
||||||
return nil, errNoRead
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.value, nil
|
|
||||||
}
|
|
212
gatts_linux.go
212
gatts_linux.go
|
@ -1,156 +1,97 @@
|
||||||
//go:build !baremetal
|
//go:build !baremetal
|
||||||
|
// +build !baremetal
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/muka/go-bluetooth/api/service"
|
||||||
"strconv"
|
"github.com/muka/go-bluetooth/bluez/profile/gatt"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
"github.com/godbus/dbus/v5/prop"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Unique ID per service (to generate a unique object path).
|
|
||||||
var serviceID uint64
|
|
||||||
|
|
||||||
// Characteristic is a single characteristic in a service. It has an UUID and a
|
// Characteristic is a single characteristic in a service. It has an UUID and a
|
||||||
// value.
|
// value.
|
||||||
type Characteristic struct {
|
type Characteristic struct {
|
||||||
char *bluezChar
|
handle *service.Char
|
||||||
permissions CharacteristicPermissions
|
permissions CharacteristicPermissions
|
||||||
}
|
}
|
||||||
|
|
||||||
// A small ObjectManager for a single service.
|
|
||||||
type objectManager struct {
|
|
||||||
objects map[dbus.ObjectPath]map[string]map[string]*prop.Prop
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method implements org.freedesktop.DBus.ObjectManager.
|
|
||||||
func (om *objectManager) GetManagedObjects() (map[dbus.ObjectPath]map[string]map[string]dbus.Variant, *dbus.Error) {
|
|
||||||
// Convert from a map with *prop.Prop keys, to a map with dbus.Variant keys.
|
|
||||||
objects := map[dbus.ObjectPath]map[string]map[string]dbus.Variant{}
|
|
||||||
for path, object := range om.objects {
|
|
||||||
obj := make(map[string]map[string]dbus.Variant)
|
|
||||||
objects[path] = obj
|
|
||||||
for iface, props := range object {
|
|
||||||
ifaceObj := make(map[string]dbus.Variant)
|
|
||||||
obj[iface] = ifaceObj
|
|
||||||
for k, v := range props {
|
|
||||||
ifaceObj[k] = dbus.MakeVariant(v.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return objects, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object that implements org.bluez.GattCharacteristic1 to be exported over
|
|
||||||
// DBus. Here is the documentation:
|
|
||||||
// https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/org.bluez.GattCharacteristic.rst
|
|
||||||
type bluezChar struct {
|
|
||||||
props *prop.Properties
|
|
||||||
writeEvent func(client Connection, offset int, value []byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *bluezChar) ReadValue(options map[string]dbus.Variant) ([]byte, *dbus.Error) {
|
|
||||||
// TODO: should we use the offset value? The BlueZ documentation doesn't
|
|
||||||
// clearly specify this. The go-bluetooth library doesn't, but I believe it
|
|
||||||
// should be respected.
|
|
||||||
value := c.props.GetMust("org.bluez.GattCharacteristic1", "Value").([]byte)
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *bluezChar) WriteValue(value []byte, options map[string]dbus.Variant) *dbus.Error {
|
|
||||||
if c.writeEvent != nil {
|
|
||||||
// BlueZ doesn't seem to tell who did the write, so pass 0 always as the
|
|
||||||
// connection ID.
|
|
||||||
client := Connection(0)
|
|
||||||
offset, _ := options["offset"].Value().(uint16)
|
|
||||||
c.writeEvent(client, int(offset), value)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddService creates a new service with the characteristics listed in the
|
// AddService creates a new service with the characteristics listed in the
|
||||||
// Service struct.
|
// Service struct.
|
||||||
func (a *Adapter) AddService(s *Service) error {
|
func (a *Adapter) AddService(s *Service) error {
|
||||||
// Create a unique DBus path for this service.
|
app, err := service.NewApp(service.AppOptions{
|
||||||
id := atomic.AddUint64(&serviceID, 1)
|
AdapterID: a.id,
|
||||||
path := dbus.ObjectPath(fmt.Sprintf("/org/tinygo/bluetooth/service%d", id))
|
})
|
||||||
|
|
||||||
// All objects that will be part of the ObjectManager.
|
|
||||||
objects := map[dbus.ObjectPath]map[string]map[string]*prop.Prop{}
|
|
||||||
|
|
||||||
// Define the service to be exported over DBus.
|
|
||||||
serviceSpec := map[string]map[string]*prop.Prop{
|
|
||||||
"org.bluez.GattService1": {
|
|
||||||
"UUID": {Value: s.UUID.String()},
|
|
||||||
"Primary": {Value: true},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
objects[path] = serviceSpec
|
|
||||||
|
|
||||||
for i, char := range s.Characteristics {
|
|
||||||
// Calculate Flags field.
|
|
||||||
bluezCharFlags := []string{
|
|
||||||
"broadcast", // bit 0
|
|
||||||
"read", // bit 1
|
|
||||||
"write-without-response", // bit 2
|
|
||||||
"write", // bit 3
|
|
||||||
"notify", // bit 4
|
|
||||||
"indicate", // bit 5
|
|
||||||
}
|
|
||||||
var flags []string
|
|
||||||
for i := 0; i < len(bluezCharFlags); i++ {
|
|
||||||
if (char.Flags>>i)&1 != 0 {
|
|
||||||
flags = append(flags, bluezCharFlags[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export the properties of this characteristic.
|
|
||||||
charPath := path + dbus.ObjectPath("/char"+strconv.Itoa(i))
|
|
||||||
propsSpec := map[string]map[string]*prop.Prop{
|
|
||||||
"org.bluez.GattCharacteristic1": {
|
|
||||||
"UUID": {Value: char.UUID.String()},
|
|
||||||
"Service": {Value: path},
|
|
||||||
"Flags": {Value: flags},
|
|
||||||
"Value": {Value: char.Value, Writable: true, Emit: prop.EmitTrue},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
objects[charPath] = propsSpec
|
|
||||||
props, err := prop.Export(a.bus, charPath, propsSpec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export the methods of this characteristic.
|
|
||||||
obj := &bluezChar{
|
|
||||||
props: props,
|
|
||||||
writeEvent: char.WriteEvent,
|
|
||||||
}
|
|
||||||
err = a.bus.Export(obj, charPath, "org.bluez.GattCharacteristic1")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep the object around for Characteristic.Write.
|
|
||||||
if char.Handle != nil {
|
|
||||||
char.Handle.permissions = char.Flags
|
|
||||||
char.Handle.char = obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export all objects that are part of our service.
|
|
||||||
om := &objectManager{
|
|
||||||
objects: objects,
|
|
||||||
}
|
|
||||||
err := a.bus.Export(om, path, "org.freedesktop.DBus.ObjectManager")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register our service.
|
// disable magic uuid generation because we send through a fully formed UUID.
|
||||||
return a.adapter.Call("org.bluez.GattManager1.RegisterApplication", 0, path, map[string]dbus.Variant(nil)).Err
|
// muka/go-bluetooth does some magic so you can use short UUIDs and it'll auto
|
||||||
|
// expand them to the full 128 bit uuid.
|
||||||
|
// setting these flags disables that behavior.
|
||||||
|
app.Options.UUIDSuffix = ""
|
||||||
|
app.Options.UUID = ""
|
||||||
|
|
||||||
|
bluezService, err := app.NewService(s.UUID.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = app.AddService(bluezService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, char := range s.Characteristics {
|
||||||
|
// Create characteristic handle.
|
||||||
|
bluezChar, err := bluezService.NewChar(char.UUID.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set properties.
|
||||||
|
bluezCharFlags := []string{
|
||||||
|
gatt.FlagCharacteristicBroadcast, // bit 0
|
||||||
|
gatt.FlagCharacteristicRead, // bit 1
|
||||||
|
gatt.FlagCharacteristicWriteWithoutResponse, // bit 2
|
||||||
|
gatt.FlagCharacteristicWrite, // bit 3
|
||||||
|
gatt.FlagCharacteristicNotify, // bit 4
|
||||||
|
gatt.FlagCharacteristicIndicate, // bit 5
|
||||||
|
}
|
||||||
|
for i := uint(0); i < 5; i++ {
|
||||||
|
if (char.Flags>>i)&1 != 0 {
|
||||||
|
bluezChar.Properties.Flags = append(bluezChar.Properties.Flags, bluezCharFlags[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bluezChar.Properties.Value = char.Value
|
||||||
|
|
||||||
|
if char.Handle != nil {
|
||||||
|
char.Handle.handle = bluezChar
|
||||||
|
char.Handle.permissions = char.Flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a callback when the value changes.
|
||||||
|
if char.WriteEvent != nil {
|
||||||
|
callback := char.WriteEvent
|
||||||
|
bluezChar.OnWrite(func(c *service.Char, value []byte) ([]byte, error) {
|
||||||
|
// BlueZ doesn't seem to tell who did the write, so pass 0
|
||||||
|
// always.
|
||||||
|
// It also doesn't provide which part of the value was written,
|
||||||
|
// so pretend the entire characteristic was updated (which might
|
||||||
|
// not be the case).
|
||||||
|
callback(0, 0, value)
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add characteristic to the service, to activate it.
|
||||||
|
err = bluezService.AddChar(bluezChar)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write replaces the characteristic value with a new value.
|
// Write replaces the characteristic value with a new value.
|
||||||
|
@ -159,10 +100,7 @@ func (c *Characteristic) Write(p []byte) (n int, err error) {
|
||||||
return 0, nil // nothing to do
|
return 0, nil // nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.char.writeEvent != nil {
|
gattError := c.handle.WriteValue(p, nil)
|
||||||
c.char.writeEvent(0, 0, p)
|
|
||||||
}
|
|
||||||
gattError := c.char.props.Set("org.bluez.GattCharacteristic1", "Value", dbus.MakeVariant(p))
|
|
||||||
if gattError != nil {
|
if gattError != nil {
|
||||||
return 0, gattError
|
return 0, gattError
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build !linux && !windows
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
|
63
gatts_sd.go
63
gatts_sd.go
|
@ -1,33 +1,18 @@
|
||||||
//go:build softdevice
|
//go:build softdevice
|
||||||
|
// +build softdevice
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#include "ble_gap.h"
|
#include "ble_gap.h"
|
||||||
#include "ble_gatts.h"
|
#include "ble_gatts.h"
|
||||||
|
|
||||||
// Workaround wrapper functions which prevent pointer arguments escaping to heap
|
|
||||||
static inline uint32_t sd_ble_gatts_hvx_noescape(uint16_t conn_handle, uint16_t handle, uint8_t type, uint16_t offset, uint16_t len, uint8_t *p_data) {
|
|
||||||
ble_gatts_hvx_params_t p_hvx_params = {handle, type, offset, &len, p_data};
|
|
||||||
return sd_ble_gatts_hvx(conn_handle, &p_hvx_params);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t sd_ble_gatts_value_set_noescape(uint16_t conn_handle, uint16_t handle, uint16_t len, uint8_t *value) {
|
|
||||||
ble_gatts_value_t p_value = {
|
|
||||||
.len = len,
|
|
||||||
.offset = 0,
|
|
||||||
.p_value = value,
|
|
||||||
};
|
|
||||||
return sd_ble_gatts_value_set(conn_handle, handle, &p_value);
|
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
// Characteristic is a single characteristic in a service. It has an UUID and a
|
// Characteristic is a single characteristic in a service. It has an UUID and a
|
||||||
// value.
|
// value.
|
||||||
type Characteristic struct {
|
type Characteristic struct {
|
||||||
handle C.uint16_t
|
handle uint16
|
||||||
permissions CharacteristicPermissions
|
permissions CharacteristicPermissions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,18 +23,18 @@ func (a *Adapter) AddService(service *Service) error {
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return Error(errCode)
|
return Error(errCode)
|
||||||
}
|
}
|
||||||
errCode = C.sd_ble_gatts_service_add(C.BLE_GATTS_SRVC_TYPE_PRIMARY, &uuid, (*C.uint16_t)(unsafe.Pointer(&service.handle)))
|
errCode = C.sd_ble_gatts_service_add(C.BLE_GATTS_SRVC_TYPE_PRIMARY, &uuid, &service.handle)
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return Error(errCode)
|
return Error(errCode)
|
||||||
}
|
}
|
||||||
for _, char := range service.Characteristics {
|
for _, char := range service.Characteristics {
|
||||||
metadata := C.ble_gatts_char_md_t{}
|
metadata := C.ble_gatts_char_md_t{}
|
||||||
metadata.char_props.set_bitfield_broadcast(C.uint8_t(char.Flags>>0) & 1)
|
metadata.char_props.set_bitfield_broadcast(uint8(char.Flags>>0) & 1)
|
||||||
metadata.char_props.set_bitfield_read(C.uint8_t(char.Flags>>1) & 1)
|
metadata.char_props.set_bitfield_read(uint8(char.Flags>>1) & 1)
|
||||||
metadata.char_props.set_bitfield_write_wo_resp(C.uint8_t(char.Flags>>2) & 1)
|
metadata.char_props.set_bitfield_write_wo_resp(uint8(char.Flags>>2) & 1)
|
||||||
metadata.char_props.set_bitfield_write(C.uint8_t(char.Flags>>3) & 1)
|
metadata.char_props.set_bitfield_write(uint8(char.Flags>>3) & 1)
|
||||||
metadata.char_props.set_bitfield_notify(C.uint8_t(char.Flags>>4) & 1)
|
metadata.char_props.set_bitfield_notify(uint8(char.Flags>>4) & 1)
|
||||||
metadata.char_props.set_bitfield_indicate(C.uint8_t(char.Flags>>5) & 1)
|
metadata.char_props.set_bitfield_indicate(uint8(char.Flags>>5) & 1)
|
||||||
handles := C.ble_gatts_char_handles_t{}
|
handles := C.ble_gatts_char_handles_t{}
|
||||||
charUUID, errCode := char.UUID.shortUUID()
|
charUUID, errCode := char.UUID.shortUUID()
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
|
@ -61,16 +46,16 @@ func (a *Adapter) AddService(service *Service) error {
|
||||||
read_perm: secModeOpen,
|
read_perm: secModeOpen,
|
||||||
write_perm: secModeOpen,
|
write_perm: secModeOpen,
|
||||||
},
|
},
|
||||||
init_len: C.uint16_t(len(char.Value)),
|
init_len: uint16(len(char.Value)),
|
||||||
init_offs: 0,
|
init_offs: 0,
|
||||||
max_len: 20, // This is a conservative maximum length.
|
max_len: 20, // This is a conservative maximum length.
|
||||||
}
|
}
|
||||||
if len(char.Value) != 0 {
|
if len(char.Value) != 0 {
|
||||||
value.p_value = (*C.uint8_t)(unsafe.Pointer(&char.Value[0]))
|
value.p_value = &char.Value[0]
|
||||||
}
|
}
|
||||||
value.p_attr_md.set_bitfield_vloc(C.BLE_GATTS_VLOC_STACK)
|
value.p_attr_md.set_bitfield_vloc(C.BLE_GATTS_VLOC_STACK)
|
||||||
value.p_attr_md.set_bitfield_vlen(1)
|
value.p_attr_md.set_bitfield_vlen(1)
|
||||||
errCode = C.sd_ble_gatts_characteristic_add(C.uint16_t(service.handle), &metadata, &value, &handles)
|
errCode = C.sd_ble_gatts_characteristic_add(service.handle, &metadata, &value, &handles)
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return Error(errCode)
|
return Error(errCode)
|
||||||
}
|
}
|
||||||
|
@ -94,13 +79,13 @@ func (a *Adapter) AddService(service *Service) error {
|
||||||
// charWriteHandler contains a handler->callback mapping for characteristic
|
// charWriteHandler contains a handler->callback mapping for characteristic
|
||||||
// writes.
|
// writes.
|
||||||
type charWriteHandler struct {
|
type charWriteHandler struct {
|
||||||
handle C.uint16_t
|
handle uint16
|
||||||
callback func(connection Connection, offset int, value []byte)
|
callback func(connection Connection, offset int, value []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCharWriteHandler returns a characteristic write handler if one matches the
|
// getCharWriteHandler returns a characteristic write handler if one matches the
|
||||||
// handle, or nil otherwise.
|
// handle, or nil otherwise.
|
||||||
func (a *Adapter) getCharWriteHandler(handle C.uint16_t) *charWriteHandler {
|
func (a *Adapter) getCharWriteHandler(handle uint16) *charWriteHandler {
|
||||||
// Look through all handlers for a match.
|
// Look through all handlers for a match.
|
||||||
// There is probably a way to do this more efficiently (with a hashmap for
|
// There is probably a way to do this more efficiently (with a hashmap for
|
||||||
// example) but the number of event handlers is likely low and improving
|
// example) but the number of event handlers is likely low and improving
|
||||||
|
@ -123,16 +108,15 @@ func (c *Characteristic) Write(p []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
connHandle := currentConnection.Get()
|
connHandle := currentConnection.Get()
|
||||||
if connHandle != C.BLE_CONN_HANDLE_INVALID && c.permissions&(CharacteristicNotifyPermission|CharacteristicIndicatePermission) != 0 {
|
if connHandle != C.BLE_CONN_HANDLE_INVALID {
|
||||||
// There is a connected central.
|
// There is a connected central.
|
||||||
p_len := uint16(len(p))
|
p_len := uint16(len(p))
|
||||||
errCode := C.sd_ble_gatts_hvx_noescape(connHandle,
|
errCode := C.sd_ble_gatts_hvx(connHandle, &C.ble_gatts_hvx_params_t{
|
||||||
c.handle,
|
handle: c.handle,
|
||||||
C.BLE_GATT_HVX_NOTIFICATION,
|
_type: C.BLE_GATT_HVX_NOTIFICATION,
|
||||||
0,
|
p_len: &p_len,
|
||||||
C.uint16_t(p_len),
|
p_data: &p[0],
|
||||||
(*C.uint8_t)(unsafe.Pointer(&p[0])),
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// Check for some expected errors. Don't report them as errors, but
|
// Check for some expected errors. Don't report them as errors, but
|
||||||
// instead fall through and do a normal characteristic value update.
|
// instead fall through and do a normal characteristic value update.
|
||||||
|
@ -150,7 +134,10 @@ func (c *Characteristic) Write(p []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errCode := C.sd_ble_gatts_value_set_noescape(C.BLE_CONN_HANDLE_INVALID, c.handle, C.uint16_t(len(p)), (*C.uint8_t)(unsafe.Pointer(&p[0])))
|
errCode := C.sd_ble_gatts_value_set(C.BLE_CONN_HANDLE_INVALID, c.handle, &C.ble_gatts_value_t{
|
||||||
|
len: uint16(len(p)),
|
||||||
|
p_value: &p[0],
|
||||||
|
})
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
return 0, Error(errCode)
|
return 0, Error(errCode)
|
||||||
}
|
}
|
||||||
|
|
309
gatts_windows.go
309
gatts_windows.go
|
@ -1,309 +0,0 @@
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/go-ole/go-ole"
|
|
||||||
"github.com/saltosystems/winrt-go"
|
|
||||||
"github.com/saltosystems/winrt-go/windows/devices/bluetooth/genericattributeprofile"
|
|
||||||
"github.com/saltosystems/winrt-go/windows/foundation"
|
|
||||||
"github.com/saltosystems/winrt-go/windows/foundation/collections"
|
|
||||||
"github.com/saltosystems/winrt-go/windows/storage/streams"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Characteristic is a single characteristic in a service. It has an UUID and a
|
|
||||||
// value.
|
|
||||||
type Characteristic struct {
|
|
||||||
wintCharacteristic *genericattributeprofile.GattLocalCharacteristic
|
|
||||||
writeEvent WriteEvent
|
|
||||||
flags CharacteristicPermissions
|
|
||||||
|
|
||||||
valueMtx *sync.Mutex
|
|
||||||
value []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddService creates a new service with the characteristics listed in the
|
|
||||||
// Service struct.
|
|
||||||
func (a *Adapter) AddService(s *Service) error {
|
|
||||||
gattServiceOp, err := genericattributeprofile.GattServiceProviderCreateAsync(syscallUUIDFromUUID(s.UUID))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = awaitAsyncOperation(gattServiceOp, genericattributeprofile.SignatureGattServiceProviderResult); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := gattServiceOp.GetResults()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceProviderResult := (*genericattributeprofile.GattServiceProviderResult)(res)
|
|
||||||
serviceProvider, err := serviceProviderResult.GetServiceProvider()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
localService, err := serviceProvider.GetService()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: "ParameterizedInstanceGUID" + "foundation.NewTypedEventHandler"
|
|
||||||
// seems to always return the same instance, need to figure out how to get different instances each time...
|
|
||||||
// was following c# source for this flow: https://github.com/microsoft/Windows-universal-samples/blob/main/Samples/BluetoothLE/cs/Scenario3_ServerForeground.xaml.cs
|
|
||||||
// which relies on instanced event handlers. for now we'll manually setup our handlers with a map of golang characteristics
|
|
||||||
//
|
|
||||||
// TypedEventHandler<GattLocalCharacteristic,GattWriteRequestedEventArgs>
|
|
||||||
guid := winrt.ParameterizedInstanceGUID(
|
|
||||||
foundation.GUIDTypedEventHandler,
|
|
||||||
genericattributeprofile.SignatureGattLocalCharacteristic,
|
|
||||||
genericattributeprofile.SignatureGattWriteRequestedEventArgs)
|
|
||||||
|
|
||||||
goChars := map[syscall.GUID]*Characteristic{}
|
|
||||||
|
|
||||||
writeRequestedHandler := foundation.NewTypedEventHandler(ole.NewGUID(guid), func(instance *foundation.TypedEventHandler, sender, args unsafe.Pointer) {
|
|
||||||
writeReqArgs := (*genericattributeprofile.GattWriteRequestedEventArgs)(args)
|
|
||||||
reqAsyncOp, err := writeReqArgs.GetRequestAsync()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = awaitAsyncOperation(reqAsyncOp, genericattributeprofile.SignatureGattWriteRequest); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := reqAsyncOp.GetResults()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gattWriteRequest := (*genericattributeprofile.GattWriteRequest)(res)
|
|
||||||
|
|
||||||
buf, err := gattWriteRequest.GetValue()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
offset, err := gattWriteRequest.GetOffset()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
characteristic := (*genericattributeprofile.GattLocalCharacteristic)(sender)
|
|
||||||
uuid, err := characteristic.GetUuid()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
goChar, ok := goChars[uuid]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if goChar.writeEvent != nil {
|
|
||||||
// TODO: connection?
|
|
||||||
goChar.writeEvent(0, int(offset), bufferToSlice(buf))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
guid = winrt.ParameterizedInstanceGUID(
|
|
||||||
foundation.GUIDTypedEventHandler,
|
|
||||||
genericattributeprofile.SignatureGattLocalCharacteristic,
|
|
||||||
genericattributeprofile.SignatureGattReadRequestedEventArgs)
|
|
||||||
|
|
||||||
readRequestedHandler := foundation.NewTypedEventHandler(ole.NewGUID(guid), func(instance *foundation.TypedEventHandler, sender, args unsafe.Pointer) {
|
|
||||||
readReqArgs := (*genericattributeprofile.GattReadRequestedEventArgs)(args)
|
|
||||||
reqAsyncOp, err := readReqArgs.GetRequestAsync()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = awaitAsyncOperation(reqAsyncOp, genericattributeprofile.SignatureGattReadRequest); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := reqAsyncOp.GetResults()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gattReadRequest := (*genericattributeprofile.GattReadRequest)(res)
|
|
||||||
|
|
||||||
characteristic := (*genericattributeprofile.GattLocalCharacteristic)(sender)
|
|
||||||
uuid, err := characteristic.GetUuid()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
goChar, ok := goChars[uuid]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writer, err := streams.NewDataWriter()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer writer.Release()
|
|
||||||
|
|
||||||
goChar.valueMtx.Lock()
|
|
||||||
defer goChar.valueMtx.Unlock()
|
|
||||||
if len(goChar.value) > 0 {
|
|
||||||
if err = writer.WriteBytes(uint32(len(goChar.value)), goChar.value); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := writer.DetachBuffer()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gattReadRequest.RespondWithValue(buf)
|
|
||||||
buf.Release()
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, char := range s.Characteristics {
|
|
||||||
params, err := genericattributeprofile.NewGattLocalCharacteristicParameters()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = params.SetCharacteristicProperties(genericattributeprofile.GattCharacteristicProperties(char.Flags)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid := syscallUUIDFromUUID(char.UUID)
|
|
||||||
createCharOp, err := localService.CreateCharacteristicAsync(uuid, params)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = awaitAsyncOperation(createCharOp, genericattributeprofile.SignatureGattLocalCharacteristicResult); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := createCharOp.GetResults()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
characteristicResults := (*genericattributeprofile.GattLocalCharacteristicResult)(res)
|
|
||||||
characteristic, err := characteristicResults.GetCharacteristic()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = characteristic.AddWriteRequested(writeRequestedHandler)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = characteristic.AddReadRequested(readRequestedHandler)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep the object around for Characteristic.Write.
|
|
||||||
if char.Handle != nil {
|
|
||||||
char.Handle.wintCharacteristic = characteristic
|
|
||||||
char.Handle.value = char.Value
|
|
||||||
char.Handle.valueMtx = &sync.Mutex{}
|
|
||||||
char.Handle.flags = char.Flags
|
|
||||||
char.Handle.writeEvent = char.WriteEvent
|
|
||||||
goChars[uuid] = char.Handle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
params, err := genericattributeprofile.NewGattServiceProviderAdvertisingParameters()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = params.SetIsConnectable(true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = params.SetIsDiscoverable(true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return serviceProvider.StartAdvertisingWithParameters(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write replaces the characteristic value with a new value.
|
|
||||||
func (c *Characteristic) Write(p []byte) (n int, err error) {
|
|
||||||
length := len(p)
|
|
||||||
|
|
||||||
if length == 0 {
|
|
||||||
return 0, nil // nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.writeEvent != nil {
|
|
||||||
c.writeEvent(0, 0, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writes are only actually processed on read events from clients, we just set a variable here.
|
|
||||||
c.valueMtx.Lock()
|
|
||||||
defer c.valueMtx.Unlock()
|
|
||||||
c.value = p
|
|
||||||
|
|
||||||
// only notify if it's enabled, otherwise the below leads to an error
|
|
||||||
if c.flags&CharacteristicNotifyPermission != 0 {
|
|
||||||
writer, err := streams.NewDataWriter()
|
|
||||||
if err != nil {
|
|
||||||
return length, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer writer.Release()
|
|
||||||
err = writer.WriteBytes(uint32(len(p)), p)
|
|
||||||
if err != nil {
|
|
||||||
return length, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := writer.DetachBuffer()
|
|
||||||
if err != nil {
|
|
||||||
return length, err
|
|
||||||
}
|
|
||||||
defer buf.Release()
|
|
||||||
|
|
||||||
op, err := c.wintCharacteristic.NotifyValueAsync(buf)
|
|
||||||
if err != nil {
|
|
||||||
return length, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IVectorView<GattClientNotificationResult>
|
|
||||||
signature := fmt.Sprintf("pinterface({%s};%s)", collections.GUIDIVectorView, genericattributeprofile.SignatureGattClientNotificationResult)
|
|
||||||
if err = awaitAsyncOperation(op, signature); err != nil {
|
|
||||||
return length, err
|
|
||||||
}
|
|
||||||
defer op.Release()
|
|
||||||
|
|
||||||
res, err := op.GetResults()
|
|
||||||
if err != nil {
|
|
||||||
return length, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: process notification results, just getting this to release
|
|
||||||
vec := (*collections.IVectorView)(res)
|
|
||||||
vec.Release()
|
|
||||||
}
|
|
||||||
|
|
||||||
return length, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func syscallUUIDFromUUID(uuid UUID) syscall.GUID {
|
|
||||||
guid := ole.NewGUID(uuid.String())
|
|
||||||
return syscall.GUID{
|
|
||||||
Data1: guid.Data1,
|
|
||||||
Data2: guid.Data2,
|
|
||||||
Data3: guid.Data3,
|
|
||||||
Data4: guid.Data4,
|
|
||||||
}
|
|
||||||
}
|
|
30
go.mod
30
go.mod
|
@ -1,25 +1,17 @@
|
||||||
module gitrepo.ru/neonxp/bluetooth
|
module tinygo.org/x/bluetooth
|
||||||
|
|
||||||
go 1.20
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-ole/go-ole v1.2.6
|
github.com/go-ole/go-ole v1.2.6
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/godbus/dbus/v5 v5.0.3
|
||||||
github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b
|
github.com/muka/go-bluetooth v0.0.0-20220830075246-0746e3a1ea53
|
||||||
github.com/soypat/cyw43439 v0.0.0-20240609122733-da9153086796
|
github.com/saltosystems/winrt-go v0.0.0-20230510070731-e096b9afa761
|
||||||
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
github.com/tinygo-org/cbgo v0.0.4
|
github.com/tinygo-org/cbgo v0.0.4
|
||||||
golang.org/x/crypto v0.12.0
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||||
tinygo.org/x/drivers v0.26.1-0.20230922160320-ed51435c2ef6
|
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
|
||||||
tinygo.org/x/tinyfont v0.4.0
|
golang.org/x/text v0.5.0
|
||||||
tinygo.org/x/tinyterm v0.3.0
|
tinygo.org/x/drivers v0.23.0
|
||||||
)
|
tinygo.org/x/tinyterm v0.1.0
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
|
||||||
github.com/soypat/seqs v0.0.0-20240527012110-1201bab640ef // indirect
|
|
||||||
github.com/tinygo-org/pio v0.0.0-20231216154340-cd888eb58899 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 // indirect
|
|
||||||
golang.org/x/sys v0.11.0 // indirect
|
|
||||||
golang.org/x/term v0.11.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
141
go.sum
141
go.sum
|
@ -1,49 +1,134 @@
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
||||||
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
|
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
||||||
|
github.com/glerchundi/subcommands v0.0.0-20181212083838-923a6ccb11f8/go.mod h1:r0g3O7Y5lrWXgDfcFBRgnAKzjmPgTzwoMC2ieB345FY=
|
||||||
|
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/muka/go-bluetooth v0.0.0-20220830075246-0746e3a1ea53 h1:zfLHhuGzmSbthZ00FfbEjgAHUOOj7NGiITojMTCFy6U=
|
||||||
|
github.com/muka/go-bluetooth v0.0.0-20220830075246-0746e3a1ea53/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk=
|
||||||
|
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||||
|
github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b h1:du3zG5fd8snsFN6RBoLA7fpaYV9ZQIsyH9snlk2Zvik=
|
github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4/go.mod h1:rOebXGuMLsXhZAC6mF/TjxONsm45498ZyzVhel++6KM=
|
||||||
github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA=
|
github.com/saltosystems/winrt-go v0.0.0-20230124093143-967a889c6c8f h1:sxsy5XkcxSzkiUkYgx38V9JviWLL8wthO2TURCi0Lcs=
|
||||||
|
github.com/saltosystems/winrt-go v0.0.0-20230124093143-967a889c6c8f/go.mod h1:UvKm1lyhg+8ehk99i8g5Q7AX1LXUJgks0lRyAkG/ahQ=
|
||||||
|
github.com/saltosystems/winrt-go v0.0.0-20230510070731-e096b9afa761 h1:xEscoMxTrGSpdho1mP9VnGsK0DGhXKwm0qP7kYcjgrI=
|
||||||
|
github.com/saltosystems/winrt-go v0.0.0-20230510070731-e096b9afa761/go.mod h1:UvKm1lyhg+8ehk99i8g5Q7AX1LXUJgks0lRyAkG/ahQ=
|
||||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
github.com/soypat/cyw43439 v0.0.0-20240609122733-da9153086796 h1:1/r2URInjjFtWqT61gU7YGVCq3BRyXt/C7z4oLRF9Lo=
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/soypat/cyw43439 v0.0.0-20240609122733-da9153086796/go.mod h1:1Otjk6PRhfzfcVHeWMEeku/VntFqWghUwuSQyivb2vE=
|
|
||||||
github.com/soypat/seqs v0.0.0-20240527012110-1201bab640ef h1:phH95I9wANjTYw6bSYLZDQfNvao+HqYDom8owbNa0P4=
|
|
||||||
github.com/soypat/seqs v0.0.0-20240527012110-1201bab640ef/go.mod h1:oCVCNGCHMKoBj97Zp9znLbQ1nHxpkmOY9X+UAGzOxc8=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q=
|
github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q=
|
||||||
|
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8=
|
||||||
|
github.com/tdakkota/win32metadata v0.1.0/go.mod h1:77e6YvX0LIVW+O81fhWLnXAxxcyu/wdZdG7iwed7Fyk=
|
||||||
github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU=
|
github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU=
|
||||||
github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk=
|
github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk=
|
||||||
github.com/tinygo-org/pio v0.0.0-20231216154340-cd888eb58899 h1:/DyaXDEWMqoVUVEJVJIlNk1bXTbFs8s3Q4GdPInSKTQ=
|
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/tinygo-org/pio v0.0.0-20231216154340-cd888eb58899/go.mod h1:LU7Dw00NJ+N86QkeTGjMLNkYcEYMor6wTDpTCu0EaH8=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
|
||||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||||
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||||
|
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
tinygo.org/x/drivers v0.26.1-0.20230922160320-ed51435c2ef6 h1:w18u47MirULgAl+bP0piUGu5VUZDs7TvXwHASEVXqHk=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
tinygo.org/x/drivers v0.26.1-0.20230922160320-ed51435c2ef6/go.mod h1:X7utcg3yfFUFuKLOMTZD56eztXMjpkcf8OHldfTBsjw=
|
tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
||||||
tinygo.org/x/tinyfont v0.4.0 h1:XexPKEKiHInf6p4CMCJwsIheVPY0T46HUs6ictYyZfE=
|
tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
||||||
tinygo.org/x/tinyfont v0.4.0/go.mod h1:7nVj3j3geqBoPDzpFukAhF1C8AP9YocMsZy0HSAcGCA=
|
tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
||||||
tinygo.org/x/tinyterm v0.3.0 h1:4MMZoMyrbWbjru1KP/Z2TGhaguy/Uh5Mdhf/niemM8c=
|
tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI=
|
||||||
tinygo.org/x/tinyterm v0.3.0/go.mod h1:F1pQjxEwNZQIc5czeJSBtk57ucEvbR4u7vHaLhWhHtg=
|
tinygo.org/x/drivers v0.23.0 h1:fUy4OmLOWWYCOzDp/83Qewej1Q+YgUpwkm11e7gxUc0=
|
||||||
|
tinygo.org/x/drivers v0.23.0/go.mod h1:J4+51Li1kcfL5F93kmnDWEEzQF3bLGz0Am3Q7E2a8/E=
|
||||||
|
tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM=
|
||||||
|
tinygo.org/x/tinyfont v0.3.0 h1:HIRLQoI3oc+2CMhPcfv+Ig88EcTImE/5npjqOnMD4lM=
|
||||||
|
tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk=
|
||||||
|
tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20=
|
||||||
|
tinygo.org/x/tinyfs v0.2.0/go.mod h1:6ZHYdvB3sFYeMB3ypmXZCNEnFwceKc61ADYTYHpep1E=
|
||||||
|
tinygo.org/x/tinyterm v0.1.0 h1:80i+j+KWoxCFa/Xfp6pWbh79x+8zUdMXC1vaKj2QhkY=
|
||||||
|
tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4=
|
||||||
|
|
787
hci.go
787
hci.go
|
@ -1,787 +0,0 @@
|
||||||
//go:build ninafw || hci || cyw43439
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ogfCommandPos = 10
|
|
||||||
|
|
||||||
ogfLinkCtl = 0x01
|
|
||||||
ogfHostCtl = 0x03
|
|
||||||
ogfInfoParam = 0x04
|
|
||||||
ogfStatusParam = 0x05
|
|
||||||
ogfLECtrl = 0x08
|
|
||||||
|
|
||||||
// ogfLinkCtl
|
|
||||||
ocfDisconnect = 0x0006
|
|
||||||
|
|
||||||
// ogfHostCtl
|
|
||||||
ocfSetEventMask = 0x0001
|
|
||||||
ocfReset = 0x0003
|
|
||||||
|
|
||||||
// ogfInfoParam
|
|
||||||
ocfReadLocalVersion = 0x0001
|
|
||||||
ocfReadBDAddr = 0x0009
|
|
||||||
|
|
||||||
// ogfStatusParam
|
|
||||||
ocfReadRSSI = 0x0005
|
|
||||||
|
|
||||||
// ogfLECtrl
|
|
||||||
ocfLEReadBufferSize = 0x0002
|
|
||||||
ocfLESetRandomAddress = 0x0005
|
|
||||||
ocfLESetAdvertisingParameters = 0x0006
|
|
||||||
ocfLESetAdvertisingData = 0x0008
|
|
||||||
ocfLESetScanResponseData = 0x0009
|
|
||||||
ocfLESetAdvertiseEnable = 0x000a
|
|
||||||
ocfLESetScanParameters = 0x000b
|
|
||||||
ocfLESetScanEnable = 0x000c
|
|
||||||
ocfLECreateConn = 0x000d
|
|
||||||
ocfLECancelConn = 0x000e
|
|
||||||
ocfLEConnUpdate = 0x0013
|
|
||||||
ocfLEParamRequestReply = 0x0020
|
|
||||||
|
|
||||||
leCommandEncrypt = 0x0017
|
|
||||||
leCommandRandom = 0x0018
|
|
||||||
leCommandLongTermKeyReply = 0x001A
|
|
||||||
leCommandLongTermKeyNegativeReply = 0x001B
|
|
||||||
leCommandReadLocalP256 = 0x0025
|
|
||||||
leCommandGenerateDHKeyV1 = 0x0026
|
|
||||||
leCommandGenerateDHKeyV2 = 0x005E
|
|
||||||
|
|
||||||
leMetaEventConnComplete = 0x01
|
|
||||||
leMetaEventAdvertisingReport = 0x02
|
|
||||||
leMetaEventConnectionUpdateComplete = 0x03
|
|
||||||
leMetaEventReadRemoteUsedFeaturesComplete = 0x04
|
|
||||||
leMetaEventLongTermKeyRequest = 0x05
|
|
||||||
leMetaEventRemoteConnParamReq = 0x06
|
|
||||||
leMetaEventDataLengthChange = 0x07
|
|
||||||
leMetaEventReadLocalP256Complete = 0x08
|
|
||||||
leMetaEventGenerateDHKeyComplete = 0x09
|
|
||||||
leMetaEventEnhancedConnectionComplete = 0x0A
|
|
||||||
leMetaEventDirectAdvertisingReport = 0x0B
|
|
||||||
|
|
||||||
hciCommandPkt = 0x01
|
|
||||||
hciACLDataPkt = 0x02
|
|
||||||
hciSynchronousDataPkt = 0x03
|
|
||||||
hciEventPkt = 0x04
|
|
||||||
hciSecurityPkt = 0x06
|
|
||||||
|
|
||||||
evtDisconnComplete = 0x05
|
|
||||||
evtEncryptionChange = 0x08
|
|
||||||
evtCmdComplete = 0x0e
|
|
||||||
evtCmdStatus = 0x0f
|
|
||||||
evtHardwareError = 0x10
|
|
||||||
evtNumCompPkts = 0x13
|
|
||||||
evtReturnLinkKeys = 0x15
|
|
||||||
evtLEMetaEvent = 0x3e
|
|
||||||
|
|
||||||
hciOEUserEndedConnection = 0x13
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
hciACLLenPos = 4
|
|
||||||
hciEvtLenPos = 2
|
|
||||||
|
|
||||||
attCID = 0x0004
|
|
||||||
bleCTL = 0x0008
|
|
||||||
signalingCID = 0x0005
|
|
||||||
securityCID = 0x0006
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrHCITimeout = errors.New("bluetooth: HCI timeout")
|
|
||||||
ErrHCIUnknownEvent = errors.New("bluetooth: HCI unknown event")
|
|
||||||
ErrHCIUnknown = errors.New("bluetooth: HCI unknown error")
|
|
||||||
ErrHCIInvalidPacket = errors.New("bluetooth: HCI invalid packet")
|
|
||||||
ErrHCIHardware = errors.New("bluetooth: HCI hardware error")
|
|
||||||
)
|
|
||||||
|
|
||||||
type leAdvertisingReport struct {
|
|
||||||
reported bool
|
|
||||||
numReports, typ, peerBdaddrType uint8
|
|
||||||
peerBdaddr [6]uint8
|
|
||||||
eirLength uint8
|
|
||||||
eirData [31]uint8
|
|
||||||
rssi int8
|
|
||||||
}
|
|
||||||
|
|
||||||
type leConnectData struct {
|
|
||||||
connected bool
|
|
||||||
status uint8
|
|
||||||
handle uint16
|
|
||||||
role uint8
|
|
||||||
peerBdaddrType uint8
|
|
||||||
peerBdaddr [6]uint8
|
|
||||||
interval uint16
|
|
||||||
timeout uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type hciTransport interface {
|
|
||||||
startRead()
|
|
||||||
endRead()
|
|
||||||
Buffered() int
|
|
||||||
ReadByte() (byte, error)
|
|
||||||
Read(buf []byte) (int, error)
|
|
||||||
Write(buf []byte) (int, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type hci struct {
|
|
||||||
transport hciTransport
|
|
||||||
att *att
|
|
||||||
l2cap *l2cap
|
|
||||||
buf []byte
|
|
||||||
address [6]byte
|
|
||||||
cmdCompleteOpcode uint16
|
|
||||||
cmdCompleteStatus uint8
|
|
||||||
cmdResponse []byte
|
|
||||||
scanning bool
|
|
||||||
advData leAdvertisingReport
|
|
||||||
connectData leConnectData
|
|
||||||
maxPkt uint16
|
|
||||||
pendingPkt uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHCI(t hciTransport) *hci {
|
|
||||||
return &hci{
|
|
||||||
transport: t,
|
|
||||||
buf: make([]byte, 256),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) start() error {
|
|
||||||
h.transport.startRead()
|
|
||||||
defer h.transport.endRead()
|
|
||||||
|
|
||||||
var data [32]byte
|
|
||||||
for {
|
|
||||||
if i := h.transport.Buffered(); i > 0 {
|
|
||||||
if i > len(data) {
|
|
||||||
i = len(data)
|
|
||||||
}
|
|
||||||
if _, err := h.transport.Read(data[:i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) stop() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) reset() error {
|
|
||||||
return h.sendCommand(ogfHostCtl<<10 | ocfReset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) poll() error {
|
|
||||||
h.transport.startRead()
|
|
||||||
defer h.transport.endRead()
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
for h.transport.Buffered() > 0 {
|
|
||||||
sz := h.transport.Buffered()
|
|
||||||
c := sz + 4 - (sz % 4)
|
|
||||||
_, err := h.transport.Read(h.buf[i : i+c])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i += sz
|
|
||||||
|
|
||||||
done, err := h.processPacket(i)
|
|
||||||
switch {
|
|
||||||
case err == ErrHCIUnknown || err == ErrHCIInvalidPacket || err == ErrHCIUnknownEvent:
|
|
||||||
if debug {
|
|
||||||
println("hci error:", err.Error(), hex.EncodeToString(h.buf[:i]))
|
|
||||||
}
|
|
||||||
i = 0
|
|
||||||
time.Sleep(5 * time.Millisecond)
|
|
||||||
case err != nil:
|
|
||||||
return err
|
|
||||||
case done:
|
|
||||||
return nil
|
|
||||||
case i+1 >= len(h.buf):
|
|
||||||
if debug {
|
|
||||||
println("hci error: buffer overflow")
|
|
||||||
}
|
|
||||||
i = 0
|
|
||||||
time.Sleep(5 * time.Millisecond)
|
|
||||||
default:
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) processPacket(i int) (bool, error) {
|
|
||||||
switch h.buf[0] {
|
|
||||||
case hciACLDataPkt:
|
|
||||||
if i > hciACLLenPos {
|
|
||||||
pktlen := int(binary.LittleEndian.Uint16(h.buf[3:5]))
|
|
||||||
switch {
|
|
||||||
case pktlen > len(h.buf):
|
|
||||||
return true, ErrHCIInvalidPacket
|
|
||||||
case i >= (hciACLLenPos + pktlen):
|
|
||||||
if debug {
|
|
||||||
println("hci acl data:", i, hex.EncodeToString(h.buf[:1+hciACLLenPos+pktlen]))
|
|
||||||
}
|
|
||||||
return true, h.handleACLData(h.buf[1 : 1+hciACLLenPos+pktlen])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case hciEventPkt:
|
|
||||||
if i > hciEvtLenPos {
|
|
||||||
pktlen := int(h.buf[hciEvtLenPos])
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case pktlen > len(h.buf):
|
|
||||||
return true, ErrHCIInvalidPacket
|
|
||||||
case i >= (hciEvtLenPos + pktlen):
|
|
||||||
if debug {
|
|
||||||
println("hci event data:", i, hex.EncodeToString(h.buf[:1+hciEvtLenPos+pktlen]))
|
|
||||||
}
|
|
||||||
return true, h.handleEventData(h.buf[1 : 1+hciEvtLenPos+pktlen])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case hciSynchronousDataPkt:
|
|
||||||
// not supported by BLE, so ignore
|
|
||||||
if i > 3 {
|
|
||||||
pktlen := int(h.buf[3])
|
|
||||||
if debug {
|
|
||||||
println("hci synchronous data:", i, pktlen, hex.EncodeToString(h.buf[:1+3+pktlen]))
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if debug {
|
|
||||||
println("unknown packet data:", hex.EncodeToString(h.buf[0:i]))
|
|
||||||
}
|
|
||||||
return true, ErrHCIUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) readBdAddr() error {
|
|
||||||
if err := h.sendCommand(ogfInfoParam<<ogfCommandPos | ocfReadBDAddr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(h.address[:], h.cmdResponse[:7])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) setEventMask(eventMask uint64) error {
|
|
||||||
var b [8]byte
|
|
||||||
binary.LittleEndian.PutUint64(b[:], eventMask)
|
|
||||||
return h.sendCommandWithParams(ogfHostCtl<<ogfCommandPos|ocfSetEventMask, b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) setLeEventMask(eventMask uint64) error {
|
|
||||||
var b [8]byte
|
|
||||||
binary.LittleEndian.PutUint64(b[:], eventMask)
|
|
||||||
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|0x01, b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) readLeBufferSize() error {
|
|
||||||
if err := h.sendCommand(ogfLECtrl<<ogfCommandPos | ocfLEReadBufferSize); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pktLen := binary.LittleEndian.Uint16(h.buf[0:])
|
|
||||||
h.maxPkt = uint16(h.buf[2])
|
|
||||||
|
|
||||||
// pkt len must be at least 27 bytes
|
|
||||||
if pktLen < 27 {
|
|
||||||
pktLen = 27
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.att.setMaxMTU(pktLen); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) leSetScanEnable(enabled, duplicates bool) error {
|
|
||||||
h.scanning = enabled
|
|
||||||
|
|
||||||
var data [2]byte
|
|
||||||
if enabled {
|
|
||||||
data[0] = 1
|
|
||||||
}
|
|
||||||
if duplicates {
|
|
||||||
data[1] = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetScanEnable, data[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) leSetScanParameters(typ uint8, interval, window uint16, ownBdaddrType, filter uint8) error {
|
|
||||||
var data [7]byte
|
|
||||||
data[0] = typ
|
|
||||||
binary.LittleEndian.PutUint16(data[1:], interval)
|
|
||||||
binary.LittleEndian.PutUint16(data[3:], window)
|
|
||||||
data[5] = ownBdaddrType
|
|
||||||
data[6] = filter
|
|
||||||
|
|
||||||
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetScanParameters, data[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) leSetAdvertiseEnable(enabled bool) error {
|
|
||||||
var data [1]byte
|
|
||||||
if enabled {
|
|
||||||
data[0] = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.sendWithoutResponse(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertiseEnable, data[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) leSetAdvertisingParameters(minInterval, maxInterval uint16,
|
|
||||||
advType, ownBdaddrType uint8,
|
|
||||||
directBdaddrType uint8, directBdaddr [6]byte,
|
|
||||||
chanMap, filter uint8) error {
|
|
||||||
|
|
||||||
var b [15]byte
|
|
||||||
binary.LittleEndian.PutUint16(b[0:], minInterval)
|
|
||||||
binary.LittleEndian.PutUint16(b[2:], maxInterval)
|
|
||||||
b[4] = advType
|
|
||||||
b[5] = ownBdaddrType
|
|
||||||
b[6] = directBdaddrType
|
|
||||||
copy(b[7:], directBdaddr[:])
|
|
||||||
b[13] = chanMap
|
|
||||||
b[14] = filter
|
|
||||||
|
|
||||||
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertisingParameters, b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) leSetAdvertisingData(data []byte) error {
|
|
||||||
var b [32]byte
|
|
||||||
b[0] = byte(len(data))
|
|
||||||
copy(b[1:], data)
|
|
||||||
|
|
||||||
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertisingData, b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) leSetScanResponseData(data []byte) error {
|
|
||||||
var b [32]byte
|
|
||||||
b[0] = byte(len(data))
|
|
||||||
copy(b[1:], data)
|
|
||||||
|
|
||||||
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetScanResponseData, b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) leCreateConn(interval, window uint16,
|
|
||||||
initiatorFilter, peerBdaddrType uint8, peerBdaddr [6]byte, ownBdaddrType uint8,
|
|
||||||
minInterval, maxInterval, latency, supervisionTimeout,
|
|
||||||
minCeLength, maxCeLength uint16) error {
|
|
||||||
|
|
||||||
var b [25]byte
|
|
||||||
binary.LittleEndian.PutUint16(b[0:], interval)
|
|
||||||
binary.LittleEndian.PutUint16(b[2:], window)
|
|
||||||
b[4] = initiatorFilter
|
|
||||||
b[5] = peerBdaddrType
|
|
||||||
copy(b[6:], peerBdaddr[:])
|
|
||||||
b[12] = ownBdaddrType
|
|
||||||
binary.LittleEndian.PutUint16(b[13:], minInterval)
|
|
||||||
binary.LittleEndian.PutUint16(b[15:], maxInterval)
|
|
||||||
binary.LittleEndian.PutUint16(b[17:], latency)
|
|
||||||
binary.LittleEndian.PutUint16(b[19:], supervisionTimeout)
|
|
||||||
binary.LittleEndian.PutUint16(b[21:], minCeLength)
|
|
||||||
binary.LittleEndian.PutUint16(b[23:], maxCeLength)
|
|
||||||
|
|
||||||
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLECreateConn, b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) leCancelConn() error {
|
|
||||||
return h.sendCommand(ogfLECtrl<<ogfCommandPos | ocfLECancelConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) leConnUpdate(handle uint16, minInterval, maxInterval,
|
|
||||||
latency, supervisionTimeout uint16) error {
|
|
||||||
|
|
||||||
var b [14]byte
|
|
||||||
binary.LittleEndian.PutUint16(b[0:], handle)
|
|
||||||
binary.LittleEndian.PutUint16(b[2:], minInterval)
|
|
||||||
binary.LittleEndian.PutUint16(b[4:], maxInterval)
|
|
||||||
binary.LittleEndian.PutUint16(b[6:], latency)
|
|
||||||
binary.LittleEndian.PutUint16(b[8:], supervisionTimeout)
|
|
||||||
binary.LittleEndian.PutUint16(b[10:], 0x0004)
|
|
||||||
binary.LittleEndian.PutUint16(b[12:], 0x0006)
|
|
||||||
|
|
||||||
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLEConnUpdate, b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) disconnect(handle uint16) error {
|
|
||||||
var b [3]byte
|
|
||||||
binary.LittleEndian.PutUint16(b[0:], handle)
|
|
||||||
b[2] = hciOEUserEndedConnection
|
|
||||||
|
|
||||||
return h.sendCommandWithParams(ogfLinkCtl<<ogfCommandPos|ocfDisconnect, b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) sendCommand(opcode uint16) error {
|
|
||||||
return h.sendCommandWithParams(opcode, []byte{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) sendCommandWithParams(opcode uint16, params []byte) error {
|
|
||||||
if debug {
|
|
||||||
println("hci send command", opcode, hex.EncodeToString(params))
|
|
||||||
}
|
|
||||||
|
|
||||||
h.buf[0] = hciCommandPkt
|
|
||||||
binary.LittleEndian.PutUint16(h.buf[1:], opcode)
|
|
||||||
h.buf[3] = byte(len(params))
|
|
||||||
copy(h.buf[4:], params)
|
|
||||||
|
|
||||||
if _, err := h.write(h.buf[:4+len(params)]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.cmdCompleteOpcode = 0xffff
|
|
||||||
h.cmdCompleteStatus = 0xff
|
|
||||||
|
|
||||||
start := time.Now().UnixNano()
|
|
||||||
for h.cmdCompleteOpcode != opcode {
|
|
||||||
if err := h.poll(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if (time.Now().UnixNano()-start)/int64(time.Second) > 3 {
|
|
||||||
return ErrHCITimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) sendWithoutResponse(opcode uint16, params []byte) error {
|
|
||||||
if debug {
|
|
||||||
println("hci send without response command", opcode, hex.EncodeToString(params))
|
|
||||||
}
|
|
||||||
|
|
||||||
h.buf[0] = hciCommandPkt
|
|
||||||
binary.LittleEndian.PutUint16(h.buf[1:], opcode)
|
|
||||||
h.buf[3] = byte(len(params))
|
|
||||||
copy(h.buf[4:], params)
|
|
||||||
|
|
||||||
if _, err := h.write(h.buf[:4+len(params)]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.cmdCompleteOpcode = 0xffff
|
|
||||||
h.cmdCompleteStatus = 0xff
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) sendAclPkt(handle uint16, cid uint8, data []byte) error {
|
|
||||||
h.buf[0] = hciACLDataPkt
|
|
||||||
binary.LittleEndian.PutUint16(h.buf[1:], handle)
|
|
||||||
binary.LittleEndian.PutUint16(h.buf[3:], uint16(len(data)+4))
|
|
||||||
binary.LittleEndian.PutUint16(h.buf[5:], uint16(len(data)))
|
|
||||||
binary.LittleEndian.PutUint16(h.buf[7:], uint16(cid))
|
|
||||||
|
|
||||||
copy(h.buf[9:], data)
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println("hci send acl data", handle, cid, hex.EncodeToString(h.buf[:9+len(data)]))
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := h.write(h.buf[:9+len(data)]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.pendingPkt++
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) write(buf []byte) (int, error) {
|
|
||||||
return h.transport.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
type aclDataHeader struct {
|
|
||||||
handle uint16
|
|
||||||
dlen uint16
|
|
||||||
len uint16
|
|
||||||
cid uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) handleACLData(buf []byte) error {
|
|
||||||
aclHdr := aclDataHeader{
|
|
||||||
handle: binary.LittleEndian.Uint16(buf[0:]),
|
|
||||||
dlen: binary.LittleEndian.Uint16(buf[2:]),
|
|
||||||
len: binary.LittleEndian.Uint16(buf[4:]),
|
|
||||||
cid: binary.LittleEndian.Uint16(buf[6:]),
|
|
||||||
}
|
|
||||||
|
|
||||||
aclFlags := (aclHdr.handle & 0xf000) >> 12
|
|
||||||
if aclHdr.dlen-4 != aclHdr.len {
|
|
||||||
return errors.New("fragmented packet")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch aclHdr.cid {
|
|
||||||
case attCID:
|
|
||||||
if aclFlags == 0x01 {
|
|
||||||
// TODO: use buffered packet
|
|
||||||
if debug {
|
|
||||||
println("WARNING: att.handleACLData needs buffered packet")
|
|
||||||
}
|
|
||||||
return h.att.handleData(aclHdr.handle&0x0fff, buf[8:aclHdr.len+8])
|
|
||||||
} else {
|
|
||||||
return h.att.handleData(aclHdr.handle&0x0fff, buf[8:aclHdr.len+8])
|
|
||||||
}
|
|
||||||
case signalingCID:
|
|
||||||
if debug {
|
|
||||||
println("signaling cid", aclHdr.cid, hex.EncodeToString(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.l2cap.handleData(aclHdr.handle&0x0fff, buf[8:aclHdr.len+8])
|
|
||||||
|
|
||||||
default:
|
|
||||||
if debug {
|
|
||||||
println("unknown acl data cid", aclHdr.cid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) handleEventData(buf []byte) error {
|
|
||||||
evt := buf[0]
|
|
||||||
plen := buf[1]
|
|
||||||
|
|
||||||
switch evt {
|
|
||||||
case evtDisconnComplete:
|
|
||||||
if debug {
|
|
||||||
println("evtDisconnComplete")
|
|
||||||
}
|
|
||||||
|
|
||||||
handle := binary.LittleEndian.Uint16(buf[3:])
|
|
||||||
h.att.removeConnection(handle)
|
|
||||||
h.l2cap.removeConnection(handle)
|
|
||||||
|
|
||||||
return h.leSetAdvertiseEnable(true)
|
|
||||||
|
|
||||||
case evtEncryptionChange:
|
|
||||||
if debug {
|
|
||||||
println("evtEncryptionChange")
|
|
||||||
}
|
|
||||||
|
|
||||||
case evtCmdComplete:
|
|
||||||
h.cmdCompleteOpcode = binary.LittleEndian.Uint16(buf[3:])
|
|
||||||
h.cmdCompleteStatus = buf[5]
|
|
||||||
if plen > 0 {
|
|
||||||
h.cmdResponse = buf[1 : plen+2]
|
|
||||||
} else {
|
|
||||||
h.cmdResponse = buf[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println("evtCmdComplete", h.cmdCompleteOpcode, h.cmdCompleteStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case evtCmdStatus:
|
|
||||||
h.cmdCompleteStatus = buf[2]
|
|
||||||
h.cmdCompleteOpcode = binary.LittleEndian.Uint16(buf[4:])
|
|
||||||
if debug {
|
|
||||||
println("evtCmdStatus", h.cmdCompleteOpcode, h.cmdCompleteOpcode, h.cmdCompleteStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.cmdResponse = buf[:0]
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case evtNumCompPkts:
|
|
||||||
if debug {
|
|
||||||
println("evtNumCompPkts", hex.EncodeToString(buf))
|
|
||||||
}
|
|
||||||
// count of handles
|
|
||||||
c := buf[2]
|
|
||||||
pkts := uint16(0)
|
|
||||||
|
|
||||||
for i := byte(0); i < c; i++ {
|
|
||||||
pkts += binary.LittleEndian.Uint16(buf[5+i*4:])
|
|
||||||
}
|
|
||||||
|
|
||||||
if pkts > 0 && h.pendingPkt > pkts {
|
|
||||||
h.pendingPkt -= pkts
|
|
||||||
} else {
|
|
||||||
h.pendingPkt = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println("evtNumCompPkts", pkts, h.pendingPkt)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case evtLEMetaEvent:
|
|
||||||
if debug {
|
|
||||||
println("evtLEMetaEvent")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch buf[2] {
|
|
||||||
case leMetaEventConnComplete, leMetaEventEnhancedConnectionComplete:
|
|
||||||
if debug {
|
|
||||||
println("leMetaEventConnComplete")
|
|
||||||
}
|
|
||||||
|
|
||||||
h.connectData.connected = true
|
|
||||||
h.connectData.status = buf[3]
|
|
||||||
h.connectData.handle = binary.LittleEndian.Uint16(buf[4:])
|
|
||||||
h.connectData.role = buf[6]
|
|
||||||
h.connectData.peerBdaddrType = buf[7]
|
|
||||||
copy(h.connectData.peerBdaddr[0:], buf[8:])
|
|
||||||
|
|
||||||
switch buf[2] {
|
|
||||||
case leMetaEventConnComplete:
|
|
||||||
h.connectData.interval = binary.LittleEndian.Uint16(buf[14:])
|
|
||||||
h.connectData.timeout = binary.LittleEndian.Uint16(buf[16:])
|
|
||||||
case leMetaEventEnhancedConnectionComplete:
|
|
||||||
h.connectData.interval = binary.LittleEndian.Uint16(buf[26:])
|
|
||||||
h.connectData.timeout = binary.LittleEndian.Uint16(buf[28:])
|
|
||||||
}
|
|
||||||
|
|
||||||
h.att.addConnection(h.connectData.handle)
|
|
||||||
if err := h.l2cap.addConnection(h.connectData.handle, h.connectData.role,
|
|
||||||
h.connectData.interval, h.connectData.timeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.leSetAdvertiseEnable(false)
|
|
||||||
|
|
||||||
case leMetaEventAdvertisingReport:
|
|
||||||
h.advData.reported = true
|
|
||||||
h.advData.numReports = buf[3]
|
|
||||||
h.advData.typ = buf[4]
|
|
||||||
h.advData.peerBdaddrType = buf[5]
|
|
||||||
copy(h.advData.peerBdaddr[0:], buf[6:])
|
|
||||||
h.advData.eirLength = buf[12]
|
|
||||||
h.advData.rssi = 0
|
|
||||||
if debug {
|
|
||||||
println("leMetaEventAdvertisingReport", plen, h.advData.numReports,
|
|
||||||
h.advData.typ, h.advData.peerBdaddrType, h.advData.eirLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
if int(13+h.advData.eirLength+1) > len(buf) || h.advData.eirLength > 31 {
|
|
||||||
if debug {
|
|
||||||
println("invalid packet length", h.advData.eirLength, len(buf))
|
|
||||||
}
|
|
||||||
return ErrHCIInvalidPacket
|
|
||||||
}
|
|
||||||
copy(h.advData.eirData[0:h.advData.eirLength], buf[13:13+h.advData.eirLength])
|
|
||||||
|
|
||||||
// TODO: handle multiple reports
|
|
||||||
if h.advData.numReports == 0x01 {
|
|
||||||
h.advData.rssi = int8(buf[int(13+h.advData.eirLength)])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case leMetaEventLongTermKeyRequest:
|
|
||||||
if debug {
|
|
||||||
println("leMetaEventLongTermKeyRequest")
|
|
||||||
}
|
|
||||||
|
|
||||||
case leMetaEventRemoteConnParamReq:
|
|
||||||
if debug {
|
|
||||||
println("leMetaEventRemoteConnParamReq")
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionHandle := binary.LittleEndian.Uint16(buf[3:])
|
|
||||||
intervalMin := binary.LittleEndian.Uint16(buf[5:])
|
|
||||||
intervalMax := binary.LittleEndian.Uint16(buf[7:])
|
|
||||||
latency := binary.LittleEndian.Uint16(buf[9:])
|
|
||||||
timeOut := binary.LittleEndian.Uint16(buf[11:])
|
|
||||||
|
|
||||||
var b [14]byte
|
|
||||||
binary.LittleEndian.PutUint16(b[0:], connectionHandle)
|
|
||||||
binary.LittleEndian.PutUint16(b[2:], intervalMin)
|
|
||||||
binary.LittleEndian.PutUint16(b[4:], intervalMax)
|
|
||||||
binary.LittleEndian.PutUint16(b[6:], latency)
|
|
||||||
binary.LittleEndian.PutUint16(b[8:], timeOut)
|
|
||||||
binary.LittleEndian.PutUint16(b[10:], 0x000F)
|
|
||||||
binary.LittleEndian.PutUint16(b[12:], 0x0FFF)
|
|
||||||
|
|
||||||
return h.sendWithoutResponse(ogfLECtrl<<10|ocfLEParamRequestReply, b[:])
|
|
||||||
|
|
||||||
case leMetaEventConnectionUpdateComplete:
|
|
||||||
if debug {
|
|
||||||
println("leMetaEventConnectionUpdateComplete")
|
|
||||||
}
|
|
||||||
|
|
||||||
case leMetaEventReadLocalP256Complete:
|
|
||||||
if debug {
|
|
||||||
println("leMetaEventReadLocalP256Complete")
|
|
||||||
}
|
|
||||||
|
|
||||||
case leMetaEventGenerateDHKeyComplete:
|
|
||||||
if debug {
|
|
||||||
println("leMetaEventGenerateDHKeyComplete")
|
|
||||||
}
|
|
||||||
|
|
||||||
case leMetaEventDataLengthChange:
|
|
||||||
if debug {
|
|
||||||
println("leMetaEventDataLengthChange")
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if debug {
|
|
||||||
println("unknown metaevent", buf[2], buf[3], buf[4], buf[5])
|
|
||||||
}
|
|
||||||
|
|
||||||
h.clearAdvData()
|
|
||||||
return ErrHCIUnknownEvent
|
|
||||||
}
|
|
||||||
case evtHardwareError:
|
|
||||||
if debug {
|
|
||||||
println("evtHardwareError", hex.EncodeToString(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrHCIUnknownEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) clearAdvData() error {
|
|
||||||
h.advData.reported = false
|
|
||||||
h.advData.numReports = 0
|
|
||||||
h.advData.typ = 0
|
|
||||||
h.advData.peerBdaddrType = 0
|
|
||||||
h.advData.peerBdaddr = [6]uint8{}
|
|
||||||
h.advData.eirLength = 0
|
|
||||||
h.advData.eirData = [31]uint8{}
|
|
||||||
h.advData.rssi = 0
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hci) clearConnectData() error {
|
|
||||||
h.connectData.connected = false
|
|
||||||
h.connectData.status = 0
|
|
||||||
h.connectData.handle = 0
|
|
||||||
h.connectData.role = 0
|
|
||||||
h.connectData.peerBdaddrType = 0
|
|
||||||
h.connectData.peerBdaddr = [6]uint8{}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
156
l2cap_hci.go
156
l2cap_hci.go
|
@ -1,156 +0,0 @@
|
||||||
//go:build ninafw || hci || cyw43439
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
connectionParamUpdateRequest = 0x12
|
|
||||||
connectionParamUpdateResponse = 0x13
|
|
||||||
)
|
|
||||||
|
|
||||||
type l2capConnectionParamReqPkt struct {
|
|
||||||
minInterval uint16
|
|
||||||
maxInterval uint16
|
|
||||||
latency uint16
|
|
||||||
timeout uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *l2capConnectionParamReqPkt) Write(buf []byte) (int, error) {
|
|
||||||
l.minInterval = binary.LittleEndian.Uint16(buf[0:])
|
|
||||||
l.maxInterval = binary.LittleEndian.Uint16(buf[2:])
|
|
||||||
l.latency = binary.LittleEndian.Uint16(buf[4:])
|
|
||||||
l.timeout = binary.LittleEndian.Uint16(buf[6:])
|
|
||||||
|
|
||||||
return 8, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *l2capConnectionParamReqPkt) Read(p []byte) (int, error) {
|
|
||||||
binary.LittleEndian.PutUint16(p[0:], l.minInterval)
|
|
||||||
binary.LittleEndian.PutUint16(p[2:], l.maxInterval)
|
|
||||||
binary.LittleEndian.PutUint16(p[4:], l.latency)
|
|
||||||
binary.LittleEndian.PutUint16(p[6:], l.timeout)
|
|
||||||
|
|
||||||
return 8, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type l2capConnectionParamResponsePkt struct {
|
|
||||||
code uint8
|
|
||||||
identifier uint8
|
|
||||||
length uint16
|
|
||||||
value uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *l2capConnectionParamResponsePkt) Read(p []byte) (int, error) {
|
|
||||||
p[0] = l.code
|
|
||||||
p[1] = l.identifier
|
|
||||||
binary.LittleEndian.PutUint16(p[2:], l.length)
|
|
||||||
binary.LittleEndian.PutUint16(p[4:], l.value)
|
|
||||||
|
|
||||||
return 6, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type l2cap struct {
|
|
||||||
hci *hci
|
|
||||||
}
|
|
||||||
|
|
||||||
func newL2CAP(hci *hci) *l2cap {
|
|
||||||
return &l2cap{
|
|
||||||
hci: hci,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *l2cap) addConnection(handle uint16, role uint8, interval, timeout uint16) error {
|
|
||||||
if role != 0x01 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var b [12]byte
|
|
||||||
b[0] = connectionParamUpdateRequest
|
|
||||||
b[1] = 0x01
|
|
||||||
binary.LittleEndian.PutUint16(b[2:], 8)
|
|
||||||
binary.LittleEndian.PutUint16(b[4:], interval)
|
|
||||||
binary.LittleEndian.PutUint16(b[6:], interval)
|
|
||||||
binary.LittleEndian.PutUint16(b[8:], 0)
|
|
||||||
binary.LittleEndian.PutUint16(b[10:], timeout)
|
|
||||||
|
|
||||||
return l.sendReq(handle, b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *l2cap) removeConnection(handle uint16) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *l2cap) handleData(handle uint16, buf []byte) error {
|
|
||||||
code := buf[0]
|
|
||||||
identifier := buf[1]
|
|
||||||
//length := binary.LittleEndian.Uint16(buf[2:4])
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println("l2cap.handleData:", handle, "data:", hex.EncodeToString(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check length
|
|
||||||
|
|
||||||
switch code {
|
|
||||||
case connectionParamUpdateRequest:
|
|
||||||
return l.handleParameterUpdateRequest(handle, identifier, buf[4:])
|
|
||||||
|
|
||||||
case connectionParamUpdateResponse:
|
|
||||||
return l.handleParameterUpdateResponse(handle, identifier, buf[4:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *l2cap) handleParameterUpdateRequest(connectionHandle uint16, identifier uint8, data []byte) error {
|
|
||||||
if debug {
|
|
||||||
println("l2cap.handleParameterUpdateRequest:", connectionHandle, "data:", hex.EncodeToString(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
req := l2capConnectionParamReqPkt{}
|
|
||||||
req.Write(data)
|
|
||||||
|
|
||||||
// TODO: check against min/max
|
|
||||||
|
|
||||||
resp := l2capConnectionParamResponsePkt{
|
|
||||||
code: connectionParamUpdateResponse,
|
|
||||||
identifier: identifier,
|
|
||||||
length: 2,
|
|
||||||
value: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
var b [6]byte
|
|
||||||
resp.Read(b[:])
|
|
||||||
|
|
||||||
if err := l.sendReq(connectionHandle, b[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// valid so update connection parameters
|
|
||||||
if resp.value == 0 {
|
|
||||||
return l.hci.leConnUpdate(connectionHandle, req.minInterval, req.maxInterval, req.latency, req.timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *l2cap) handleParameterUpdateResponse(connectionHandle uint16, identifier uint8, data []byte) error {
|
|
||||||
if debug {
|
|
||||||
println("l2cap.handleParameterUpdateResponse:", connectionHandle, "data:", hex.EncodeToString(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// for now do nothing
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *l2cap) sendReq(handle uint16, data []byte) error {
|
|
||||||
if debug {
|
|
||||||
println("l2cap.sendReq:", handle, "data:", hex.EncodeToString(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
return l.hci.sendAclPkt(handle, signalingCID, data)
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
//go:build !bledebug
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
var debug = false
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build (linux && !baremetal) || darwin
|
// +build linux,!baremetal darwin
|
||||||
|
|
||||||
// Package rawterm provides some sort of raw terminal interface, both on hosted
|
// Package rawterm provides some sort of raw terminal interface, both on hosted
|
||||||
// systems and baremetal. It is intended only for use by examples.
|
// systems and baremetal. It is intended only for use by examples.
|
||||||
|
@ -43,9 +43,9 @@ func Putchar(ch byte) {
|
||||||
// Getchar/Putchar). It must be restored after use with Restore. You can do this
|
// Getchar/Putchar). It must be restored after use with Restore. You can do this
|
||||||
// with the following code:
|
// with the following code:
|
||||||
//
|
//
|
||||||
// rawterm.Configure()
|
// rawterm.Configure()
|
||||||
// defer rawterm.Restore()
|
// defer rawterm.Restore()
|
||||||
// // use raw terminal features
|
// // use raw terminal features
|
||||||
func Configure() {
|
func Configure() {
|
||||||
terminalState, _ = terminal.MakeRaw(0)
|
terminalState, _ = terminal.MakeRaw(0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build nrf
|
// +build nrf
|
||||||
|
|
||||||
package rawterm
|
package rawterm
|
||||||
|
|
||||||
|
@ -42,9 +42,9 @@ func Putchar(ch byte) {
|
||||||
// Getchar/Putchar). It must be restored after use with Restore. You can do this
|
// Getchar/Putchar). It must be restored after use with Restore. You can do this
|
||||||
// with the following code:
|
// with the following code:
|
||||||
//
|
//
|
||||||
// rawterm.Configure()
|
// rawterm.Configure()
|
||||||
// defer rawterm.Restore()
|
// defer rawterm.Restore()
|
||||||
// // use raw terminal features
|
// // use raw terminal features
|
||||||
func Configure() {
|
func Configure() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
// Code generated by bin/gen-service-uuids; DO NOT EDIT.
|
// Code generated by bin/gen-service-uuids; DO NOT EDIT.
|
||||||
// This file was generated on 2022-12-21 19:21:51.011665984 +0100 CET m=+0.000615122 using the list of standard service UUIDs from
|
// This file was generated on 2022-12-21 19:21:51.011665984 +0100 CET m=+0.000615122 using the list of standard service UUIDs from
|
||||||
// https://github.com/NordicSemiconductor/bluetooth-numbers-database/blob/master/v1/service_uuids.json
|
// https://github.com/NordicSemiconductor/bluetooth-numbers-database/blob/master/v1/service_uuids.json
|
||||||
|
//
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
||||||
// ServiceUUIDExperimentalButtonlessDFU - Experimental Buttonless DFU Service
|
// ServiceUUIDExperimentalButtonlessDFU - Experimental Buttonless DFU Service
|
||||||
ServiceUUIDExperimentalButtonlessDFU = NewUUID([16]byte{0x8e, 0x40, 0x00, 0x01, 0xf3, 0x15, 0x4f, 0x60, 0x9f, 0xb8, 0x83, 0x88, 0x30, 0xda, 0xea, 0x50})
|
ServiceUUIDExperimentalButtonlessDFU = NewUUID([16]byte{0x8e,0x40,0x00,0x01,0xf3,0x15,0x4f,0x60,0x9f,0xb8,0x83,0x88,0x30,0xda,0xea,0x50,})
|
||||||
|
|
||||||
// ServiceUUIDAdafruitTemperature - Adafruit Temperature Service
|
// ServiceUUIDAdafruitTemperature - Adafruit Temperature Service
|
||||||
ServiceUUIDAdafruitTemperature = NewUUID([16]byte{0xad, 0xaf, 0x01, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitTemperature = NewUUID([16]byte{0xad,0xaf,0x01,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDAdafruitHumidity - Adafruit Humidity Service
|
// ServiceUUIDAdafruitHumidity - Adafruit Humidity Service
|
||||||
ServiceUUIDAdafruitHumidity = NewUUID([16]byte{0xad, 0xaf, 0x07, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitHumidity = NewUUID([16]byte{0xad,0xaf,0x07,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDUserData - User Data
|
// ServiceUUIDUserData - User Data
|
||||||
ServiceUUIDUserData = New16BitUUID(0x181C)
|
ServiceUUIDUserData = New16BitUUID(0x181C)
|
||||||
|
@ -21,16 +22,16 @@ var (
|
||||||
ServiceUUIDAudioStreamControl = New16BitUUID(0x184E)
|
ServiceUUIDAudioStreamControl = New16BitUUID(0x184E)
|
||||||
|
|
||||||
// ServiceUUIDSMP - SMP Service
|
// ServiceUUIDSMP - SMP Service
|
||||||
ServiceUUIDSMP = NewUUID([16]byte{0x8d, 0x53, 0xdc, 0x1d, 0x1d, 0xb7, 0x4c, 0xd3, 0x86, 0x8b, 0x8a, 0x52, 0x74, 0x60, 0xaa, 0x84})
|
ServiceUUIDSMP = NewUUID([16]byte{0x8d,0x53,0xdc,0x1d,0x1d,0xb7,0x4c,0xd3,0x86,0x8b,0x8a,0x52,0x74,0x60,0xaa,0x84,})
|
||||||
|
|
||||||
// ServiceUUIDAdafruitSound - Adafruit Sound Service
|
// ServiceUUIDAdafruitSound - Adafruit Sound Service
|
||||||
ServiceUUIDAdafruitSound = NewUUID([16]byte{0xad, 0xaf, 0x0b, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitSound = NewUUID([16]byte{0xad,0xaf,0x0b,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDPulseOximeter - Pulse Oximeter Service
|
// ServiceUUIDPulseOximeter - Pulse Oximeter Service
|
||||||
ServiceUUIDPulseOximeter = New16BitUUID(0x1822)
|
ServiceUUIDPulseOximeter = New16BitUUID(0x1822)
|
||||||
|
|
||||||
// ServiceUUIDEddystoneConfiguration - Eddystone Configuration Service
|
// ServiceUUIDEddystoneConfiguration - Eddystone Configuration Service
|
||||||
ServiceUUIDEddystoneConfiguration = NewUUID([16]byte{0xa3, 0xc8, 0x75, 0x00, 0x8e, 0xd3, 0x4b, 0xdf, 0x8a, 0x39, 0xa0, 0x1b, 0xeb, 0xed, 0xe2, 0x95})
|
ServiceUUIDEddystoneConfiguration = NewUUID([16]byte{0xa3,0xc8,0x75,0x00,0x8e,0xd3,0x4b,0xdf,0x8a,0x39,0xa0,0x1b,0xeb,0xed,0xe2,0x95,})
|
||||||
|
|
||||||
// ServiceUUIDLocationAndNavigation - Location and Navigation
|
// ServiceUUIDLocationAndNavigation - Location and Navigation
|
||||||
ServiceUUIDLocationAndNavigation = New16BitUUID(0x1819)
|
ServiceUUIDLocationAndNavigation = New16BitUUID(0x1819)
|
||||||
|
@ -45,10 +46,10 @@ var (
|
||||||
ServiceUUIDSignifyNetherlandsBVFormerlyPhilipsLighting = New16BitUUID(0xFE0F)
|
ServiceUUIDSignifyNetherlandsBVFormerlyPhilipsLighting = New16BitUUID(0xFE0F)
|
||||||
|
|
||||||
// ServiceUUIDPhilipsHueLightControl - Philips Hue Light Control Service
|
// ServiceUUIDPhilipsHueLightControl - Philips Hue Light Control Service
|
||||||
ServiceUUIDPhilipsHueLightControl = NewUUID([16]byte{0x93, 0x2c, 0x32, 0xbd, 0x00, 0x00, 0x47, 0xa2, 0x83, 0x5a, 0xa8, 0xd4, 0x55, 0xb8, 0x59, 0xdd})
|
ServiceUUIDPhilipsHueLightControl = NewUUID([16]byte{0x93,0x2c,0x32,0xbd,0x00,0x00,0x47,0xa2,0x83,0x5a,0xa8,0xd4,0x55,0xb8,0x59,0xdd,})
|
||||||
|
|
||||||
// ServiceUUIDThingyUI - Thingy UI Service
|
// ServiceUUIDThingyUI - Thingy UI Service
|
||||||
ServiceUUIDThingyUI = NewUUID([16]byte{0xef, 0x68, 0x03, 0x00, 0x9b, 0x35, 0x49, 0x33, 0x9b, 0x10, 0x52, 0xff, 0xa9, 0x74, 0x00, 0x42})
|
ServiceUUIDThingyUI = NewUUID([16]byte{0xef,0x68,0x03,0x00,0x9b,0x35,0x49,0x33,0x9b,0x10,0x52,0xff,0xa9,0x74,0x00,0x42,})
|
||||||
|
|
||||||
// ServiceUUIDAlertNotification - Alert Notification Service
|
// ServiceUUIDAlertNotification - Alert Notification Service
|
||||||
ServiceUUIDAlertNotification = New16BitUUID(0x1811)
|
ServiceUUIDAlertNotification = New16BitUUID(0x1811)
|
||||||
|
@ -63,16 +64,16 @@ var (
|
||||||
ServiceUUIDGenericAttribute = New16BitUUID(0x1801)
|
ServiceUUIDGenericAttribute = New16BitUUID(0x1801)
|
||||||
|
|
||||||
// ServiceUUIDAdafruitMagnetometer - Adafruit Magnetometer Service
|
// ServiceUUIDAdafruitMagnetometer - Adafruit Magnetometer Service
|
||||||
ServiceUUIDAdafruitMagnetometer = NewUUID([16]byte{0xad, 0xaf, 0x05, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitMagnetometer = NewUUID([16]byte{0xad,0xaf,0x05,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDMicrophoneControl - Microphone Control
|
// ServiceUUIDMicrophoneControl - Microphone Control
|
||||||
ServiceUUIDMicrophoneControl = New16BitUUID(0x184D)
|
ServiceUUIDMicrophoneControl = New16BitUUID(0x184D)
|
||||||
|
|
||||||
// ServiceUUIDLEGOWirelessProtocolV3Bootloader - LEGO® Wireless Protocol v3 Bootloader Service
|
// ServiceUUIDLEGOWirelessProtocolV3Bootloader - LEGO® Wireless Protocol v3 Bootloader Service
|
||||||
ServiceUUIDLEGOWirelessProtocolV3Bootloader = NewUUID([16]byte{0x00, 0x00, 0x16, 0x25, 0x12, 0x12, 0xef, 0xde, 0x16, 0x23, 0x78, 0x5f, 0xea, 0xbc, 0xd1, 0x23})
|
ServiceUUIDLEGOWirelessProtocolV3Bootloader = NewUUID([16]byte{0x00,0x00,0x16,0x25,0x12,0x12,0xef,0xde,0x16,0x23,0x78,0x5f,0xea,0xbc,0xd1,0x23,})
|
||||||
|
|
||||||
// ServiceUUIDAdafruitColor - Adafruit Color Service
|
// ServiceUUIDAdafruitColor - Adafruit Color Service
|
||||||
ServiceUUIDAdafruitColor = NewUUID([16]byte{0xad, 0xaf, 0x0a, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitColor = NewUUID([16]byte{0xad,0xaf,0x0a,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDMeshProvisioning - Mesh Provisioning Service
|
// ServiceUUIDMeshProvisioning - Mesh Provisioning Service
|
||||||
ServiceUUIDMeshProvisioning = New16BitUUID(0x1827)
|
ServiceUUIDMeshProvisioning = New16BitUUID(0x1827)
|
||||||
|
@ -81,22 +82,22 @@ var (
|
||||||
ServiceUUIDTransportDiscovery = New16BitUUID(0x1824)
|
ServiceUUIDTransportDiscovery = New16BitUUID(0x1824)
|
||||||
|
|
||||||
// ServiceUUIDAppleReserved1 - Apple Reserved Service 1
|
// ServiceUUIDAppleReserved1 - Apple Reserved Service 1
|
||||||
ServiceUUIDAppleReserved1 = NewUUID([16]byte{0x7d, 0xfc, 0x60, 0x00, 0x7d, 0x1c, 0x49, 0x51, 0x86, 0xaa, 0x8d, 0x97, 0x28, 0xf8, 0xd6, 0x6c})
|
ServiceUUIDAppleReserved1 = NewUUID([16]byte{0x7d,0xfc,0x60,0x00,0x7d,0x1c,0x49,0x51,0x86,0xaa,0x8d,0x97,0x28,0xf8,0xd6,0x6c,})
|
||||||
|
|
||||||
// ServiceUUIDAppleReserved2 - Apple Reserved Service 2
|
// ServiceUUIDAppleReserved2 - Apple Reserved Service 2
|
||||||
ServiceUUIDAppleReserved2 = NewUUID([16]byte{0x7d, 0xfc, 0x70, 0x00, 0x7d, 0x1c, 0x49, 0x51, 0x86, 0xaa, 0x8d, 0x97, 0x28, 0xf8, 0xd6, 0x6c})
|
ServiceUUIDAppleReserved2 = NewUUID([16]byte{0x7d,0xfc,0x70,0x00,0x7d,0x1c,0x49,0x51,0x86,0xaa,0x8d,0x97,0x28,0xf8,0xd6,0x6c,})
|
||||||
|
|
||||||
// ServiceUUIDAppleReserved3 - Apple Reserved Service 3
|
// ServiceUUIDAppleReserved3 - Apple Reserved Service 3
|
||||||
ServiceUUIDAppleReserved3 = NewUUID([16]byte{0x7d, 0xfc, 0x80, 0x00, 0x7d, 0x1c, 0x49, 0x51, 0x86, 0xaa, 0x8d, 0x97, 0x28, 0xf8, 0xd6, 0x6c})
|
ServiceUUIDAppleReserved3 = NewUUID([16]byte{0x7d,0xfc,0x80,0x00,0x7d,0x1c,0x49,0x51,0x86,0xaa,0x8d,0x97,0x28,0xf8,0xd6,0x6c,})
|
||||||
|
|
||||||
// ServiceUUIDAppleReserved4 - Apple Reserved Service 4
|
// ServiceUUIDAppleReserved4 - Apple Reserved Service 4
|
||||||
ServiceUUIDAppleReserved4 = NewUUID([16]byte{0x7d, 0xfc, 0x90, 0x00, 0x7d, 0x1c, 0x49, 0x51, 0x86, 0xaa, 0x8d, 0x97, 0x28, 0xf8, 0xd6, 0x6c})
|
ServiceUUIDAppleReserved4 = NewUUID([16]byte{0x7d,0xfc,0x90,0x00,0x7d,0x1c,0x49,0x51,0x86,0xaa,0x8d,0x97,0x28,0xf8,0xd6,0x6c,})
|
||||||
|
|
||||||
// ServiceUUIDAdafruitButton - Adafruit Button Service
|
// ServiceUUIDAdafruitButton - Adafruit Button Service
|
||||||
ServiceUUIDAdafruitButton = NewUUID([16]byte{0xad, 0xaf, 0x06, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitButton = NewUUID([16]byte{0xad,0xaf,0x06,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDMemfaultDiagnostic - Memfault Diagnostic Service
|
// ServiceUUIDMemfaultDiagnostic - Memfault Diagnostic Service
|
||||||
ServiceUUIDMemfaultDiagnostic = NewUUID([16]byte{0x54, 0x22, 0x00, 0x00, 0xf6, 0xa5, 0x40, 0x07, 0xa3, 0x71, 0x72, 0x2f, 0x4e, 0xbd, 0x84, 0x36})
|
ServiceUUIDMemfaultDiagnostic = NewUUID([16]byte{0x54,0x22,0x00,0x00,0xf6,0xa5,0x40,0x07,0xa3,0x71,0x72,0x2f,0x4e,0xbd,0x84,0x36,})
|
||||||
|
|
||||||
// ServiceUUIDScanParameters - Scan Parameters
|
// ServiceUUIDScanParameters - Scan Parameters
|
||||||
ServiceUUIDScanParameters = New16BitUUID(0x1813)
|
ServiceUUIDScanParameters = New16BitUUID(0x1813)
|
||||||
|
@ -105,13 +106,13 @@ var (
|
||||||
ServiceUUIDCoordinatedSetIdentification = New16BitUUID(0x1846)
|
ServiceUUIDCoordinatedSetIdentification = New16BitUUID(0x1846)
|
||||||
|
|
||||||
// ServiceUUIDMicrobitAccelerometer - micro:bit Accelerometer Service
|
// ServiceUUIDMicrobitAccelerometer - micro:bit Accelerometer Service
|
||||||
ServiceUUIDMicrobitAccelerometer = NewUUID([16]byte{0xe9, 0x5d, 0x07, 0x53, 0x25, 0x1d, 0x47, 0x0a, 0xa0, 0x62, 0xfa, 0x19, 0x22, 0xdf, 0xa9, 0xa8})
|
ServiceUUIDMicrobitAccelerometer = NewUUID([16]byte{0xe9,0x5d,0x07,0x53,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8,})
|
||||||
|
|
||||||
// ServiceUUIDMicrobitEvent - micro:bit Event Service
|
// ServiceUUIDMicrobitEvent - micro:bit Event Service
|
||||||
ServiceUUIDMicrobitEvent = NewUUID([16]byte{0xe9, 0x5d, 0x93, 0xaf, 0x25, 0x1d, 0x47, 0x0a, 0xa0, 0x62, 0xfa, 0x19, 0x22, 0xdf, 0xa9, 0xa8})
|
ServiceUUIDMicrobitEvent = NewUUID([16]byte{0xe9,0x5d,0x93,0xaf,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8,})
|
||||||
|
|
||||||
// ServiceUUIDThingySound - Thingy Sound Service
|
// ServiceUUIDThingySound - Thingy Sound Service
|
||||||
ServiceUUIDThingySound = NewUUID([16]byte{0xef, 0x68, 0x05, 0x00, 0x9b, 0x35, 0x49, 0x33, 0x9b, 0x10, 0x52, 0xff, 0xa9, 0x74, 0x00, 0x42})
|
ServiceUUIDThingySound = NewUUID([16]byte{0xef,0x68,0x05,0x00,0x9b,0x35,0x49,0x33,0x9b,0x10,0x52,0xff,0xa9,0x74,0x00,0x42,})
|
||||||
|
|
||||||
// ServiceUUIDImmediateAlert - Immediate Alert
|
// ServiceUUIDImmediateAlert - Immediate Alert
|
||||||
ServiceUUIDImmediateAlert = New16BitUUID(0x1802)
|
ServiceUUIDImmediateAlert = New16BitUUID(0x1802)
|
||||||
|
@ -126,10 +127,10 @@ var (
|
||||||
ServiceUUIDVolumeControl = New16BitUUID(0x1844)
|
ServiceUUIDVolumeControl = New16BitUUID(0x1844)
|
||||||
|
|
||||||
// ServiceUUIDMicrobitMagnetometer - micro:bit Magnetometer Service
|
// ServiceUUIDMicrobitMagnetometer - micro:bit Magnetometer Service
|
||||||
ServiceUUIDMicrobitMagnetometer = NewUUID([16]byte{0xe9, 0x5d, 0xf2, 0xd8, 0x25, 0x1d, 0x47, 0x0a, 0xa0, 0x62, 0xfa, 0x19, 0x22, 0xdf, 0xa9, 0xa8})
|
ServiceUUIDMicrobitMagnetometer = NewUUID([16]byte{0xe9,0x5d,0xf2,0xd8,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8,})
|
||||||
|
|
||||||
// ServiceUUIDMicrobitLED - micro:bit LED Service
|
// ServiceUUIDMicrobitLED - micro:bit LED Service
|
||||||
ServiceUUIDMicrobitLED = NewUUID([16]byte{0xe9, 0x5d, 0xd9, 0x1d, 0x25, 0x1d, 0x47, 0x0a, 0xa0, 0x62, 0xfa, 0x19, 0x22, 0xdf, 0xa9, 0xa8})
|
ServiceUUIDMicrobitLED = NewUUID([16]byte{0xe9,0x5d,0xd9,0x1d,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8,})
|
||||||
|
|
||||||
// ServiceUUIDFileTransferByAdafruit - File Transfer Service by Adafruit
|
// ServiceUUIDFileTransferByAdafruit - File Transfer Service by Adafruit
|
||||||
ServiceUUIDFileTransferByAdafruit = New16BitUUID(0xFEBB)
|
ServiceUUIDFileTransferByAdafruit = New16BitUUID(0xFEBB)
|
||||||
|
@ -150,16 +151,16 @@ var (
|
||||||
ServiceUUIDFastPair = New16BitUUID(0xFE2C)
|
ServiceUUIDFastPair = New16BitUUID(0xFE2C)
|
||||||
|
|
||||||
// ServiceUUIDAdafruitGyroscope - Adafruit Gyroscope Service
|
// ServiceUUIDAdafruitGyroscope - Adafruit Gyroscope Service
|
||||||
ServiceUUIDAdafruitGyroscope = NewUUID([16]byte{0xad, 0xaf, 0x04, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitGyroscope = NewUUID([16]byte{0xad,0xaf,0x04,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDAdafruitProximity - Adafruit Proximity Service
|
// ServiceUUIDAdafruitProximity - Adafruit Proximity Service
|
||||||
ServiceUUIDAdafruitProximity = NewUUID([16]byte{0xad, 0xaf, 0x0e, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitProximity = NewUUID([16]byte{0xad,0xaf,0x0e,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDTelephoneBearer - Telephone Bearer
|
// ServiceUUIDTelephoneBearer - Telephone Bearer
|
||||||
ServiceUUIDTelephoneBearer = New16BitUUID(0x184B)
|
ServiceUUIDTelephoneBearer = New16BitUUID(0x184B)
|
||||||
|
|
||||||
// ServiceUUIDNordicLEDAndButton - Nordic LED and Button Service
|
// ServiceUUIDNordicLEDAndButton - Nordic LED and Button Service
|
||||||
ServiceUUIDNordicLEDAndButton = NewUUID([16]byte{0x00, 0x00, 0x15, 0x23, 0x12, 0x12, 0xef, 0xde, 0x15, 0x23, 0x78, 0x5f, 0xea, 0xbc, 0xd1, 0x23})
|
ServiceUUIDNordicLEDAndButton = NewUUID([16]byte{0x00,0x00,0x15,0x23,0x12,0x12,0xef,0xde,0x15,0x23,0x78,0x5f,0xea,0xbc,0xd1,0x23,})
|
||||||
|
|
||||||
// ServiceUUIDInternetProtocolSupport - Internet Protocol Support Service
|
// ServiceUUIDInternetProtocolSupport - Internet Protocol Support Service
|
||||||
ServiceUUIDInternetProtocolSupport = New16BitUUID(0x1820)
|
ServiceUUIDInternetProtocolSupport = New16BitUUID(0x1820)
|
||||||
|
@ -168,7 +169,7 @@ var (
|
||||||
ServiceUUIDPublishedAudioCapabilities = New16BitUUID(0x1850)
|
ServiceUUIDPublishedAudioCapabilities = New16BitUUID(0x1850)
|
||||||
|
|
||||||
// ServiceUUIDNordicUART - Nordic UART Service
|
// ServiceUUIDNordicUART - Nordic UART Service
|
||||||
ServiceUUIDNordicUART = NewUUID([16]byte{0x6e, 0x40, 0x00, 0x01, 0xb5, 0xa3, 0xf3, 0x93, 0xe0, 0xa9, 0xe5, 0x0e, 0x24, 0xdc, 0xca, 0x9e})
|
ServiceUUIDNordicUART = NewUUID([16]byte{0x6e,0x40,0x00,0x01,0xb5,0xa3,0xf3,0x93,0xe0,0xa9,0xe5,0x0e,0x24,0xdc,0xca,0x9e,})
|
||||||
|
|
||||||
// ServiceUUIDExposureNotification - Exposure Notification Service
|
// ServiceUUIDExposureNotification - Exposure Notification Service
|
||||||
ServiceUUIDExposureNotification = New16BitUUID(0xFD6F)
|
ServiceUUIDExposureNotification = New16BitUUID(0xFD6F)
|
||||||
|
@ -186,7 +187,7 @@ var (
|
||||||
ServiceUUIDConstantToneExtension = New16BitUUID(0x184A)
|
ServiceUUIDConstantToneExtension = New16BitUUID(0x184A)
|
||||||
|
|
||||||
// ServiceUUIDThingyWeatherStation - Thingy Weather Station Service
|
// ServiceUUIDThingyWeatherStation - Thingy Weather Station Service
|
||||||
ServiceUUIDThingyWeatherStation = NewUUID([16]byte{0xef, 0x68, 0x02, 0x00, 0x9b, 0x35, 0x49, 0x33, 0x9b, 0x10, 0x52, 0xff, 0xa9, 0x74, 0x00, 0x42})
|
ServiceUUIDThingyWeatherStation = NewUUID([16]byte{0xef,0x68,0x02,0x00,0x9b,0x35,0x49,0x33,0x9b,0x10,0x52,0xff,0xa9,0x74,0x00,0x42,})
|
||||||
|
|
||||||
// ServiceUUIDHealthThermometer - Health Thermometer
|
// ServiceUUIDHealthThermometer - Health Thermometer
|
||||||
ServiceUUIDHealthThermometer = New16BitUUID(0x1809)
|
ServiceUUIDHealthThermometer = New16BitUUID(0x1809)
|
||||||
|
@ -201,19 +202,19 @@ var (
|
||||||
ServiceUUIDReconnectionConfiguration = New16BitUUID(0x1829)
|
ServiceUUIDReconnectionConfiguration = New16BitUUID(0x1829)
|
||||||
|
|
||||||
// ServiceUUIDAdafruitQuaternion - Adafruit Quaternion Service
|
// ServiceUUIDAdafruitQuaternion - Adafruit Quaternion Service
|
||||||
ServiceUUIDAdafruitQuaternion = NewUUID([16]byte{0xad, 0xaf, 0x0d, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitQuaternion = NewUUID([16]byte{0xad,0xaf,0x0d,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDMeshProxy - Mesh Proxy Service
|
// ServiceUUIDMeshProxy - Mesh Proxy Service
|
||||||
ServiceUUIDMeshProxy = New16BitUUID(0x1828)
|
ServiceUUIDMeshProxy = New16BitUUID(0x1828)
|
||||||
|
|
||||||
// ServiceUUIDThingyMotion - Thingy Motion Service
|
// ServiceUUIDThingyMotion - Thingy Motion Service
|
||||||
ServiceUUIDThingyMotion = NewUUID([16]byte{0xef, 0x68, 0x04, 0x00, 0x9b, 0x35, 0x49, 0x33, 0x9b, 0x10, 0x52, 0xff, 0xa9, 0x74, 0x00, 0x42})
|
ServiceUUIDThingyMotion = NewUUID([16]byte{0xef,0x68,0x04,0x00,0x9b,0x35,0x49,0x33,0x9b,0x10,0x52,0xff,0xa9,0x74,0x00,0x42,})
|
||||||
|
|
||||||
// ServiceUUIDIndoorPositioning - Indoor Positioning
|
// ServiceUUIDIndoorPositioning - Indoor Positioning
|
||||||
ServiceUUIDIndoorPositioning = New16BitUUID(0x1821)
|
ServiceUUIDIndoorPositioning = New16BitUUID(0x1821)
|
||||||
|
|
||||||
// ServiceUUIDAdafruitAddressable - Adafruit Addressable Service
|
// ServiceUUIDAdafruitAddressable - Adafruit Addressable Service
|
||||||
ServiceUUIDAdafruitAddressable = NewUUID([16]byte{0xad, 0xaf, 0x09, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitAddressable = NewUUID([16]byte{0xad,0xaf,0x09,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDBloodPressure - Blood Pressure
|
// ServiceUUIDBloodPressure - Blood Pressure
|
||||||
ServiceUUIDBloodPressure = New16BitUUID(0x1810)
|
ServiceUUIDBloodPressure = New16BitUUID(0x1810)
|
||||||
|
@ -237,7 +238,7 @@ var (
|
||||||
ServiceUUIDBroadcastAudioAnnouncement = New16BitUUID(0x1852)
|
ServiceUUIDBroadcastAudioAnnouncement = New16BitUUID(0x1852)
|
||||||
|
|
||||||
// ServiceUUIDMicrobitDFUControl - micro:bit DFU Control Service
|
// ServiceUUIDMicrobitDFUControl - micro:bit DFU Control Service
|
||||||
ServiceUUIDMicrobitDFUControl = NewUUID([16]byte{0xe9, 0x5d, 0x93, 0xb0, 0x25, 0x1d, 0x47, 0x0a, 0xa0, 0x62, 0xfa, 0x19, 0x22, 0xdf, 0xa9, 0xa8})
|
ServiceUUIDMicrobitDFUControl = NewUUID([16]byte{0xe9,0x5d,0x93,0xb0,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8,})
|
||||||
|
|
||||||
// ServiceUUIDBondManagement - Bond Management Service
|
// ServiceUUIDBondManagement - Bond Management Service
|
||||||
ServiceUUIDBondManagement = New16BitUUID(0x181E)
|
ServiceUUIDBondManagement = New16BitUUID(0x181E)
|
||||||
|
@ -246,7 +247,7 @@ var (
|
||||||
ServiceUUIDLinkLoss = New16BitUUID(0x1803)
|
ServiceUUIDLinkLoss = New16BitUUID(0x1803)
|
||||||
|
|
||||||
// ServiceUUIDAdafruitAccelerometer - Adafruit Accelerometer Service
|
// ServiceUUIDAdafruitAccelerometer - Adafruit Accelerometer Service
|
||||||
ServiceUUIDAdafruitAccelerometer = NewUUID([16]byte{0xad, 0xaf, 0x02, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitAccelerometer = NewUUID([16]byte{0xad,0xaf,0x02,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDReferenceTimeUpdate - Reference Time Update Service
|
// ServiceUUIDReferenceTimeUpdate - Reference Time Update Service
|
||||||
ServiceUUIDReferenceTimeUpdate = New16BitUUID(0x1806)
|
ServiceUUIDReferenceTimeUpdate = New16BitUUID(0x1806)
|
||||||
|
@ -255,7 +256,7 @@ var (
|
||||||
ServiceUUIDTxPower = New16BitUUID(0x1804)
|
ServiceUUIDTxPower = New16BitUUID(0x1804)
|
||||||
|
|
||||||
// ServiceUUIDMicrobitTemperature - micro:bit Temperature Service
|
// ServiceUUIDMicrobitTemperature - micro:bit Temperature Service
|
||||||
ServiceUUIDMicrobitTemperature = NewUUID([16]byte{0xe9, 0x5d, 0x61, 0x00, 0x25, 0x1d, 0x47, 0x0a, 0xa0, 0x62, 0xfa, 0x19, 0x22, 0xdf, 0xa9, 0xa8})
|
ServiceUUIDMicrobitTemperature = NewUUID([16]byte{0xe9,0x5d,0x61,0x00,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8,})
|
||||||
|
|
||||||
// ServiceUUIDCyclingSpeedAndCadence - Cycling Speed and Cadence
|
// ServiceUUIDCyclingSpeedAndCadence - Cycling Speed and Cadence
|
||||||
ServiceUUIDCyclingSpeedAndCadence = New16BitUUID(0x1816)
|
ServiceUUIDCyclingSpeedAndCadence = New16BitUUID(0x1816)
|
||||||
|
@ -267,10 +268,10 @@ var (
|
||||||
ServiceUUIDTMAS = New16BitUUID(0x1855)
|
ServiceUUIDTMAS = New16BitUUID(0x1855)
|
||||||
|
|
||||||
// ServiceUUIDMicrobitButton - micro:bit Button Service
|
// ServiceUUIDMicrobitButton - micro:bit Button Service
|
||||||
ServiceUUIDMicrobitButton = NewUUID([16]byte{0xe9, 0x5d, 0x98, 0x82, 0x25, 0x1d, 0x47, 0x0a, 0xa0, 0x62, 0xfa, 0x19, 0x22, 0xdf, 0xa9, 0xa8})
|
ServiceUUIDMicrobitButton = NewUUID([16]byte{0xe9,0x5d,0x98,0x82,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8,})
|
||||||
|
|
||||||
// ServiceUUIDMicrobitIOPin - micro:bit IO Pin Service
|
// ServiceUUIDMicrobitIOPin - micro:bit IO Pin Service
|
||||||
ServiceUUIDMicrobitIOPin = NewUUID([16]byte{0xe9, 0x5d, 0x12, 0x7b, 0x25, 0x1d, 0x47, 0x0a, 0xa0, 0x62, 0xfa, 0x19, 0x22, 0xdf, 0xa9, 0xa8})
|
ServiceUUIDMicrobitIOPin = NewUUID([16]byte{0xe9,0x5d,0x12,0x7b,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8,})
|
||||||
|
|
||||||
// ServiceUUIDAutomationIO - Automation IO
|
// ServiceUUIDAutomationIO - Automation IO
|
||||||
ServiceUUIDAutomationIO = New16BitUUID(0x1815)
|
ServiceUUIDAutomationIO = New16BitUUID(0x1815)
|
||||||
|
@ -279,28 +280,28 @@ var (
|
||||||
ServiceUUIDGlucose = New16BitUUID(0x1808)
|
ServiceUUIDGlucose = New16BitUUID(0x1808)
|
||||||
|
|
||||||
// ServiceUUIDAppleMedia - Apple Media Service
|
// ServiceUUIDAppleMedia - Apple Media Service
|
||||||
ServiceUUIDAppleMedia = NewUUID([16]byte{0x89, 0xd3, 0x50, 0x2b, 0x0f, 0x36, 0x43, 0x3a, 0x8e, 0xf4, 0xc5, 0x02, 0xad, 0x55, 0xf8, 0xdc})
|
ServiceUUIDAppleMedia = NewUUID([16]byte{0x89,0xd3,0x50,0x2b,0x0f,0x36,0x43,0x3a,0x8e,0xf4,0xc5,0x02,0xad,0x55,0xf8,0xdc,})
|
||||||
|
|
||||||
// ServiceUUIDEdgeImpulseRemoteManagement - Edge Impulse Remote Management Service
|
// ServiceUUIDEdgeImpulseRemoteManagement - Edge Impulse Remote Management Service
|
||||||
ServiceUUIDEdgeImpulseRemoteManagement = NewUUID([16]byte{0xe2, 0xa0, 0x00, 0x01, 0xec, 0x31, 0x4e, 0xc3, 0xa9, 0x7a, 0x1c, 0x34, 0xd8, 0x7e, 0x98, 0x78})
|
ServiceUUIDEdgeImpulseRemoteManagement = NewUUID([16]byte{0xe2,0xa0,0x00,0x01,0xec,0x31,0x4e,0xc3,0xa9,0x7a,0x1c,0x34,0xd8,0x7e,0x98,0x78,})
|
||||||
|
|
||||||
// ServiceUUIDHeartRate - Heart Rate
|
// ServiceUUIDHeartRate - Heart Rate
|
||||||
ServiceUUIDHeartRate = New16BitUUID(0x180D)
|
ServiceUUIDHeartRate = New16BitUUID(0x180D)
|
||||||
|
|
||||||
// ServiceUUIDPhilipsHueLightUpdate - Philips Hue Light Update Service
|
// ServiceUUIDPhilipsHueLightUpdate - Philips Hue Light Update Service
|
||||||
ServiceUUIDPhilipsHueLightUpdate = NewUUID([16]byte{0xb8, 0x84, 0x3a, 0xdd, 0x00, 0x00, 0x4a, 0xa1, 0x87, 0x94, 0xc3, 0xf4, 0x62, 0x03, 0x0b, 0xda})
|
ServiceUUIDPhilipsHueLightUpdate = NewUUID([16]byte{0xb8,0x84,0x3a,0xdd,0x00,0x00,0x4a,0xa1,0x87,0x94,0xc3,0xf4,0x62,0x03,0x0b,0xda,})
|
||||||
|
|
||||||
// ServiceUUIDLegacyDFU - Legacy DFU Service
|
// ServiceUUIDLegacyDFU - Legacy DFU Service
|
||||||
ServiceUUIDLegacyDFU = NewUUID([16]byte{0x00, 0x00, 0x15, 0x30, 0x12, 0x12, 0xef, 0xde, 0x15, 0x23, 0x78, 0x5f, 0xea, 0xbc, 0xd1, 0x23})
|
ServiceUUIDLegacyDFU = NewUUID([16]byte{0x00,0x00,0x15,0x30,0x12,0x12,0xef,0xde,0x15,0x23,0x78,0x5f,0xea,0xbc,0xd1,0x23,})
|
||||||
|
|
||||||
// ServiceUUIDLEGOWirelessProtocolV3Hub - LEGO® Wireless Protocol v3 Hub Service
|
// ServiceUUIDLEGOWirelessProtocolV3Hub - LEGO® Wireless Protocol v3 Hub Service
|
||||||
ServiceUUIDLEGOWirelessProtocolV3Hub = NewUUID([16]byte{0x00, 0x00, 0x16, 0x23, 0x12, 0x12, 0xef, 0xde, 0x16, 0x23, 0x78, 0x5f, 0xea, 0xbc, 0xd1, 0x23})
|
ServiceUUIDLEGOWirelessProtocolV3Hub = NewUUID([16]byte{0x00,0x00,0x16,0x23,0x12,0x12,0xef,0xde,0x16,0x23,0x78,0x5f,0xea,0xbc,0xd1,0x23,})
|
||||||
|
|
||||||
// ServiceUUIDTexasInstrumentsOvertheAirDownloadOAD - Texas Instruments Over-the-Air Download (OAD) Service
|
// ServiceUUIDTexasInstrumentsOvertheAirDownloadOAD - Texas Instruments Over-the-Air Download (OAD) Service
|
||||||
ServiceUUIDTexasInstrumentsOvertheAirDownloadOAD = NewUUID([16]byte{0xf0, 0x00, 0xff, 0xc0, 0x04, 0x51, 0x40, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
ServiceUUIDTexasInstrumentsOvertheAirDownloadOAD = NewUUID([16]byte{0xf0,0x00,0xff,0xc0,0x04,0x51,0x40,0x00,0xb0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,})
|
||||||
|
|
||||||
// ServiceUUIDHeliumHotspotCustom - Helium Hotspot Custom Service
|
// ServiceUUIDHeliumHotspotCustom - Helium Hotspot Custom Service
|
||||||
ServiceUUIDHeliumHotspotCustom = NewUUID([16]byte{0x0f, 0xda, 0x92, 0xb2, 0x44, 0xa2, 0x4a, 0xf2, 0x84, 0xf5, 0xfa, 0x68, 0x2b, 0xaa, 0x2b, 0x8d})
|
ServiceUUIDHeliumHotspotCustom = NewUUID([16]byte{0x0f,0xda,0x92,0xb2,0x44,0xa2,0x4a,0xf2,0x84,0xf5,0xfa,0x68,0x2b,0xaa,0x2b,0x8d,})
|
||||||
|
|
||||||
// ServiceUUIDObjectTransfer - Object Transfer Service
|
// ServiceUUIDObjectTransfer - Object Transfer Service
|
||||||
ServiceUUIDObjectTransfer = New16BitUUID(0x1825)
|
ServiceUUIDObjectTransfer = New16BitUUID(0x1825)
|
||||||
|
@ -315,16 +316,16 @@ var (
|
||||||
ServiceUUIDVolumeOffsetControl = New16BitUUID(0x1845)
|
ServiceUUIDVolumeOffsetControl = New16BitUUID(0x1845)
|
||||||
|
|
||||||
// ServiceUUIDThingyConfiguration - Thingy Configuration Service
|
// ServiceUUIDThingyConfiguration - Thingy Configuration Service
|
||||||
ServiceUUIDThingyConfiguration = NewUUID([16]byte{0xef, 0x68, 0x01, 0x00, 0x9b, 0x35, 0x49, 0x33, 0x9b, 0x10, 0x52, 0xff, 0xa9, 0x74, 0x00, 0x42})
|
ServiceUUIDThingyConfiguration = NewUUID([16]byte{0xef,0x68,0x01,0x00,0x9b,0x35,0x49,0x33,0x9b,0x10,0x52,0xff,0xa9,0x74,0x00,0x42,})
|
||||||
|
|
||||||
// ServiceUUIDAdafruitLight - Adafruit Light Service
|
// ServiceUUIDAdafruitLight - Adafruit Light Service
|
||||||
ServiceUUIDAdafruitLight = NewUUID([16]byte{0xad, 0xaf, 0x03, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitLight = NewUUID([16]byte{0xad,0xaf,0x03,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDAdafruitBarometric - Adafruit Barometric Service
|
// ServiceUUIDAdafruitBarometric - Adafruit Barometric Service
|
||||||
ServiceUUIDAdafruitBarometric = NewUUID([16]byte{0xad, 0xaf, 0x08, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitBarometric = NewUUID([16]byte{0xad,0xaf,0x08,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDAdafruitTone - Adafruit Tone Service
|
// ServiceUUIDAdafruitTone - Adafruit Tone Service
|
||||||
ServiceUUIDAdafruitTone = NewUUID([16]byte{0xad, 0xaf, 0x0c, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8})
|
ServiceUUIDAdafruitTone = NewUUID([16]byte{0xad,0xaf,0x0c,0x00,0xc3,0x32,0x42,0xa8,0x93,0xbd,0x25,0xe9,0x05,0x75,0x6c,0xb8,})
|
||||||
|
|
||||||
// ServiceUUIDBattery - Battery Service
|
// ServiceUUIDBattery - Battery Service
|
||||||
ServiceUUIDBattery = New16BitUUID(0x180F)
|
ServiceUUIDBattery = New16BitUUID(0x180F)
|
||||||
|
@ -336,7 +337,7 @@ var (
|
||||||
ServiceUUIDAudioInputControl = New16BitUUID(0x1843)
|
ServiceUUIDAudioInputControl = New16BitUUID(0x1843)
|
||||||
|
|
||||||
// ServiceUUIDAppleNotificationCenter - Apple Notification Center Service
|
// ServiceUUIDAppleNotificationCenter - Apple Notification Center Service
|
||||||
ServiceUUIDAppleNotificationCenter = NewUUID([16]byte{0x79, 0x05, 0xf4, 0x31, 0xb5, 0xce, 0x4e, 0x99, 0xa4, 0x0f, 0x4b, 0x1e, 0x12, 0x2d, 0x00, 0xd0})
|
ServiceUUIDAppleNotificationCenter = NewUUID([16]byte{0x79,0x05,0xf4,0x31,0xb5,0xce,0x4e,0x99,0xa4,0x0f,0x4b,0x1e,0x12,0x2d,0x00,0xd0,})
|
||||||
|
|
||||||
// ServiceUUIDGenericAccess - Generic Access
|
// ServiceUUIDGenericAccess - Generic Access
|
||||||
ServiceUUIDGenericAccess = New16BitUUID(0x1800)
|
ServiceUUIDGenericAccess = New16BitUUID(0x1800)
|
||||||
|
@ -349,4 +350,5 @@ var (
|
||||||
|
|
||||||
// ServiceUUIDHTTPProxy - HTTP Proxy
|
// ServiceUUIDHTTPProxy - HTTP Proxy
|
||||||
ServiceUUIDHTTPProxy = New16BitUUID(0x1823)
|
ServiceUUIDHTTPProxy = New16BitUUID(0x1823)
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build ignore
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ import (
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Characteristic struct {
|
type Characteristic struct {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build ignore
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ import (
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/bluetooth"
|
"tinygo.org/x/bluetooth"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
|
|
22
uuid.go
22
uuid.go
|
@ -38,20 +38,6 @@ func New16BitUUID(shortUUID uint16) UUID {
|
||||||
return uuid
|
return uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
// New32BitUUID returns a new 128-bit UUID based on a 32-bit UUID.
|
|
||||||
//
|
|
||||||
// Note: only use registered UUIDs. See
|
|
||||||
// https://www.bluetooth.com/specifications/gatt/services/ for a list.
|
|
||||||
func New32BitUUID(shortUUID uint32) UUID {
|
|
||||||
// https://stackoverflow.com/questions/36212020/how-can-i-convert-a-bluetooth-16-bit-service-uuid-into-a-128-bit-uuid
|
|
||||||
var uuid UUID
|
|
||||||
uuid[0] = 0x5F9B34FB
|
|
||||||
uuid[1] = 0x80000080
|
|
||||||
uuid[2] = 0x00001000
|
|
||||||
uuid[3] = shortUUID
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace16BitComponent returns a new UUID where bits 16..32 have been replaced
|
// Replace16BitComponent returns a new UUID where bits 16..32 have been replaced
|
||||||
// with the bits given in the argument. These bits are the same bits that vary
|
// with the bits given in the argument. These bits are the same bits that vary
|
||||||
// in the 16-bit compressed UUID form.
|
// in the 16-bit compressed UUID form.
|
||||||
|
@ -82,14 +68,6 @@ func (uuid UUID) Get16Bit() uint16 {
|
||||||
return uint16(uuid[3])
|
return uint16(uuid[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get32Bit returns the 32-bit version of this UUID. This is only valid if it
|
|
||||||
// actually is a 32-bit UUID, see Is32Bit.
|
|
||||||
func (uuid UUID) Get32Bit() uint32 {
|
|
||||||
// Note: using a Get* function as a getter because method names can't start
|
|
||||||
// with a number.
|
|
||||||
return uuid[3]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns a 16-byte array containing the raw UUID.
|
// Bytes returns a 16-byte array containing the raw UUID.
|
||||||
func (uuid UUID) Bytes() [16]byte {
|
func (uuid UUID) Bytes() [16]byte {
|
||||||
buf := [16]byte{}
|
buf := [16]byte{}
|
||||||
|
|
20
uuid_hci.go
20
uuid_hci.go
|
@ -1,20 +0,0 @@
|
||||||
//go:build hci || ninafw || cyw43439
|
|
||||||
|
|
||||||
package bluetooth
|
|
||||||
|
|
||||||
type shortUUID uint16
|
|
||||||
|
|
||||||
// UUID returns the full length UUID for this short UUID.
|
|
||||||
func (s shortUUID) UUID() UUID {
|
|
||||||
return New16BitUUID(uint16(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// isIn checks the passed in slice of UUIDs to see if this uuid is in it.
|
|
||||||
func (uuid UUID) isIn(uuids []UUID) bool {
|
|
||||||
for _, u := range uuids {
|
|
||||||
if u == uuid {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:build softdevice
|
//go:build softdevice
|
||||||
|
// +build softdevice
|
||||||
|
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
|
@ -10,9 +11,9 @@ import "unsafe"
|
||||||
|
|
||||||
type shortUUID C.ble_uuid_t
|
type shortUUID C.ble_uuid_t
|
||||||
|
|
||||||
func (uuid UUID) shortUUID() (C.ble_uuid_t, C.uint32_t) {
|
func (uuid UUID) shortUUID() (C.ble_uuid_t, uint32) {
|
||||||
var short C.ble_uuid_t
|
var short C.ble_uuid_t
|
||||||
short.uuid = C.uint16_t(uuid[3])
|
short.uuid = uint16(uuid[3])
|
||||||
if uuid.Is16Bit() {
|
if uuid.Is16Bit() {
|
||||||
short._type = C.BLE_UUID_TYPE_BLE
|
short._type = C.BLE_UUID_TYPE_BLE
|
||||||
return short, 0
|
return short, 0
|
||||||
|
@ -24,7 +25,7 @@ func (uuid UUID) shortUUID() (C.ble_uuid_t, C.uint32_t) {
|
||||||
// UUID returns the full length UUID for this short UUID.
|
// UUID returns the full length UUID for this short UUID.
|
||||||
func (s shortUUID) UUID() UUID {
|
func (s shortUUID) UUID() UUID {
|
||||||
if s._type == C.BLE_UUID_TYPE_BLE {
|
if s._type == C.BLE_UUID_TYPE_BLE {
|
||||||
return New16BitUUID(uint16(s.uuid))
|
return New16BitUUID(s.uuid)
|
||||||
}
|
}
|
||||||
var outLen C.uint8_t
|
var outLen C.uint8_t
|
||||||
var outUUID UUID
|
var outUUID UUID
|
||||||
|
|
|
@ -2,4 +2,4 @@ package bluetooth
|
||||||
|
|
||||||
// Version returns a user-readable string showing the version of the bluetooth package for support purposes.
|
// Version returns a user-readable string showing the version of the bluetooth package for support purposes.
|
||||||
// Update this value before release of new version of software.
|
// Update this value before release of new version of software.
|
||||||
const Version = "0.10.0"
|
const Version = "0.6.0"
|
||||||
|
|
Loading…
Reference in a new issue