Compare commits

...

3 Commits

Author SHA1 Message Date
deadprogram
97f17e93f1
gatt/darwin: macOS implementation and ensure pointer usage
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-10-03 23:41:37 +02:00
deadprogram
47770f6c59 gattc: add Read() characteristic method implementations for Linux and nRF528xx
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-10-03 23:10:46 +02:00
deadprogram
3e31cedee9 gattc: always return pointers to DeviceService and DeviceCharacteristic types to ensure consistency
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-10-03 23:02:35 +02:00
6 changed files with 103 additions and 26 deletions

@ -219,6 +219,16 @@ func handleEvent() {
} }
} }
} }
case C.BLE_GATTC_EVT_READ_RSP:
readEvent := gattcEvent.params.unionfield_read_rsp()
if debug {
println("evt: read response, data length", readEvent.len)
}
readingCharacteristic.handle_value.Set(readEvent.handle)
readingCharacteristic.offset = readEvent.offset
// Create a Go slice from the data.
readingCharacteristic.value = (*[255]byte)(unsafe.Pointer(&readEvent.data[0]))[:readEvent.len:readEvent.len]
case C.BLE_GATTC_EVT_HVX: case C.BLE_GATTC_EVT_HVX:
hvxEvent := gattcEvent.params.unionfield_hvx() hvxEvent := gattcEvent.params.unionfield_hvx()
switch hvxEvent._type { switch hvxEvent._type {

@ -68,6 +68,12 @@ func main() {
} }
for _, char := range chars { for _, char := range chars {
println("-- characteristic", char.UUID().String()) println("-- characteristic", char.UUID().String())
val, err := char.Read()
if err != nil {
println("---", err.Error())
} else {
println("--- value =", string(val))
}
} }
} }

@ -162,5 +162,9 @@ func (pd *peripheralDelegate) DidUpdateValueForCharacteristic(prph cbgo.Peripher
if char != nil && char.callback != nil { if char != nil && char.callback != nil {
go char.callback(chr.Value()) go char.callback(chr.Value())
} }
if char.readChan != nil {
char.readChan <- nil
}
} }
} }

