blob: ae8fcc00d5704cce5921efacbb58918f04754932 [file] [log] [blame]
package ble
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/pkg/errors"
)
// ErrDefaultDevice ...
var ErrDefaultDevice = errors.New("default device is not set")
var defaultDevice Device
// SetDefaultDevice returns the default HCI device.
func SetDefaultDevice(d Device) {
defaultDevice = d
}
// AddService adds a service to database.
func AddService(svc *Service) error {
if defaultDevice == nil {
return ErrDefaultDevice
}
return defaultDevice.AddService(svc)
}
// RemoveAllServices removes all services that are currently in the database.
func RemoveAllServices() error {
if defaultDevice == nil {
return ErrDefaultDevice
}
return defaultDevice.RemoveAllServices()
}
// SetServices set the specified service to the database.
// It removes all currently added services, if any.
func SetServices(svcs []*Service) error {
if defaultDevice == nil {
return ErrDefaultDevice
}
return defaultDevice.SetServices(svcs)
}
// Stop detatch the GATT server from a peripheral device.
func Stop() error {
if defaultDevice == nil {
return ErrDefaultDevice
}
return defaultDevice.Stop()
}
// AdvertiseNameAndServices advertises device name, and specified service UUIDs.
// It tres to fit the UUIDs in the advertising packet as much as possi
// If name doesn't fit in the advertising packet, it will be put in scan response.
func AdvertiseNameAndServices(ctx context.Context, name string, uuids ...UUID) error {
if defaultDevice == nil {
return ErrDefaultDevice
}
defer untrap(trap(ctx))
return defaultDevice.AdvertiseNameAndServices(ctx, name, uuids...)
}
// AdvertiseIBeaconData advertise iBeacon with given manufacturer data.
func AdvertiseIBeaconData(ctx context.Context, b []byte) error {
if defaultDevice == nil {
return ErrDefaultDevice
}
defer untrap(trap(ctx))
return defaultDevice.AdvertiseIBeaconData(ctx, b)
}
// AdvertiseIBeacon advertises iBeacon with specified parameters.
func AdvertiseIBeacon(ctx context.Context, u UUID, major, minor uint16, pwr int8) error {
if defaultDevice == nil {
return ErrDefaultDevice
}
defer untrap(trap(ctx))
return defaultDevice.AdvertiseIBeacon(ctx, u, major, minor, pwr)
}
// Scan starts scanning. Duplicated advertisements will be filtered out if allowDup is set to false.
func Scan(ctx context.Context, allowDup bool, h AdvHandler, f AdvFilter) error {
if defaultDevice == nil {
return ErrDefaultDevice
}
defer untrap(trap(ctx))
if f == nil {
return defaultDevice.Scan(ctx, allowDup, h)
}
h2 := func(a Advertisement) {
if f(a) {
h(a)
}
}
return defaultDevice.Scan(ctx, allowDup, h2)
}
// Find ...
func Find(ctx context.Context, allowDup bool, f AdvFilter) ([]Advertisement, error) {
if defaultDevice == nil {
return nil, ErrDefaultDevice
}
var advs []Advertisement
h := func(a Advertisement) {
advs = append(advs, a)
}
defer untrap(trap(ctx))
return advs, Scan(ctx, allowDup, h, f)
}
// Dial ...
func Dial(ctx context.Context, a Addr) (Client, error) {
if defaultDevice == nil {
return nil, ErrDefaultDevice
}
defer untrap(trap(ctx))
return defaultDevice.Dial(ctx, a)
}
// Connect searches for and connects to a Peripheral which matches specified condition.
func Connect(ctx context.Context, f AdvFilter) (Client, error) {
ctx2, cancel := context.WithCancel(ctx)
go func() {
select {
case <-ctx.Done():
cancel()
case <-ctx2.Done():
}
}()
ch := make(chan Advertisement)
fn := func(a Advertisement) {
cancel()
ch <- a
}
if err := Scan(ctx2, false, fn, f); err != nil {
if err != context.Canceled {
return nil, errors.Wrap(err, "can't scan")
}
}
cln, err := Dial(ctx, (<-ch).Addr())
return cln, errors.Wrap(err, "can't dial")
}
// A NotificationHandler handles notification or indication from a server.
type NotificationHandler func(req []byte)
// WithSigHandler ...
func WithSigHandler(ctx context.Context, cancel func()) context.Context {
return context.WithValue(ctx, ContextKeySig, cancel)
}
// Cleanup for the interrupted case.
func trap(ctx context.Context) chan<- os.Signal {
v := ctx.Value(ContextKeySig)
if v == nil {
return nil
}
cancel, ok := v.(func())
if cancel == nil || !ok {
return nil
}
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
select {
case <-sigs:
cancel()
case <-ctx.Done():
}
}()
return sigs
}
func untrap(sigs chan<- os.Signal) {
if sigs == nil {
return
}
signal.Stop(sigs)
}