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