@ -14,7 +14,7 @@ import (
// //
// Passing a nil slice of UUIDs will return a complete list of // Passing a nil slice of UUIDs will return a complete list of
// services. // services.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) { func (d *Device) DiscoverServices(uuids []UUID) ([]*DeviceService, error) {
cbuuids := []cbgo.UUID{} cbuuids := []cbgo.UUID{}
for _, u := range uuids { for _, u := range uuids {
uuid, _ := cbgo.ParseUUID(u.String()) uuid, _ := cbgo.ParseUUID(u.String())
@ -29,16 +29,16 @@ func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
// wait on channel for service discovery // wait on channel for service discovery
select { select {
case <-d.servicesChan: case <-d.servicesChan:
svcs := []DeviceService{} svcs := []*DeviceService{}
for _, dsvc := range d.prph.Services() { for _, dsvc := range d.prph.Services() {
uuid, _ := ParseUUID(dsvc.UUID().String()) uuid, _ := ParseUUID(dsvc.UUID().String())
svc := DeviceService{ svc := &DeviceService{
uuidWrapper: uuid, uuidWrapper: uuid,
device: d, device: d,
service: dsvc, service: dsvc,
} }
svcs = append(svcs, svc) svcs = append(svcs, svc)
d.services[svc.uuidWrapper] = &svc d.services[svc.uuidWrapper] = svc
} }
return svcs, nil return svcs, nil
case <-time.NewTimer(10 * time.Second).C: case <-time.NewTimer(10 * time.Second).C:
@ -73,7 +73,7 @@ func (s *DeviceService) UUID() UUID {
// //
// Passing a nil slice of UUIDs will return a complete list of // Passing a nil slice of UUIDs will return a complete list of
// characteristics. // characteristics.
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) { func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]*DeviceCharacteristic, error) {
cbuuids := []cbgo.UUID{} cbuuids := []cbgo.UUID{}
for _, u := range uuids { for _, u := range uuids {
uuid, _ := cbgo.ParseUUID(u.String()) uuid, _ := cbgo.ParseUUID(u.String())
@ -88,16 +88,16 @@ func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacter
// wait on channel for characteristic discovery // wait on channel for characteristic discovery
select { select {
case <-s.device.charsChan: case <-s.device.charsChan:
chars := []DeviceCharacteristic{} chars := []*DeviceCharacteristic{}
for _, dchar := range s.service.Characteristics() { for _, dchar := range s.service.Characteristics() {
uuid, _ := ParseUUID(dchar.UUID().String()) uuid, _ := ParseUUID(dchar.UUID().String())
char := DeviceCharacteristic{ char := &DeviceCharacteristic{
uuidWrapper: uuid, uuidWrapper: uuid,
service: s, service: s,
characteristic: dchar, characteristic: dchar,
} }
chars = append(chars, char) chars = append(chars, char)
s.device.characteristics[char.uuidWrapper] = &char s.device.characteristics[char.uuidWrapper] = char
} }
return chars, nil return chars, nil
case <-time.NewTimer(10 * time.Second).C: case <-time.NewTimer(10 * time.Second).C:
@ -114,6 +114,7 @@ type DeviceCharacteristic struct {
characteristic cbgo.Characteristic characteristic cbgo.Characteristic
callback func(buf []byte) callback func(buf []byte)
readChan chan error
} }
// UUID returns the UUID for this DeviceCharacteristic. // UUID returns the UUID for this DeviceCharacteristic.
@ -125,7 +126,7 @@ func (c *DeviceCharacteristic) UUID() UUID {
// call will return before all data has been written. A limited number of such // 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 // writes can be in flight at any given time. This call is also known as a
// "write command" (as opposed to a write request). // "write command" (as opposed to a write request).
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) { func (c *DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
c.service.device.prph.WriteCharacteristic(p, c.characteristic, false) c.service.device.prph.WriteCharacteristic(p, c.characteristic, false)
return len(p), nil return len(p), nil
@ -135,7 +136,7 @@ func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error)
// Configuration Descriptor (CCCD). This means that most peripherals will send a // Configuration Descriptor (CCCD). This means that most peripherals will send a
// notification with a new value every time the value of the characteristic // notification with a new value every time the value of the characteristic
// changes. // changes.
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error { func (c *DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
if callback == nil { if callback == nil {
return errors.New("must provide a callback for EnableNotifications") return errors.New("must provide a callback for EnableNotifications")
} }
@ -145,3 +146,20 @@ func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) err
return nil return nil
} }
// Read reads the current characteristic value.
func (c *DeviceCharacteristic) Read() (data []byte, err error) {
c.readChan = make(chan error)
c.service.device.prph.ReadCharacteristic(c.characteristic)
// wait for result
select {
case <-c.readChan:
c.readChan = nil
case <-time.NewTimer(10 * time.Second).C:
c.readChan = nil
return nil, errors.New("timeout on Read()")
}
return c.characteristic.Value(), nil
}

@ -37,7 +37,7 @@ func (s *DeviceService) UUID() UUID {
// //
// On Linux with BlueZ, this just waits for the ServicesResolved signal (if // On Linux with BlueZ, this just waits for the ServicesResolved signal (if
// services haven't been resolved yet) and uses this list of cached services. // services haven't been resolved yet) and uses this list of cached services.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) { func (d *Device) DiscoverServices(uuids []UUID) ([]*DeviceService, error) {
for { for {
resolved, err := d.device.GetServicesResolved() resolved, err := d.device.GetServicesResolved()
if err != nil { if err != nil {
@ -50,7 +50,7 @@ func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
} }
services := []DeviceService{} services := []*DeviceService{}
uuidServices := make(map[string]string) uuidServices := make(map[string]string)
servicesFound := 0 servicesFound := 0
@ -98,7 +98,7 @@ func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
} }
uuid, _ := ParseUUID(service.Properties.UUID) uuid, _ := ParseUUID(service.Properties.UUID)
ds := DeviceService{uuidWrapper: uuid, ds := &DeviceService{uuidWrapper: uuid,
service: service, service: service,
} }
@ -136,8 +136,8 @@ func (c *DeviceCharacteristic) UUID() UUID {
// //
// Passing a nil slice of UUIDs will return a complete // Passing a nil slice of UUIDs will return a complete
// list of characteristics. // list of characteristics.
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) { func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]*DeviceCharacteristic, error) {
chars := []DeviceCharacteristic{} chars := []*DeviceCharacteristic{}
uuidChars := make(map[string]string) uuidChars := make(map[string]string)
characteristicsFound := 0 characteristicsFound := 0
@ -185,7 +185,8 @@ func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacter
} }
uuid, _ := ParseUUID(char.Properties.UUID) uuid, _ := ParseUUID(char.Properties.UUID)
dc := DeviceCharacteristic{uuidWrapper: uuid, dc := &DeviceCharacteristic{
uuidWrapper: uuid,
characteristic: char, characteristic: char,
} }
@ -205,7 +206,7 @@ func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacter
// call will return before all data has been written. A limited number of such // 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 // writes can be in flight at any given time. This call is also known as a
// "write command" (as opposed to a write request). // "write command" (as opposed to a write request).
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) { func (c *DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
err = c.characteristic.WriteValue(p, nil) err = c.characteristic.WriteValue(p, nil)
if err != nil { if err != nil {
return 0, err return 0, err
@ -217,7 +218,7 @@ func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error)
// Configuration Descriptor (CCCD). This means that most peripherals will send a // Configuration Descriptor (CCCD). This means that most peripherals will send a
// notification with a new value every time the value of the characteristic // notification with a new value every time the value of the characteristic
// changes. // changes.
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error { func (c *DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
ch, err := c.characteristic.WatchProperties() ch, err := c.characteristic.WatchProperties()
if err != nil { if err != nil {
return err return err
@ -231,3 +232,9 @@ func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) err
}() }()
return c.characteristic.StartNotify() return c.characteristic.StartNotify()
} }
// Read reads the current characteristic value.
func (c *DeviceCharacteristic) Read() ([]byte, error) {
options := make(map[string]interface{})
return c.characteristic.ReadValue(options)
}

@ -62,7 +62,7 @@ func (s *DeviceService) UUID() UUID {
// //
// On the Nordic SoftDevice, only one service discovery procedure may be done at // On the Nordic SoftDevice, only one service discovery procedure may be done at
// a time. // a time.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) { func (d *Device) DiscoverServices(uuids []UUID) ([]*DeviceService, error) {
if discoveringService.state.Get() != 0 { if discoveringService.state.Get() != 0 {
// Not concurrency safe, but should catch most concurrency misuses. // Not concurrency safe, but should catch most concurrency misuses.
return nil, errAlreadyDiscovering return nil, errAlreadyDiscovering
@ -72,7 +72,7 @@ func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
if len(uuids) > 0 { if len(uuids) > 0 {
sz = len(uuids) sz = len(uuids)
} }
services := make([]DeviceService, 0, sz) services := make([]*DeviceService, 0, sz)
var shortUUIDs []C.ble_uuid_t var shortUUIDs []C.ble_uuid_t
@ -133,7 +133,7 @@ func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
} }
// Store the discovered service. // Store the discovered service.
svc := DeviceService{ svc := &DeviceService{
uuid: suuid, uuid: suuid,
connectionHandle: d.connectionHandle, connectionHandle: d.connectionHandle,
startHandle: startHandle, startHandle: startHandle,
@ -191,7 +191,7 @@ var discoveringCharacteristic struct {
// //
// Passing a nil slice of UUIDs will return a complete // Passing a nil slice of UUIDs will return a complete
// list of characteristics. // list of characteristics.
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) { func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]*DeviceCharacteristic, error) {
if discoveringCharacteristic.handle_value.Get() != 0 { if discoveringCharacteristic.handle_value.Get() != 0 {
return nil, errAlreadyDiscovering return nil, errAlreadyDiscovering
} }
@ -200,7 +200,7 @@ func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacter
if len(uuids) > 0 { if len(uuids) > 0 {
sz = len(uuids) sz = len(uuids)
} }
characteristics := make([]DeviceCharacteristic, 0, sz) characteristics := make([]*DeviceCharacteristic, 0, sz)
var shortUUIDs []C.ble_uuid_t var shortUUIDs []C.ble_uuid_t
@ -276,7 +276,7 @@ func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacter
permissions |= CharacteristicIndicatePermission permissions |= CharacteristicIndicatePermission
} }
dc := DeviceCharacteristic{uuid: discoveringCharacteristic.uuid} dc := &DeviceCharacteristic{uuid: discoveringCharacteristic.uuid}
dc.permissions = permissions dc.permissions = permissions
dc.valueHandle = foundCharacteristicHandle dc.valueHandle = foundCharacteristicHandle
@ -319,7 +319,7 @@ func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacter
// call will return before all data has been written. A limited number of such // 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 // writes can be in flight at any given time. This call is also known as a
// "write command" (as opposed to a write request). // "write command" (as opposed to a write request).
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) { func (c *DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
if len(p) == 0 { if len(p) == 0 {
return 0, nil return 0, nil
} }
@ -337,6 +337,38 @@ func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error)
return len(p), nil return len(p), nil
} }
// A global used to pass information from the event handler back to the
// Read function below.
var readingCharacteristic struct {
handle_value volatile.Register16
offset uint16
value []byte
}
// Read reads the current characteristic value up to MTU length.
// A future enhancement would be to be able to retrieve a longer
// value by making multiple calls.
func (c *DeviceCharacteristic) Read() ([]byte, error) {
errCode := C.sd_ble_gattc_read(c.connectionHandle, c.valueHandle, 0)
if errCode != 0 {
return nil, Error(errCode)
}
// wait for response with data
for readingCharacteristic.handle_value.Get() == 0 {
arm.Asm("wfe")
}
// copy data since value is slice to unsafe pointer.
data := make([]byte, len(readingCharacteristic.value))
copy(data, readingCharacteristic.value)
// prepare for next read
readingCharacteristic.handle_value.Set(0)
return data, nil
}
type gattcNotificationCallback struct { type gattcNotificationCallback struct {
connectionHandle uint16 connectionHandle uint16
valueHandle uint16 // may be 0 if the slot is empty valueHandle uint16 // may be 0 if the slot is empty
@ -356,7 +388,7 @@ var gattcNotificationCallbacks []gattcNotificationCallback
// Warning: when using the SoftDevice, the callback is called from an interrupt // Warning: when using the SoftDevice, the callback is called from an interrupt
// which means there are various limitations (such as not being able to allocate // which means there are various limitations (such as not being able to allocate
// heap memory). // heap memory).
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error { func (c *DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
if c.permissions&CharacteristicNotifyPermission == 0 { if c.permissions&CharacteristicNotifyPermission == 0 {
return errNoNotify return errNoNotify
} }