Søren Rasmussen
07a23c1845
Some checks reported errors
continuous-integration/drone/push Build encountered an error
194 lines
5.2 KiB
Go
194 lines
5.2 KiB
Go
package linux
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log"
|
|
|
|
"github.com/JuulLabs-OSS/ble"
|
|
"github.com/JuulLabs-OSS/ble/linux/att"
|
|
"github.com/JuulLabs-OSS/ble/linux/gatt"
|
|
"github.com/JuulLabs-OSS/ble/linux/hci"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// NewDevice returns the default HCI device.
|
|
func NewDevice(opts ...ble.Option) (*Device, error) {
|
|
return NewDeviceWithName("Gopher", opts...)
|
|
}
|
|
|
|
// NewDeviceWithName returns the default HCI device.
|
|
func NewDeviceWithName(name string, opts ...ble.Option) (*Device, error) {
|
|
return NewDeviceWithNameAndHandler(name, nil, opts...)
|
|
}
|
|
|
|
func NewDeviceWithNameAndHandler(name string, handler ble.NotifyHandler, opts ...ble.Option) (*Device, error) {
|
|
dev, err := hci.NewHCI(opts...)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "can't create hci")
|
|
}
|
|
if err = dev.Init(); err != nil {
|
|
dev.Close()
|
|
return nil, errors.Wrap(err, "can't init hci")
|
|
}
|
|
|
|
srv, err := gatt.NewServerWithNameAndHandler(name, handler)
|
|
if err != nil {
|
|
dev.Close()
|
|
return nil, errors.Wrap(err, "can't create server")
|
|
}
|
|
|
|
// mtu := ble.DefaultMTU
|
|
mtu := ble.MaxMTU // TODO: get this from user using Option.
|
|
if mtu > ble.MaxMTU {
|
|
dev.Close()
|
|
return nil, errors.Wrapf(err, "maximum ATT_MTU is %d", ble.MaxMTU)
|
|
}
|
|
|
|
go loop(dev, srv, mtu)
|
|
|
|
return &Device{HCI: dev, Server: srv}, nil
|
|
}
|
|
|
|
func loop(dev *hci.HCI, s *gatt.Server, mtu int) {
|
|
for {
|
|
l2c, err := dev.Accept()
|
|
if err != nil {
|
|
// An EOF error indicates that the HCI socket was closed during
|
|
// the read. Don't report this as an error.
|
|
if err != io.EOF {
|
|
log.Printf("can't accept: %s", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Initialize the per-connection cccd values.
|
|
l2c.SetContext(context.WithValue(l2c.Context(), ble.ContextKeyCCC, make(map[uint16]uint16)))
|
|
l2c.SetRxMTU(mtu)
|
|
|
|
s.Lock()
|
|
as, err := att.NewServer(s.DB(), l2c)
|
|
s.Unlock()
|
|
if err != nil {
|
|
log.Printf("can't create ATT server: %s", err)
|
|
continue
|
|
|
|
}
|
|
go as.Loop()
|
|
}
|
|
}
|
|
|
|
// Device ...
|
|
type Device struct {
|
|
HCI *hci.HCI
|
|
Server *gatt.Server
|
|
}
|
|
|
|
// AddService adds a service to database.
|
|
func (d *Device) AddService(svc *ble.Service) error {
|
|
return d.Server.AddService(svc)
|
|
}
|
|
|
|
// RemoveAllServices removes all services that are currently in the database.
|
|
func (d *Device) RemoveAllServices() error {
|
|
return d.Server.RemoveAllServices()
|
|
}
|
|
|
|
// SetServices set the specified service to the database.
|
|
// It removes all currently added services, if any.
|
|
func (d *Device) SetServices(svcs []*ble.Service) error {
|
|
return d.Server.SetServices(svcs)
|
|
}
|
|
|
|
// Stop stops gatt server.
|
|
func (d *Device) Stop() error {
|
|
return d.HCI.Close()
|
|
}
|
|
|
|
func (d *Device) Advertise(ctx context.Context, adv ble.Advertisement) error {
|
|
if err := d.HCI.AdvertiseAdv(adv); err != nil {
|
|
return err
|
|
}
|
|
<-ctx.Done()
|
|
d.HCI.StopAdvertising()
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
// AdvertiseNameAndServices advertises device name, and specified service UUIDs.
|
|
// It tres to fit the UUIDs in the advertising packet as much as possible.
|
|
// If name doesn't fit in the advertising packet, it will be put in scan response.
|
|
func (d *Device) AdvertiseNameAndServices(ctx context.Context, name string, uuids ...ble.UUID) error {
|
|
if err := d.HCI.AdvertiseNameAndServices(name, uuids...); err != nil {
|
|
return err
|
|
}
|
|
<-ctx.Done()
|
|
d.HCI.StopAdvertising()
|
|
return ctx.Err()
|
|
}
|
|
|
|
// AdvertiseMfgData avertises the given manufacturer data.
|
|
func (d *Device) AdvertiseMfgData(ctx context.Context, id uint16, b []byte) error {
|
|
if err := d.HCI.AdvertiseMfgData(id, b); err != nil {
|
|
return err
|
|
}
|
|
<-ctx.Done()
|
|
d.HCI.StopAdvertising()
|
|
return ctx.Err()
|
|
}
|
|
|
|
// AdvertiseServiceData16 advertises data associated with a 16bit service uuid
|
|
func (d *Device) AdvertiseServiceData16(ctx context.Context, id uint16, b []byte) error {
|
|
if err := d.HCI.AdvertiseServiceData16(id, b); err != nil {
|
|
return err
|
|
}
|
|
<-ctx.Done()
|
|
d.HCI.StopAdvertising()
|
|
return ctx.Err()
|
|
}
|
|
|
|
// AdvertiseIBeaconData advertise iBeacon with given manufacturer data.
|
|
func (d *Device) AdvertiseIBeaconData(ctx context.Context, b []byte) error {
|
|
if err := d.HCI.AdvertiseIBeaconData(b); err != nil {
|
|
return err
|
|
}
|
|
<-ctx.Done()
|
|
d.HCI.StopAdvertising()
|
|
return ctx.Err()
|
|
}
|
|
|
|
// AdvertiseIBeacon advertises iBeacon with specified parameters.
|
|
func (d *Device) AdvertiseIBeacon(ctx context.Context, u ble.UUID, major, minor uint16, pwr int8) error {
|
|
if err := d.HCI.AdvertiseIBeacon(u, major, minor, pwr); err != nil {
|
|
return err
|
|
}
|
|
<-ctx.Done()
|
|
d.HCI.StopAdvertising()
|
|
return ctx.Err()
|
|
}
|
|
|
|
// Scan starts scanning. Duplicated advertisements will be filtered out if allowDup is set to false.
|
|
func (d *Device) Scan(ctx context.Context, allowDup bool, h ble.AdvHandler) error {
|
|
if err := d.HCI.SetAdvHandler(h); err != nil {
|
|
return err
|
|
}
|
|
if err := d.HCI.Scan(allowDup); err != nil {
|
|
return err
|
|
}
|
|
<-ctx.Done()
|
|
d.HCI.StopScanning()
|
|
return ctx.Err()
|
|
}
|
|
|
|
// Dial ...
|
|
func (d *Device) Dial(ctx context.Context, a ble.Addr) (ble.Client, error) {
|
|
// d.HCI.Dial is a blocking call, although most of time it should return immediately.
|
|
// But in case passing wrong device address or the device went non-connectable, it blocks.
|
|
cln, err := d.HCI.Dial(ctx, a)
|
|
return cln, errors.Wrap(err, "can't dial")
|
|
}
|
|
|
|
// Address returns the listener's device address.
|
|
func (d *Device) Address() ble.Addr {
|
|
return d.HCI.Addr()
|
|
}
|