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