blob: 86fe565fa44af3035324e76c2e8d987c8dc95fda [file] [log] [blame]
package linux
import (
"context"
"io"
"log"
"github.com/go-ble/ble"
"github.com/go-ble/ble/linux/att"
"github.com/go-ble/ble/linux/gatt"
"github.com/go-ble/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 {
return nil, errors.Wrap(err, "can't init hci")
}
srv, err := gatt.NewServerWithNameAndHandler(name, handler)
if err != nil {
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 {
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()
}