7a11ef8562
There are some limitations, but it basically works (on both Linux and nrf).
131 lines
3.7 KiB
Go
131 lines
3.7 KiB
Go
// +build softdevice
|
|
|
|
package bluetooth
|
|
|
|
import (
|
|
"device/arm"
|
|
"runtime/volatile"
|
|
)
|
|
|
|
/*
|
|
// Define SoftDevice functions as regular function declarations (not inline
|
|
// static functions).
|
|
#define SVCALL_AS_NORMAL_FUNCTION
|
|
|
|
#include "ble_gap.h"
|
|
*/
|
|
import "C"
|
|
|
|
// Memory buffers needed by sd_ble_gap_scan_start.
|
|
var (
|
|
scanReportBuffer rawAdvertisementPayload
|
|
gotScanReport volatile.Register8
|
|
globalScanResult ScanResult
|
|
)
|
|
|
|
// Advertisement encapsulates a single advertisement instance.
|
|
type Advertisement struct {
|
|
handle uint8
|
|
}
|
|
|
|
// NewAdvertisement creates a new advertisement instance but does not configure
|
|
// it. It can be called before the SoftDevice has been initialized.
|
|
func (a *Adapter) NewAdvertisement() *Advertisement {
|
|
return &Advertisement{
|
|
handle: C.BLE_GAP_ADV_SET_HANDLE_NOT_SET,
|
|
}
|
|
}
|
|
|
|
// Configure this advertisement. Must be called after SoftDevice initialization.
|
|
func (a *Advertisement) Configure(broadcastData, scanResponseData []byte, options *AdvertiseOptions) error {
|
|
data := C.ble_gap_adv_data_t{}
|
|
if broadcastData != nil {
|
|
data.adv_data = C.ble_data_t{
|
|
p_data: &broadcastData[0],
|
|
len: uint16(len(broadcastData)),
|
|
}
|
|
}
|
|
if scanResponseData != nil {
|
|
data.scan_rsp_data = C.ble_data_t{
|
|
p_data: &scanResponseData[0],
|
|
len: uint16(len(scanResponseData)),
|
|
}
|
|
}
|
|
params := C.ble_gap_adv_params_t{
|
|
properties: C.ble_gap_adv_properties_t{
|
|
_type: C.BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED,
|
|
},
|
|
interval: uint32(options.Interval),
|
|
}
|
|
errCode := C.sd_ble_gap_adv_set_configure(&a.handle, &data, ¶ms)
|
|
return makeError(errCode)
|
|
}
|
|
|
|
// Start advertisement. May only be called after it has been configured.
|
|
func (a *Advertisement) Start() error {
|
|
errCode := C.sd_ble_gap_adv_start(a.handle, C.BLE_CONN_CFG_TAG_DEFAULT)
|
|
return makeError(errCode)
|
|
}
|
|
|
|
// 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.
|
|
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
|
if a.scanning {
|
|
// There is a possible race condition here if Scan() is called from a
|
|
// different goroutine, but that is not allowed (and will likely result
|
|
// in an error below anyway).
|
|
return errScanning
|
|
}
|
|
a.scanning = true
|
|
|
|
scanParams := C.ble_gap_scan_params_t{}
|
|
scanParams.set_bitfield_extended(0)
|
|
scanParams.set_bitfield_active(0)
|
|
scanParams.interval = 100 * 1000 / 625 // 100ms in 625µs units
|
|
scanParams.window = 100 * 1000 / 625 // 100ms in 625µs units
|
|
scanParams.timeout = C.BLE_GAP_SCAN_TIMEOUT_UNLIMITED
|
|
scanReportBufferInfo := C.ble_data_t{
|
|
p_data: &scanReportBuffer.data[0],
|
|
len: uint16(len(scanReportBuffer.data)),
|
|
}
|
|
errCode := C.sd_ble_gap_scan_start(&scanParams, &scanReportBufferInfo)
|
|
if errCode != 0 {
|
|
return Error(errCode)
|
|
}
|
|
|
|
// Wait for received scan reports.
|
|
for a.scanning {
|
|
// Wait for the next advertisement packet to arrive.
|
|
// TODO: use some sort of condition variable once the scheduler supports
|
|
// them.
|
|
arm.Asm("wfe")
|
|
if gotScanReport.Get() == 0 {
|
|
// Spurious event. Continue waiting.
|
|
continue
|
|
}
|
|
gotScanReport.Set(0)
|
|
|
|
// Call the callback with the scan result.
|
|
callback(a, globalScanResult)
|
|
|
|
// Restart the advertisement. This is needed, because advertisements are
|
|
// automatically stopped when the first packet arrives.
|
|
errCode := C.sd_ble_gap_scan_start(nil, &scanReportBufferInfo)
|
|
if errCode != 0 {
|
|
return Error(errCode)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StopScan stops any in-progress scan. It can be called from within a Scan
|
|
// callback to stop the current scan. If no scan is in progress, an error will
|
|
// be returned.
|
|
func (a *Adapter) StopScan() error {
|
|
if !a.scanning {
|
|
return errNotScanning
|
|
}
|
|
a.scanning = false
|
|
return nil
|
|
}
|