2023-12-21 01:29:23 +03:00
|
|
|
//go:build ninafw
|
|
|
|
|
|
|
|
package bluetooth
|
|
|
|
|
|
|
|
import "errors"
|
|
|
|
|
|
|
|
var (
|
|
|
|
errNotYetImplemented = errors.New("bluetooth: not yet implemented")
|
|
|
|
errNoWrite = errors.New("bluetooth: write not permitted")
|
|
|
|
errNoWriteWithoutResponse = errors.New("bluetooth: write without response not permitted")
|
|
|
|
errWriteFailed = errors.New("bluetooth: write failed")
|
|
|
|
errNoRead = errors.New("bluetooth: read not permitted")
|
|
|
|
errReadFailed = errors.New("bluetooth: read failed")
|
|
|
|
errNoNotify = errors.New("bluetooth: notify/indicate not permitted")
|
|
|
|
errEnableNotificationsFailed = errors.New("bluetooth: enable notifications failed")
|
|
|
|
errServiceNotFound = errors.New("bluetooth: service not found")
|
|
|
|
errCharacteristicNotFound = errors.New("bluetooth: characteristic not found")
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
maxDefaultServicesToDiscover = 6
|
|
|
|
maxDefaultCharacteristicsToDiscover = 8
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
charPropertyBroadcast = 0x01
|
|
|
|
charPropertyRead = 0x02
|
|
|
|
charPropertyWriteWithoutResponse = 0x04
|
|
|
|
charPropertyWrite = 0x08
|
|
|
|
charPropertyNotify = 0x10
|
|
|
|
charPropertyIndicate = 0x20
|
|
|
|
)
|
|
|
|
|
|
|
|
// DeviceService is a BLE service on a connected peripheral device.
|
|
|
|
type DeviceService struct {
|
|
|
|
uuid UUID
|
|
|
|
|
2023-12-25 16:28:56 +03:00
|
|
|
device Device
|
2023-12-21 01:29:23 +03:00
|
|
|
startHandle, endHandle uint16
|
|
|
|
}
|
|
|
|
|
|
|
|
// UUID returns the UUID for this DeviceService.
|
2024-01-03 23:01:40 +03:00
|
|
|
func (s DeviceService) UUID() UUID {
|
2023-12-21 01:29:23 +03:00
|
|
|
return s.uuid
|
|
|
|
}
|
|
|
|
|
|
|
|
// DiscoverServices starts a service discovery procedure. Pass a list of service
|
|
|
|
// UUIDs you are interested in to this function. Either a slice of all services
|
|
|
|
// is returned (of the same length as the requested UUIDs and in the same
|
|
|
|
// order), or if some services could not be discovered an error is returned.
|
|
|
|
//
|
|
|
|
// Passing a nil slice of UUIDs will return a complete list of
|
|
|
|
// services.
|
2023-12-25 16:28:56 +03:00
|
|
|
func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
2024-01-16 19:18:32 +03:00
|
|
|
if debug {
|
2023-12-21 01:29:23 +03:00
|
|
|
println("DiscoverServices")
|
|
|
|
}
|
|
|
|
|
|
|
|
services := make([]DeviceService, 0, maxDefaultServicesToDiscover)
|
|
|
|
foundServices := make(map[UUID]DeviceService)
|
|
|
|
|
|
|
|
startHandle := uint16(0x0001)
|
|
|
|
endHandle := uint16(0xffff)
|
|
|
|
for endHandle == uint16(0xffff) {
|
|
|
|
err := d.adapter.att.readByGroupReq(d.handle, startHandle, endHandle, gattServiceUUID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-01-16 19:18:32 +03:00
|
|
|
if debug {
|
2023-12-21 01:29:23 +03:00
|
|
|
println("found d.adapter.att.services", len(d.adapter.att.services))
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(d.adapter.att.services) == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rawService := range d.adapter.att.services {
|
|
|
|
if len(uuids) == 0 || rawService.uuid.isIn(uuids) {
|
|
|
|
foundServices[rawService.uuid] =
|
|
|
|
DeviceService{
|
|
|
|
device: d,
|
|
|
|
uuid: rawService.uuid,
|
|
|
|
startHandle: rawService.startHandle,
|
|
|
|
endHandle: rawService.endHandle,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
startHandle = rawService.endHandle + 1
|
|
|
|
if startHandle == 0x0000 {
|
|
|
|
endHandle = 0x0000
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset raw services
|
|
|
|
d.adapter.att.services = []rawService{}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case len(uuids) > 0:
|
|
|
|
// put into correct order
|
|
|
|
for _, uuid := range uuids {
|
|
|
|
s, ok := foundServices[uuid]
|
|
|
|
if !ok {
|
|
|
|
return nil, errServiceNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
services = append(services, s)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
for _, s := range foundServices {
|
|
|
|
services = append(services, s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return services, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeviceCharacteristic is a BLE characteristic on a connected peripheral
|
|
|
|
// device.
|
|
|
|
type DeviceCharacteristic struct {
|
|
|
|
uuid UUID
|
|
|
|
|
|
|
|
service *DeviceService
|
|
|
|
permissions CharacteristicPermissions
|
|
|
|
handle uint16
|
|
|
|
properties uint8
|
|
|
|
callback func(buf []byte)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UUID returns the UUID for this DeviceCharacteristic.
|
2024-01-03 23:01:40 +03:00
|
|
|
func (c DeviceCharacteristic) UUID() UUID {
|
2023-12-21 01:29:23 +03:00
|
|
|
return c.uuid
|
|
|
|
}
|
|
|
|
|
|
|
|
// DiscoverCharacteristics discovers characteristics in this service. Pass a
|
|
|
|
// list of characteristic UUIDs you are interested in to this function. Either a
|
|
|
|
// list of all requested services is returned, or if some services could not be
|
|
|
|
// discovered an error is returned. If there is no error, the characteristics
|
|
|
|
// slice has the same length as the UUID slice with characteristics in the same
|
|
|
|
// order in the slice as in the requested UUID list.
|
|
|
|
//
|
|
|
|
// Passing a nil slice of UUIDs will return a complete
|
|
|
|
// list of characteristics.
|
2024-01-03 23:01:40 +03:00
|
|
|
func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
|
2024-01-16 19:18:32 +03:00
|
|
|
if debug {
|
2023-12-21 01:29:23 +03:00
|
|
|
println("DiscoverCharacteristics")
|
|
|
|
}
|
|
|
|
|
|
|
|
characteristics := make([]DeviceCharacteristic, 0, maxDefaultCharacteristicsToDiscover)
|
|
|
|
foundCharacteristics := make(map[UUID]DeviceCharacteristic)
|
|
|
|
|
|
|
|
startHandle := s.startHandle
|
|
|
|
endHandle := s.endHandle
|
|
|
|
for startHandle < endHandle {
|
|
|
|
err := s.device.adapter.att.readByTypeReq(s.device.handle, startHandle, endHandle, gattCharacteristicUUID)
|
|
|
|
switch {
|
|
|
|
case err == ErrATTOp &&
|
|
|
|
s.device.adapter.att.lastErrorOpcode == attOpReadByTypeReq &&
|
|
|
|
s.device.adapter.att.lastErrorCode == attErrorAttrNotFound:
|
|
|
|
|
|
|
|
// no characteristics found
|
|
|
|
break
|
|
|
|
case err != nil:
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-01-16 19:18:32 +03:00
|
|
|
if debug {
|
2023-12-21 01:29:23 +03:00
|
|
|
println("found s.device.adapter.att.characteristics", len(s.device.adapter.att.characteristics))
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(s.device.adapter.att.characteristics) == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rawCharacteristic := range s.device.adapter.att.characteristics {
|
|
|
|
if len(uuids) == 0 || rawCharacteristic.uuid.isIn(uuids) {
|
|
|
|
dc := DeviceCharacteristic{
|
2024-01-03 23:01:40 +03:00
|
|
|
service: &s,
|
2023-12-21 01:29:23 +03:00
|
|
|
uuid: rawCharacteristic.uuid,
|
|
|
|
handle: rawCharacteristic.valueHandle,
|
|
|
|
properties: rawCharacteristic.properties,
|
|
|
|
permissions: CharacteristicPermissions(rawCharacteristic.properties),
|
|
|
|
}
|
|
|
|
|
|
|
|
foundCharacteristics[rawCharacteristic.uuid] = dc
|
|
|
|
}
|
|
|
|
|
|
|
|
startHandle = rawCharacteristic.valueHandle + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset raw characteristics
|
|
|
|
s.device.adapter.att.characteristics = []rawCharacteristic{}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case len(uuids) > 0:
|
|
|
|
// put into correct order
|
|
|
|
for _, uuid := range uuids {
|
|
|
|
c, ok := foundCharacteristics[uuid]
|
|
|
|
if !ok {
|
|
|
|
return nil, errCharacteristicNotFound
|
|
|
|
}
|
|
|
|
characteristics = append(characteristics, c)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
for _, c := range foundCharacteristics {
|
|
|
|
characteristics = append(characteristics, c)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return characteristics, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteWithoutResponse replaces the characteristic value with a new value. The
|
|
|
|
// call will return before all data has been written. A limited number of such
|
|
|
|
// writes can be in flight at any given time. This call is also known as a
|
|
|
|
// "write command" (as opposed to a write request).
|
|
|
|
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
|
|
|
|
if !c.permissions.WriteWithoutResponse() {
|
|
|
|
return 0, errNoWriteWithoutResponse
|
|
|
|
}
|
|
|
|
|
|
|
|
err = c.service.device.adapter.att.writeCmd(c.service.device.handle, c.handle, p)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return len(p), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// EnableNotifications enables notifications in the Client Characteristic
|
|
|
|
// Configuration Descriptor (CCCD). This means that most peripherals will send a
|
|
|
|
// notification with a new value every time the value of the characteristic
|
|
|
|
// changes.
|
|
|
|
//
|
|
|
|
// Users may call EnableNotifications with a nil callback to disable notifications.
|
2024-01-03 23:01:40 +03:00
|
|
|
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
|
2023-12-21 01:29:23 +03:00
|
|
|
if !c.permissions.Notify() {
|
|
|
|
return errNoNotify
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case callback == nil:
|
|
|
|
// disable notifications
|
2024-01-16 19:18:32 +03:00
|
|
|
if debug {
|
2023-12-21 01:29:23 +03:00
|
|
|
println("disabling notifications")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.service.device.adapter.att.writeReq(c.service.device.handle, c.handle+1, []byte{0x00, 0x00})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// enable notifications
|
2024-01-16 19:18:32 +03:00
|
|
|
if debug {
|
2023-12-21 01:29:23 +03:00
|
|
|
println("enabling notifications")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.service.device.adapter.att.writeReq(c.service.device.handle, c.handle+1, []byte{0x01, 0x00})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c.callback = callback
|
|
|
|
|
|
|
|
c.service.device.startNotifications()
|
|
|
|
c.service.device.addNotificationRegistration(c.handle, c.callback)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetMTU returns the MTU for the characteristic.
|
|
|
|
func (c DeviceCharacteristic) GetMTU() (uint16, error) {
|
2024-01-04 00:14:24 +03:00
|
|
|
err := c.service.device.adapter.att.mtuReq(c.service.device.handle, c.service.device.mtu)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c.service.device.mtu = c.service.device.adapter.att.mtu
|
|
|
|
|
|
|
|
return c.service.device.mtu, nil
|
2023-12-21 01:29:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read reads the current characteristic value.
|
2024-01-03 23:01:40 +03:00
|
|
|
func (c DeviceCharacteristic) Read(data []byte) (int, error) {
|
2023-12-21 01:29:23 +03:00
|
|
|
if !c.permissions.Read() {
|
|
|
|
return 0, errNoRead
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.service.device.adapter.att.readReq(c.service.device.handle, c.handle)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(c.service.device.adapter.att.value) == 0 {
|
|
|
|
return 0, errReadFailed
|
|
|
|
}
|
|
|
|
|
|
|
|
copy(data, c.service.device.adapter.att.value)
|
|
|
|
|
|
|
|
return len(c.service.device.adapter.att.value), nil
|
|
|
|
}
|