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() }