gobot.io/x/gobot@v1.16.0/platforms/ble/ble_client_adaptor.go (about)

     1  package ble
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/pkg/errors"
    10  	"gobot.io/x/gobot"
    11  
    12  	"tinygo.org/x/bluetooth"
    13  )
    14  
    15  //var currentDevice *blelib.Device
    16  var currentAdapter *bluetooth.Adapter
    17  var bleMutex sync.Mutex
    18  
    19  // BLEConnector is the interface that a BLE ClientAdaptor must implement
    20  type BLEConnector interface {
    21  	Connect() error
    22  	Reconnect() error
    23  	Disconnect() error
    24  	Finalize() error
    25  	Name() string
    26  	SetName(string)
    27  	Address() string
    28  	ReadCharacteristic(string) ([]byte, error)
    29  	WriteCharacteristic(string, []byte) error
    30  	Subscribe(string, func([]byte, error)) error
    31  	WithoutResponses(bool)
    32  }
    33  
    34  // ClientAdaptor represents a Client Connection to a BLE Peripheral
    35  type ClientAdaptor struct {
    36  	name        string
    37  	address     string
    38  	AdapterName string
    39  
    40  	addr            bluetooth.Address
    41  	adpt            *bluetooth.Adapter
    42  	device          *bluetooth.Device
    43  	characteristics map[string]bluetooth.DeviceCharacteristic
    44  
    45  	connected        bool
    46  	ready            chan struct{}
    47  	withoutResponses bool
    48  }
    49  
    50  // NewClientAdaptor returns a new ClientAdaptor given an address
    51  func NewClientAdaptor(address string) *ClientAdaptor {
    52  	return &ClientAdaptor{
    53  		name:             gobot.DefaultName("BLEClient"),
    54  		address:          address,
    55  		AdapterName:      "default",
    56  		connected:        false,
    57  		withoutResponses: false,
    58  		characteristics:  make(map[string]bluetooth.DeviceCharacteristic),
    59  	}
    60  }
    61  
    62  // Name returns the name for the adaptor
    63  func (b *ClientAdaptor) Name() string { return b.name }
    64  
    65  // SetName sets the name for the adaptor
    66  func (b *ClientAdaptor) SetName(n string) { b.name = n }
    67  
    68  // Address returns the Bluetooth LE address for the adaptor
    69  func (b *ClientAdaptor) Address() string { return b.address }
    70  
    71  // WithoutResponses sets if the adaptor should expect responses after
    72  // writing characteristics for this device
    73  func (b *ClientAdaptor) WithoutResponses(use bool) { b.withoutResponses = use }
    74  
    75  // Connect initiates a connection to the BLE peripheral. Returns true on successful connection.
    76  func (b *ClientAdaptor) Connect() (err error) {
    77  	bleMutex.Lock()
    78  	defer bleMutex.Unlock()
    79  
    80  	// enable adaptor
    81  	b.adpt, err = getBLEAdapter(b.AdapterName)
    82  	if err != nil {
    83  		return errors.Wrap(err, "can't enable adapter "+b.AdapterName)
    84  	}
    85  
    86  	// handle address
    87  	b.addr.Set(b.Address())
    88  
    89  	// scan for the address
    90  	ch := make(chan bluetooth.ScanResult, 1)
    91  	err = b.adpt.Scan(func(adapter *bluetooth.Adapter, result bluetooth.ScanResult) {
    92  		if result.Address.String() == b.Address() {
    93  			b.adpt.StopScan()
    94  			b.SetName(result.LocalName())
    95  			ch <- result
    96  		}
    97  	})
    98  
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	// wait to connect to peripheral device
   104  	select {
   105  	case result := <-ch:
   106  		b.device, err = b.adpt.Connect(result.Address, bluetooth.ConnectionParams{})
   107  		if err != nil {
   108  			return err
   109  		}
   110  	}
   111  
   112  	// get all services/characteristics
   113  	srvcs, err := b.device.DiscoverServices(nil)
   114  	for _, srvc := range srvcs {
   115  		chars, err := srvc.DiscoverCharacteristics(nil)
   116  		if err != nil {
   117  			log.Println(err)
   118  			continue
   119  		}
   120  		for _, char := range chars {
   121  			b.characteristics[char.UUID().String()] = char
   122  		}
   123  	}
   124  
   125  	b.connected = true
   126  	return
   127  }
   128  
   129  // Reconnect attempts to reconnect to the BLE peripheral. If it has an active connection
   130  // it will first close that connection and then establish a new connection.
   131  // Returns true on Successful reconnection
   132  func (b *ClientAdaptor) Reconnect() (err error) {
   133  	if b.connected {
   134  		b.Disconnect()
   135  	}
   136  	return b.Connect()
   137  }
   138  
   139  // Disconnect terminates the connection to the BLE peripheral. Returns true on successful disconnect.
   140  func (b *ClientAdaptor) Disconnect() (err error) {
   141  	err = b.device.Disconnect()
   142  	time.Sleep(500 * time.Millisecond)
   143  	return
   144  }
   145  
   146  // Finalize finalizes the BLEAdaptor
   147  func (b *ClientAdaptor) Finalize() (err error) {
   148  	return b.Disconnect()
   149  }
   150  
   151  // ReadCharacteristic returns bytes from the BLE device for the
   152  // requested characteristic uuid
   153  func (b *ClientAdaptor) ReadCharacteristic(cUUID string) (data []byte, err error) {
   154  	if !b.connected {
   155  		log.Fatalf("Cannot read from BLE device until connected")
   156  		return
   157  	}
   158  
   159  	cUUID = convertUUID(cUUID)
   160  
   161  	if char, ok := b.characteristics[cUUID]; ok {
   162  		buf := make([]byte, 255)
   163  		n, err := char.Read(buf)
   164  		if err != nil {
   165  			return nil, err
   166  		}
   167  		return buf[:n], nil
   168  	}
   169  
   170  	return nil, fmt.Errorf("Unknown characteristic: %s", cUUID)
   171  }
   172  
   173  // WriteCharacteristic writes bytes to the BLE device for the
   174  // requested service and characteristic
   175  func (b *ClientAdaptor) WriteCharacteristic(cUUID string, data []byte) (err error) {
   176  	if !b.connected {
   177  		log.Println("Cannot write to BLE device until connected")
   178  		return
   179  	}
   180  
   181  	cUUID = convertUUID(cUUID)
   182  
   183  	if char, ok := b.characteristics[cUUID]; ok {
   184  		_, err := char.WriteWithoutResponse(data)
   185  		if err != nil {
   186  			return err
   187  		}
   188  		return nil
   189  	}
   190  
   191  	return fmt.Errorf("Unknown characteristic: %s", cUUID)
   192  }
   193  
   194  // Subscribe subscribes to notifications from the BLE device for the
   195  // requested service and characteristic
   196  func (b *ClientAdaptor) Subscribe(cUUID string, f func([]byte, error)) (err error) {
   197  	if !b.connected {
   198  		log.Fatalf("Cannot subscribe to BLE device until connected")
   199  		return
   200  	}
   201  
   202  	cUUID = convertUUID(cUUID)
   203  
   204  	if char, ok := b.characteristics[cUUID]; ok {
   205  		fn := func(d []byte) {
   206  			f(d, nil)
   207  		}
   208  		err = char.EnableNotifications(fn)
   209  		return
   210  	}
   211  
   212  	return fmt.Errorf("Unknown characteristic: %s", cUUID)
   213  }
   214  
   215  // getBLEDevice is singleton for bluetooth adapter connection
   216  func getBLEAdapter(impl string) (*bluetooth.Adapter, error) {
   217  	if currentAdapter != nil {
   218  		return currentAdapter, nil
   219  	}
   220  
   221  	currentAdapter = bluetooth.DefaultAdapter
   222  	err := currentAdapter.Enable()
   223  	if err != nil {
   224  		return nil, errors.Wrap(err, "can't get device")
   225  	}
   226  
   227  	return currentAdapter, nil
   228  }