188 lines
4.4 KiB
Go
188 lines
4.4 KiB
Go
|
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)
|
||
|
}
|