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 }