macos: able to discover services and characteristics for a device

Signed-off-by: Ron Evans <ron@hybridgroup.com>
This commit is contained in:
Ron Evans 2020-08-29 11:35:26 +02:00
parent 5f44bb4a96
commit dc738f9c47
4 changed files with 108 additions and 54 deletions

View file

@ -10,7 +10,7 @@ This package attempts to build a cross-platform Bluetooth Low Energy module for
| API used | WinRT | BlueZ (over D-Bus) | SoftDevice | CoreBluetooth |
| Scanning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Connect to peripheral | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Write peripheral characteristics | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |
| Write peripheral characteristics | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Receive notifications | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |
| Advertisement | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |
| Local services | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |

View file

@ -80,40 +80,6 @@ func (a *Adapter) DidConnectPeripheral(cmgr cbgo.CentralManager, prph cbgo.Perip
a.connectChan <- prph
}
// DidDisconnectPeripheral when peripheral is disconnected.
func (a *Adapter) DidDisconnectPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peripheral, err error) {
}
// PeripheralManager delegate functions
// PeripheralManagerDidUpdateState when state updated.
func (a *Adapter) PeripheralManagerDidUpdateState(pmgr cbgo.PeripheralManager) {
}
// DidAddService when service added.
func (a *Adapter) DidAddService(pmgr cbgo.PeripheralManager, svc cbgo.Service, err error) {
}
// DidStartAdvertising when advertising starts.
func (a *Adapter) DidStartAdvertising(pmgr cbgo.PeripheralManager, err error) {
}
// DidReceiveReadRequest when read request received.
func (a *Adapter) DidReceiveReadRequest(pmgr cbgo.PeripheralManager, cbreq cbgo.ATTRequest) {
}
// DidReceiveWriteRequests when write requests received.
func (a *Adapter) DidReceiveWriteRequests(pmgr cbgo.PeripheralManager, cbreqs []cbgo.ATTRequest) {
}
// CentralDidSubscribe when central subscribed.
func (a *Adapter) CentralDidSubscribe(pmgr cbgo.PeripheralManager, cent cbgo.Central, cbchr cbgo.Characteristic) {
}
// CentralDidUnsubscribe when central unsubscribed.
func (a *Adapter) CentralDidUnsubscribe(pmgr cbgo.PeripheralManager, cent cbgo.Central, chr cbgo.Characteristic) {
}
// makeScanResult creates a ScanResult when peripheral is found.
func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) ScanResult {
uuid, _ := ParseUUID(prph.Identifier().String())

View file

@ -81,7 +81,11 @@ func (a *Adapter) StopScan() error {
type Device struct {
cbgo.PeripheralDelegateBase
cm cbgo.CentralManager
cm cbgo.CentralManager
prph cbgo.Peripheral
servicesChan chan error
charsChan chan error
}
// Connect starts a connection attempt to the given peripheral device address.
@ -101,7 +105,10 @@ func (a *Adapter) Connect(address Addresser, params ConnectionParams) (*Device,
select {
case p := <-a.connectChan:
d := &Device{
cm: a.cm,
cm: a.cm,
prph: p,
servicesChan: make(chan error),
charsChan: make(chan error),
}
p.SetDelegate(d)
@ -111,23 +118,27 @@ func (a *Adapter) Connect(address Addresser, params ConnectionParams) (*Device,
}
}
// Peripheral returns the Device's cbgo.Peripheral
func (d *Device) Peripheral() (prph cbgo.Peripheral) {
return d.prph
}
// CharsChan returns the Device's charsChan channel used for
// blocking on discovering the characteristics for a service.
func (d *Device) CharsChan() chan error {
return d.charsChan
}
// Peripheral delegate functions
// DidDiscoverServices is called when the services for a Peripheral
// have been discovered.
func (d *Device) DidDiscoverServices(prph cbgo.Peripheral, err error) {
d.servicesChan <- nil
}
// DidDiscoverCharacteristics is called when the characteristics for a Service
// for a Peripheral have been discovered.
func (d *Device) DidDiscoverCharacteristics(prph cbgo.Peripheral, svc cbgo.Service, err error) {
}
func (d *Device) DidDiscoverDescriptors(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
}
func (d *Device) DidUpdateValueForCharacteristic(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
}
func (d *Device) DidUpdateValueForDescriptor(prph cbgo.Peripheral, dsc cbgo.Descriptor, err error) {
}
func (d *Device) DidWriteValueForCharacteristic(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
}
func (d *Device) DidWriteValueForDescriptor(prph cbgo.Peripheral, dsc cbgo.Descriptor, err error) {
}
func (d *Device) DidUpdateNotificationState(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
}
func (d *Device) DidReadRSSI(prph cbgo.Peripheral, rssi int, err error) {
d.charsChan <- nil
}

View file

@ -1,15 +1,56 @@
package bluetooth
import (
"errors"
"time"
"github.com/JuulLabs-OSS/cbgo"
)
// 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.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
return nil, nil
cbuuids := []cbgo.UUID{}
for _, u := range uuids {
uuid, _ := cbgo.ParseUUID(u.String())
cbuuids = append(cbuuids, uuid)
}
d.prph.DiscoverServices(cbuuids)
// wait on channel for service discovery
select {
case <-d.servicesChan:
svcs := []DeviceService{}
for _, dsvc := range d.prph.Services() {
uuid, _ := ParseUUID(dsvc.UUID().String())
svc := DeviceService{
UUID: uuid,
device: d,
service: dsvc,
}
svcs = append(svcs, svc)
}
return svcs, nil
case <-time.NewTimer(10 * time.Second).C:
return nil, errors.New("timeout on DiscoverServices")
}
}
// DeviceService is a BLE service on a connected peripheral device.
type DeviceService struct {
UUID
device *Device
service cbgo.Service
}
// Device returns the Device for this service.
func (s *DeviceService) Device() *Device {
return s.device
}
// DiscoverCharacteristics discovers characteristics in this service. Pass a
@ -19,12 +60,42 @@ type DeviceService struct {
// slice has the same length as the UUID slice with characteristics in the same
// order in the slice as in the requested UUID list.
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
return nil, nil
cbuuids := []cbgo.UUID{}
for _, u := range uuids {
uuid, _ := cbgo.ParseUUID(u.String())
cbuuids = append(cbuuids, uuid)
}
s.Device().Peripheral().DiscoverCharacteristics(cbuuids, s.service)
// wait on channel for characteristic discovery
select {
case <-s.Device().CharsChan():
chars := []DeviceCharacteristic{}
for _, dchar := range s.service.Characteristics() {
uuid, _ := ParseUUID(dchar.UUID().String())
char := DeviceCharacteristic{
UUID: uuid,
service: s,
characteristic: dchar,
}
chars = append(chars, char)
}
return chars, nil
case <-time.NewTimer(10 * time.Second).C:
return nil, errors.New("timeout on DiscoverCharacteristics")
}
}
// DeviceCharacteristic is a BLE characteristic on a connected peripheral
// device.
type DeviceCharacteristic struct {
UUID
service *DeviceService
characteristic cbgo.Characteristic
callback func(buf []byte)
}
// WriteWithoutResponse replaces the characteristic value with a new value. The
@ -32,7 +103,9 @@ type DeviceCharacteristic struct {
// 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) {
return 0, nil
c.service.Device().Peripheral().WriteCharacteristic(p, c.characteristic, false)
return len(p), nil
}
// EnableNotifications enables notifications in the Client Characteristic
@ -40,5 +113,9 @@ func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error)
// notification with a new value every time the value of the characteristic
// changes.
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
if callback == nil {
return errors.New("must provide a callback for EnableNotifications")
}
return nil
}