Skip to content

Commit

Permalink
ninafw: Receive notifications on characteristics
Browse files Browse the repository at this point in the history
Signed-off-by: deadprogram <[email protected]>
  • Loading branch information
deadprogram committed Jan 3, 2024
1 parent 62d698a commit 8abebc2
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 104 deletions.
55 changes: 50 additions & 5 deletions adapter_ninafw.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"time"
)

const maxConnections = 1

// Adapter represents the UART connection to the NINA fw.
type Adapter struct {
hci *hci
Expand All @@ -20,7 +22,7 @@ type Adapter struct {
reset func()
connectHandler func(device Address, connected bool)

connectedDevices map[uint16]*Device
connectedDevices []*Device
notificationsStarted bool
}

Expand All @@ -33,7 +35,7 @@ var DefaultAdapter = &Adapter{
connectHandler: func(device Address, connected bool) {
return
},
connectedDevices: make(map[uint16]*Device),
connectedDevices: make([]*Device, 0, maxConnections),
}

// Enable configures the BLE stack. It must be called before any
Expand Down Expand Up @@ -135,12 +137,21 @@ func (a *Adapter) startNotifications() {
return
}

if _debug {
println("starting notifications...")
}

a.notificationsStarted = true

// go routine to poll for HCI events for ATT notifications
go func() {
for {
a.att.poll()
if err := a.att.poll(); err != nil {
// TODO: handle error
if _debug {
println("error polling for notifications:", err.Error())
}
}

time.Sleep(250 * time.Millisecond)
}
Expand All @@ -151,14 +162,48 @@ func (a *Adapter) startNotifications() {
for {
select {
case not := <-a.att.notifications:
// TODO: find and call the handler
if _debug {
println("notification received", not.handle, not.data)
println("notification received", not.connectionHandle, not.handle, not.data)
}

d := a.findDevice(not.connectionHandle)
if d == nil {
if _debug {
println("no device found for handle", not.connectionHandle)
}
continue
}

n := d.findNotificationRegistration(not.handle)
if n == nil {
if _debug {
println("no notification registered for handle", not.handle)
}
continue
}

if n.callback != nil {
n.callback(not.data)
}

default:
}

runtime.Gosched()
}
}()
}

func (a *Adapter) findDevice(handle uint16) *Device {
for _, d := range a.connectedDevices {
if d.handle == handle {
if _debug {
println("found device", handle, d.Address.String(), "with notifications registered", len(d.notificationRegistrations))
}

return d
}
}

return nil
}
115 changes: 105 additions & 10 deletions att_ninafw.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ const (
)

var (
ErrATTTimeout = errors.New("ATT timeout")
ErrATTUnknownEvent = errors.New("ATT unknown event")
ErrATTUnknown = errors.New("ATT unknown error")
ErrATTOp = errors.New("ATT OP error")
ErrATTTimeout = errors.New("bluetooth: ATT timeout")
ErrATTUnknownEvent = errors.New("bluetooth: ATT unknown event")
ErrATTUnknown = errors.New("bluetooth: ATT unknown error")
ErrATTOp = errors.New("bluetooth: ATT OP error")
)

type rawService struct {
Expand All @@ -94,8 +94,9 @@ type rawDescriptor struct {
}

type rawNotification struct {
handle uint16
data []byte
connectionHandle uint16
handle uint16
data []byte
}

type att struct {
Expand All @@ -108,6 +109,7 @@ type att struct {
lastErrorCode uint8
services []rawService
characteristics []rawCharacteristic
descriptors []rawDescriptor
value []byte
notifications chan rawNotification
}
Expand Down Expand Up @@ -164,6 +166,26 @@ func (a *att) readByTypeReq(connectionHandle, startHandle, endHandle uint16, typ
return a.waitUntilResponse()
}

func (a *att) findInfoReq(connectionHandle, startHandle, endHandle uint16) error {
if _debug {
println("att.findInfoReq:", connectionHandle, startHandle, endHandle)
}

a.busy.Lock()
defer a.busy.Unlock()

var b [5]byte
b[0] = attOpFindInfoReq
binary.LittleEndian.PutUint16(b[1:], startHandle)
binary.LittleEndian.PutUint16(b[3:], endHandle)

if err := a.sendReq(connectionHandle, b[:]); err != nil {
return err
}

return a.waitUntilResponse()
}

func (a *att) readReq(connectionHandle, valueHandle uint16) error {
if _debug {
println("att.readReq:", connectionHandle, valueHandle)
Expand All @@ -185,7 +207,7 @@ func (a *att) readReq(connectionHandle, valueHandle uint16) error {

func (a *att) writeCmd(connectionHandle, valueHandle uint16, data []byte) error {
if _debug {
println("att.writeCmd:", connectionHandle, valueHandle)
println("att.writeCmd:", connectionHandle, valueHandle, hex.EncodeToString(data))
}

a.busy.Lock()
Expand All @@ -202,6 +224,25 @@ func (a *att) writeCmd(connectionHandle, valueHandle uint16, data []byte) error
return a.waitUntilResponse()
}

func (a *att) writeReq(connectionHandle, valueHandle uint16, data []byte) error {
if _debug {
println("att.writeReq:", connectionHandle, valueHandle, hex.EncodeToString(data))
}

a.busy.Lock()
defer a.busy.Unlock()

var b [3]byte
b[0] = attOpWriteReq
binary.LittleEndian.PutUint16(b[1:], valueHandle)

if err := a.sendReq(connectionHandle, append(b[:], data...)); err != nil {
return err
}

return a.waitUntilResponse()
}

func (a *att) sendReq(handle uint16, data []byte) error {
a.clearResponse()

Expand Down Expand Up @@ -253,6 +294,30 @@ func (a *att) handleData(handle uint16, buf []byte) error {
if _debug {
println("att.handleData: attOpFindInfoResponse")
}
a.responded = true

lengthPerDescriptor := int(buf[1])
var uuid [16]byte

for i := 2; i < len(buf); i += lengthPerDescriptor {
d := rawDescriptor{
handle: binary.LittleEndian.Uint16(buf[i:]),
}
switch lengthPerDescriptor - 2 {
case 2:
d.uuid = New16BitUUID(binary.LittleEndian.Uint16(buf[i+2:]))
case 16:
copy(uuid[:], buf[i+2:])
slices.Reverse(uuid[:])
d.uuid = NewUUID(uuid)
}

if _debug {
println("att.handleData: descriptor", d.handle, d.uuid.String())
}

a.descriptors = append(a.descriptors, d)
}

case attOpFindByTypeReq:
if _debug {
Expand Down Expand Up @@ -302,6 +367,33 @@ func (a *att) handleData(handle uint16, buf []byte) error {
println("att.handleData: attOpReadByGroupReq")
}

// return generic services
var response [14]byte
response[0] = attOpReadByGroupResponse
response[1] = 0x06 // length per service

genericAccessService := rawService{
startHandle: 0,
endHandle: 1,
uuid: ServiceUUIDGenericAccess,
}
binary.LittleEndian.PutUint16(response[2:], genericAccessService.startHandle)
binary.LittleEndian.PutUint16(response[4:], genericAccessService.endHandle)
binary.LittleEndian.PutUint16(response[6:], genericAccessService.uuid.Get16Bit())

genericAttributeService := rawService{
startHandle: 2,
endHandle: 5,
uuid: ServiceUUIDGenericAttribute,
}
binary.LittleEndian.PutUint16(response[8:], genericAttributeService.startHandle)
binary.LittleEndian.PutUint16(response[10:], genericAttributeService.endHandle)
binary.LittleEndian.PutUint16(response[12:], genericAttributeService.uuid.Get16Bit())

if err := a.hci.sendAclPkt(handle, attCID, response[:]); err != nil {
return err
}

case attOpReadByGroupResponse:
if _debug {
println("att.handleData: attOpReadByGroupResponse")
Expand Down Expand Up @@ -383,8 +475,9 @@ func (a *att) handleData(handle uint16, buf []byte) error {
}

not := rawNotification{
handle: binary.LittleEndian.Uint16(buf[1:]),
data: []byte{},
connectionHandle: handle,
handle: binary.LittleEndian.Uint16(buf[1:]),
data: []byte{},
}
not.data = append(not.data, buf[3:]...)

Expand Down Expand Up @@ -460,7 +553,9 @@ func (a *att) poll() error {
a.busy.Lock()
defer a.busy.Unlock()

a.hci.poll()
if err := a.hci.poll(); err != nil {
return err
}

return nil
}
Loading

0 comments on commit 8abebc2

Please sign in to comment.