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 }