ninafw: implement BLE peripheral support
Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
parent
b8c79250c7
commit
10d1c71078
5 changed files with 912 additions and 79 deletions
672
att_ninafw.go
672
att_ninafw.go
|
@ -62,10 +62,11 @@ const (
|
||||||
attErrorUnsupportedGroupType = 0x10
|
attErrorUnsupportedGroupType = 0x10
|
||||||
attErrorInsufficientResources = 0x11
|
attErrorInsufficientResources = 0x11
|
||||||
|
|
||||||
gattUnknownUUID = 0x0000
|
gattUnknownUUID = 0x0000
|
||||||
gattServiceUUID = 0x2800
|
gattServiceUUID = 0x2800
|
||||||
gattCharacteristicUUID = 0x2803
|
gattCharacteristicUUID = 0x2803
|
||||||
gattDescriptorUUID = 0x2900
|
gattDescriptorUUID = 0x2900
|
||||||
|
gattClientCharacteristicConfigUUID = 0x2902
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -81,16 +82,109 @@ type rawService struct {
|
||||||
uuid UUID
|
uuid UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *rawService) Write(buf []byte) (int, error) {
|
||||||
|
s.startHandle = binary.LittleEndian.Uint16(buf[0:])
|
||||||
|
s.endHandle = binary.LittleEndian.Uint16(buf[2:])
|
||||||
|
|
||||||
|
sz := 4
|
||||||
|
switch len(buf) - 4 {
|
||||||
|
case 2:
|
||||||
|
s.uuid = New16BitUUID(binary.LittleEndian.Uint16(buf[4:]))
|
||||||
|
sz += 2
|
||||||
|
case 16:
|
||||||
|
var uuid [16]byte
|
||||||
|
copy(uuid[:], buf[4:])
|
||||||
|
slices.Reverse(uuid[:])
|
||||||
|
s.uuid = NewUUID(uuid)
|
||||||
|
sz += 16
|
||||||
|
}
|
||||||
|
|
||||||
|
return sz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rawService) Read(p []byte) (int, error) {
|
||||||
|
binary.LittleEndian.PutUint16(p[0:], s.startHandle)
|
||||||
|
binary.LittleEndian.PutUint16(p[2:], s.endHandle)
|
||||||
|
|
||||||
|
sz := 4
|
||||||
|
switch {
|
||||||
|
case s.uuid.Is16Bit():
|
||||||
|
binary.LittleEndian.PutUint16(p[4:], s.uuid.Get16Bit())
|
||||||
|
sz += 2
|
||||||
|
default:
|
||||||
|
uuid := s.uuid.Bytes()
|
||||||
|
copy(p[4:], uuid[:])
|
||||||
|
sz += 16
|
||||||
|
}
|
||||||
|
|
||||||
|
return sz, nil
|
||||||
|
}
|
||||||
|
|
||||||
type rawCharacteristic struct {
|
type rawCharacteristic struct {
|
||||||
startHandle uint16
|
startHandle uint16
|
||||||
properties uint8
|
properties uint8
|
||||||
valueHandle uint16
|
valueHandle uint16
|
||||||
uuid UUID
|
uuid UUID
|
||||||
|
chr *Characteristic
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rawCharacteristic) Write(buf []byte) (int, error) {
|
||||||
|
c.startHandle = binary.LittleEndian.Uint16(buf[0:])
|
||||||
|
c.properties = buf[2]
|
||||||
|
c.valueHandle = binary.LittleEndian.Uint16(buf[3:])
|
||||||
|
|
||||||
|
sz := 5
|
||||||
|
switch len(buf) - 5 {
|
||||||
|
case 2:
|
||||||
|
c.uuid = New16BitUUID(binary.LittleEndian.Uint16(buf[5:]))
|
||||||
|
sz += 2
|
||||||
|
case 16:
|
||||||
|
var uuid [16]byte
|
||||||
|
copy(uuid[:], buf[5:])
|
||||||
|
slices.Reverse(uuid[:])
|
||||||
|
c.uuid = NewUUID(uuid)
|
||||||
|
sz += 16
|
||||||
|
}
|
||||||
|
|
||||||
|
return sz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rawCharacteristic) Read(p []byte) (int, error) {
|
||||||
|
binary.LittleEndian.PutUint16(p[0:], c.startHandle)
|
||||||
|
p[2] = c.properties
|
||||||
|
binary.LittleEndian.PutUint16(p[3:], c.valueHandle)
|
||||||
|
|
||||||
|
sz := 5
|
||||||
|
switch {
|
||||||
|
case c.uuid.Is16Bit():
|
||||||
|
binary.LittleEndian.PutUint16(p[5:], c.uuid.Get16Bit())
|
||||||
|
sz += 2
|
||||||
|
default:
|
||||||
|
uuid := c.uuid.Bytes()
|
||||||
|
copy(p[5:], uuid[:])
|
||||||
|
sz += 16
|
||||||
|
}
|
||||||
|
|
||||||
|
return sz, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawDescriptor struct {
|
type rawDescriptor struct {
|
||||||
handle uint16
|
handle uint16
|
||||||
uuid UUID
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *rawDescriptor) Write(buf []byte) (int, error) {
|
||||||
|
d.handle = binary.LittleEndian.Uint16(buf[0:])
|
||||||
|
d.data = append(d.data, buf[2:]...)
|
||||||
|
|
||||||
|
return len(d.data) + 2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *rawDescriptor) Read(p []byte) (int, error) {
|
||||||
|
binary.LittleEndian.PutUint16(p[0:], d.handle)
|
||||||
|
copy(p[2:], d.data)
|
||||||
|
|
||||||
|
return len(d.data) + 2, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawNotification struct {
|
type rawNotification struct {
|
||||||
|
@ -99,6 +193,65 @@ type rawNotification struct {
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type attributeType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
attributeTypeService attributeType = iota
|
||||||
|
attributeTypeCharacteristic
|
||||||
|
attributeTypeCharacteristicValue
|
||||||
|
attributeTypeDescriptor
|
||||||
|
)
|
||||||
|
|
||||||
|
type rawAttribute struct {
|
||||||
|
typ attributeType
|
||||||
|
parent uint16
|
||||||
|
handle uint16
|
||||||
|
uuid UUID
|
||||||
|
permissions CharacteristicPermissions
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *rawAttribute) Write(buf []byte) (int, error) {
|
||||||
|
return 0, errNotYetImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *rawAttribute) Read(p []byte) (int, error) {
|
||||||
|
binary.LittleEndian.PutUint16(p[0:], a.handle)
|
||||||
|
sz := 2
|
||||||
|
|
||||||
|
switch a.typ {
|
||||||
|
case attributeTypeCharacteristicValue, attributeTypeDescriptor:
|
||||||
|
switch {
|
||||||
|
case a.uuid.Is16Bit():
|
||||||
|
binary.LittleEndian.PutUint16(p[sz:], a.uuid.Get16Bit())
|
||||||
|
sz += 2
|
||||||
|
default:
|
||||||
|
uuid := a.uuid.Bytes()
|
||||||
|
copy(p[sz:], uuid[:])
|
||||||
|
sz += 16
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
copy(p[sz:], a.value)
|
||||||
|
sz += len(a.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *rawAttribute) length() int {
|
||||||
|
switch a.typ {
|
||||||
|
case attributeTypeCharacteristicValue, attributeTypeDescriptor:
|
||||||
|
switch {
|
||||||
|
case a.uuid.Is16Bit():
|
||||||
|
return 2
|
||||||
|
default:
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return len(a.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type att struct {
|
type att struct {
|
||||||
hci *hci
|
hci *hci
|
||||||
busy sync.Mutex
|
busy sync.Mutex
|
||||||
|
@ -113,6 +266,11 @@ type att struct {
|
||||||
descriptors []rawDescriptor
|
descriptors []rawDescriptor
|
||||||
value []byte
|
value []byte
|
||||||
notifications chan rawNotification
|
notifications chan rawNotification
|
||||||
|
|
||||||
|
connections []uint16
|
||||||
|
lastHandle uint16
|
||||||
|
attributes []rawAttribute
|
||||||
|
localServices []rawService
|
||||||
}
|
}
|
||||||
|
|
||||||
func newATT(hci *hci) *att {
|
func newATT(hci *hci) *att {
|
||||||
|
@ -122,6 +280,10 @@ func newATT(hci *hci) *att {
|
||||||
characteristics: []rawCharacteristic{},
|
characteristics: []rawCharacteristic{},
|
||||||
value: []byte{},
|
value: []byte{},
|
||||||
notifications: make(chan rawNotification, 32),
|
notifications: make(chan rawNotification, 32),
|
||||||
|
connections: []uint16{},
|
||||||
|
lastHandle: 0x0001,
|
||||||
|
attributes: []rawAttribute{},
|
||||||
|
localServices: []rawService{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +439,51 @@ func (a *att) sendReq(handle uint16, data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *att) sendNotification(handle uint16, data []byte) error {
|
||||||
|
if _debug {
|
||||||
|
println("att.sendNotifications:", handle, "data:", hex.EncodeToString(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
a.busy.Lock()
|
||||||
|
defer a.busy.Unlock()
|
||||||
|
|
||||||
|
var b [3]byte
|
||||||
|
b[0] = attOpHandleNotify
|
||||||
|
binary.LittleEndian.PutUint16(b[1:], handle)
|
||||||
|
|
||||||
|
for connection := range a.connections {
|
||||||
|
if _debug {
|
||||||
|
println("att.sendNotifications: sending to", connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.hci.sendAclPkt(uint16(connection), attCID, append(b[:], data...)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) sendError(handle uint16, opcode uint8, hdl uint16, code uint8) error {
|
||||||
|
a.clearResponse()
|
||||||
|
|
||||||
|
if _debug {
|
||||||
|
println("att.sendError:", handle, "data:", opcode, hdl, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
var b [5]byte
|
||||||
|
b[0] = attOpError
|
||||||
|
b[1] = opcode
|
||||||
|
binary.LittleEndian.PutUint16(b[2:], hdl)
|
||||||
|
b[4] = code
|
||||||
|
|
||||||
|
if err := a.hci.sendAclPkt(handle, attCID, b[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *att) handleData(handle uint16, buf []byte) error {
|
func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
if _debug {
|
if _debug {
|
||||||
println("att.handleData:", handle, "data:", hex.EncodeToString(buf))
|
println("att.handleData:", handle, "data:", hex.EncodeToString(buf))
|
||||||
|
@ -299,6 +506,11 @@ func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
if _debug {
|
if _debug {
|
||||||
println("att.handleData: attOpMTUReq")
|
println("att.handleData: attOpMTUReq")
|
||||||
}
|
}
|
||||||
|
a.mtu = binary.LittleEndian.Uint16(buf[1:])
|
||||||
|
response := [3]byte{attOpMTUResponse, buf[1], buf[2]}
|
||||||
|
if err := a.hci.sendAclPkt(handle, attCID, response[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
case attOpMTUResponse:
|
case attOpMTUResponse:
|
||||||
if _debug {
|
if _debug {
|
||||||
|
@ -312,6 +524,11 @@ func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
println("att.handleData: attOpFindInfoReq")
|
println("att.handleData: attOpFindInfoReq")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startHandle := binary.LittleEndian.Uint16(buf[1:])
|
||||||
|
endHandle := binary.LittleEndian.Uint16(buf[3:])
|
||||||
|
|
||||||
|
return a.handleFindInfoReq(handle, startHandle, endHandle)
|
||||||
|
|
||||||
case attOpFindInfoResponse:
|
case attOpFindInfoResponse:
|
||||||
if _debug {
|
if _debug {
|
||||||
println("att.handleData: attOpFindInfoResponse")
|
println("att.handleData: attOpFindInfoResponse")
|
||||||
|
@ -319,23 +536,13 @@ func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
a.responded = true
|
a.responded = true
|
||||||
|
|
||||||
lengthPerDescriptor := int(buf[1])
|
lengthPerDescriptor := int(buf[1])
|
||||||
var uuid [16]byte
|
|
||||||
|
|
||||||
for i := 2; i < len(buf); i += lengthPerDescriptor {
|
for i := 2; i < len(buf); i += lengthPerDescriptor {
|
||||||
d := rawDescriptor{
|
d := rawDescriptor{}
|
||||||
handle: binary.LittleEndian.Uint16(buf[i:]),
|
d.Write(buf[i : i+lengthPerDescriptor])
|
||||||
}
|
|
||||||
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 {
|
if _debug {
|
||||||
println("att.handleData: descriptor", d.handle, d.uuid.String())
|
println("att.handleData: descriptor", d.handle, hex.EncodeToString(d.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
a.descriptors = append(a.descriptors, d)
|
a.descriptors = append(a.descriptors, d)
|
||||||
|
@ -351,6 +558,12 @@ func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
println("att.handleData: attOpReadByTypeReq")
|
println("att.handleData: attOpReadByTypeReq")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startHandle := binary.LittleEndian.Uint16(buf[1:])
|
||||||
|
endHandle := binary.LittleEndian.Uint16(buf[3:])
|
||||||
|
uuid := shortUUID(binary.LittleEndian.Uint16(buf[5:]))
|
||||||
|
|
||||||
|
return a.handleReadByTypeReq(handle, startHandle, endHandle, uuid)
|
||||||
|
|
||||||
case attOpReadByTypeResponse:
|
case attOpReadByTypeResponse:
|
||||||
if _debug {
|
if _debug {
|
||||||
println("att.handleData: attOpReadByTypeResponse")
|
println("att.handleData: attOpReadByTypeResponse")
|
||||||
|
@ -358,22 +571,10 @@ func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
a.responded = true
|
a.responded = true
|
||||||
|
|
||||||
lengthPerCharacteristic := int(buf[1])
|
lengthPerCharacteristic := int(buf[1])
|
||||||
var uuid [16]byte
|
|
||||||
|
|
||||||
for i := 2; i < len(buf); i += lengthPerCharacteristic {
|
for i := 2; i < len(buf); i += lengthPerCharacteristic {
|
||||||
c := rawCharacteristic{
|
c := rawCharacteristic{}
|
||||||
startHandle: binary.LittleEndian.Uint16(buf[i:]),
|
c.Write(buf[i : i+lengthPerCharacteristic])
|
||||||
properties: buf[i+2],
|
|
||||||
valueHandle: binary.LittleEndian.Uint16(buf[i+3:]),
|
|
||||||
}
|
|
||||||
switch lengthPerCharacteristic - 5 {
|
|
||||||
case 2:
|
|
||||||
c.uuid = New16BitUUID(binary.LittleEndian.Uint16(buf[i+5:]))
|
|
||||||
case 16:
|
|
||||||
copy(uuid[:], buf[i+5:])
|
|
||||||
slices.Reverse(uuid[:])
|
|
||||||
c.uuid = NewUUID(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _debug {
|
if _debug {
|
||||||
println("att.handleData: characteristic", c.startHandle, c.properties, c.valueHandle, c.uuid.String())
|
println("att.handleData: characteristic", c.startHandle, c.properties, c.valueHandle, c.uuid.String())
|
||||||
|
@ -389,32 +590,11 @@ func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
println("att.handleData: attOpReadByGroupReq")
|
println("att.handleData: attOpReadByGroupReq")
|
||||||
}
|
}
|
||||||
|
|
||||||
// return generic services
|
startHandle := binary.LittleEndian.Uint16(buf[1:])
|
||||||
var response [14]byte
|
endHandle := binary.LittleEndian.Uint16(buf[3:])
|
||||||
response[0] = attOpReadByGroupResponse
|
uuid := shortUUID(binary.LittleEndian.Uint16(buf[5:]))
|
||||||
response[1] = 0x06 // length per service
|
|
||||||
|
|
||||||
genericAccessService := rawService{
|
return a.handleReadByGroupReq(handle, startHandle, endHandle, uuid)
|
||||||
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:
|
case attOpReadByGroupResponse:
|
||||||
if _debug {
|
if _debug {
|
||||||
|
@ -423,21 +603,10 @@ func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
a.responded = true
|
a.responded = true
|
||||||
|
|
||||||
lengthPerService := int(buf[1])
|
lengthPerService := int(buf[1])
|
||||||
var uuid [16]byte
|
|
||||||
|
|
||||||
for i := 2; i < len(buf); i += lengthPerService {
|
for i := 2; i < len(buf); i += lengthPerService {
|
||||||
service := rawService{
|
service := rawService{}
|
||||||
startHandle: binary.LittleEndian.Uint16(buf[i:]),
|
service.Write(buf[i : i+lengthPerService])
|
||||||
endHandle: binary.LittleEndian.Uint16(buf[i+2:]),
|
|
||||||
}
|
|
||||||
switch lengthPerService - 4 {
|
|
||||||
case 2:
|
|
||||||
service.uuid = New16BitUUID(binary.LittleEndian.Uint16(buf[i+4:]))
|
|
||||||
case 16:
|
|
||||||
copy(uuid[:], buf[i+4:])
|
|
||||||
slices.Reverse(uuid[:])
|
|
||||||
service.uuid = NewUUID(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _debug {
|
if _debug {
|
||||||
println("att.handleData: service", service.startHandle, service.endHandle, service.uuid.String())
|
println("att.handleData: service", service.startHandle, service.endHandle, service.uuid.String())
|
||||||
|
@ -453,6 +622,9 @@ func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
println("att.handleData: attOpReadReq")
|
println("att.handleData: attOpReadReq")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attrHandle := binary.LittleEndian.Uint16(buf[1:])
|
||||||
|
return a.handleReadReq(handle, attrHandle)
|
||||||
|
|
||||||
case attOpReadBlobReq:
|
case attOpReadBlobReq:
|
||||||
if _debug {
|
if _debug {
|
||||||
println("att.handleData: attOpReadBlobReq")
|
println("att.handleData: attOpReadBlobReq")
|
||||||
|
@ -470,6 +642,9 @@ func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
println("att.handleData: attOpWriteReq")
|
println("att.handleData: attOpWriteReq")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attrHandle := binary.LittleEndian.Uint16(buf[1:])
|
||||||
|
return a.handleWriteReq(handle, attrHandle, buf[3:])
|
||||||
|
|
||||||
case attOpWriteCmd:
|
case attOpWriteCmd:
|
||||||
if _debug {
|
if _debug {
|
||||||
println("att.handleData: attOpWriteCmd")
|
println("att.handleData: attOpWriteCmd")
|
||||||
|
@ -538,6 +713,295 @@ func (a *att) handleData(handle uint16, buf []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *att) handleReadByGroupReq(handle, start, end uint16, uuid shortUUID) error {
|
||||||
|
var response [64]byte
|
||||||
|
response[0] = attOpReadByGroupResponse
|
||||||
|
response[1] = 0x0 // length per service
|
||||||
|
pos := 2
|
||||||
|
|
||||||
|
switch uuid {
|
||||||
|
case shortUUID(gattServiceUUID):
|
||||||
|
for _, s := range a.localServices {
|
||||||
|
if s.startHandle >= start && s.endHandle <= end {
|
||||||
|
if _debug {
|
||||||
|
println("attOpReadByGroupReq: replying with service", s.startHandle, s.endHandle, s.uuid.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
length := 20
|
||||||
|
if s.uuid.Is16Bit() {
|
||||||
|
length = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
if response[1] == 0 {
|
||||||
|
response[1] = byte(length)
|
||||||
|
} else if response[1] != byte(length) {
|
||||||
|
// change of UUID size
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Read(response[pos : pos+length])
|
||||||
|
pos += length
|
||||||
|
|
||||||
|
if uint16(pos+length) > a.mtu {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case pos > 2:
|
||||||
|
if err := a.hci.sendAclPkt(handle, attCID, response[:pos]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := a.sendError(handle, attOpReadByGroupReq, start, attErrorAttrNotFound); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
if _debug {
|
||||||
|
println("handleReadByGroupReq: unknown uuid", New16BitUUID(uint16(uuid)).String())
|
||||||
|
}
|
||||||
|
if err := a.sendError(handle, attOpReadByGroupReq, start, attErrorAttrNotFound); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) handleReadByTypeReq(handle, start, end uint16, uuid shortUUID) error {
|
||||||
|
var response [64]byte
|
||||||
|
response[0] = attOpReadByTypeResponse
|
||||||
|
pos := 0
|
||||||
|
|
||||||
|
switch uuid {
|
||||||
|
case shortUUID(gattCharacteristicUUID):
|
||||||
|
pos = 2
|
||||||
|
response[1] = 0
|
||||||
|
|
||||||
|
for _, c := range a.characteristics {
|
||||||
|
if _debug {
|
||||||
|
println("handleReadByTypeReq: looking at characteristic", c.startHandle, c.uuid.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.startHandle >= start && c.valueHandle <= end {
|
||||||
|
if _debug {
|
||||||
|
println("handleReadByTypeReq: replying with characteristic", c.startHandle, c.uuid.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
length := 21
|
||||||
|
if c.uuid.Is16Bit() {
|
||||||
|
length = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
if response[1] == 0 {
|
||||||
|
response[1] = byte(length)
|
||||||
|
} else if response[1] != byte(length) {
|
||||||
|
// change of UUID size
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Read(response[pos : pos+length])
|
||||||
|
pos += length
|
||||||
|
|
||||||
|
if uint16(pos+length) > a.mtu {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case pos > 2:
|
||||||
|
if err := a.hci.sendAclPkt(handle, attCID, response[:pos]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := a.sendError(handle, attOpReadByTypeReq, start, attErrorAttrNotFound); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
if _debug {
|
||||||
|
println("handleReadByTypeReq: unknown uuid", New16BitUUID(uint16(uuid)).String())
|
||||||
|
}
|
||||||
|
if err := a.sendError(handle, attOpReadByTypeReq, start, attErrorAttrNotFound); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) handleFindInfoReq(handle, start, end uint16) error {
|
||||||
|
var response [64]byte
|
||||||
|
response[0] = attOpFindInfoResponse
|
||||||
|
pos := 0
|
||||||
|
|
||||||
|
pos = 2
|
||||||
|
infoType := 0
|
||||||
|
response[1] = 0
|
||||||
|
|
||||||
|
for _, attr := range a.attributes {
|
||||||
|
if _debug {
|
||||||
|
println("handleFindInfoReq: looking at attribute")
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr.handle >= start && attr.handle <= end {
|
||||||
|
if _debug {
|
||||||
|
println("handleFindInfoReq: replying with attribute", attr.handle, attr.uuid.String(), attr.typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr.typ == attributeTypeCharacteristicValue || attr.typ == attributeTypeDescriptor {
|
||||||
|
infoType = 1
|
||||||
|
} else {
|
||||||
|
infoType = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
length := attr.length() + 2
|
||||||
|
if response[1] == 0 {
|
||||||
|
response[1] = byte(infoType)
|
||||||
|
} else if response[1] != byte(infoType) {
|
||||||
|
// change of info type
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
attr.Read(response[pos : pos+length])
|
||||||
|
pos += length
|
||||||
|
|
||||||
|
if uint16(pos+length) >= a.mtu {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case pos > 2:
|
||||||
|
if err := a.hci.sendAclPkt(handle, attCID, response[:pos]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := a.sendError(handle, attOpFindInfoReq, start, attErrorAttrNotFound); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) handleReadReq(handle, attrHandle uint16) error {
|
||||||
|
attr := a.findAttribute(attrHandle)
|
||||||
|
if attr == nil {
|
||||||
|
if _debug {
|
||||||
|
println("att.handleReadReq: attribute not found", attrHandle)
|
||||||
|
}
|
||||||
|
return a.sendError(handle, attOpReadReq, attrHandle, attErrorAttrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response [64]byte
|
||||||
|
response[0] = attOpReadResponse
|
||||||
|
pos := 1
|
||||||
|
|
||||||
|
switch attr.typ {
|
||||||
|
case attributeTypeCharacteristicValue:
|
||||||
|
if _debug {
|
||||||
|
println("att.handleReadReq: reading characteristic value", attrHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := a.findCharacteristic(attr.parent)
|
||||||
|
if c != nil && c.chr != nil {
|
||||||
|
value, err := c.chr.readValue()
|
||||||
|
if err != nil {
|
||||||
|
return a.sendError(handle, attOpReadReq, attrHandle, attErrorReadNotPermitted)
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(response[pos:], value)
|
||||||
|
pos += len(value)
|
||||||
|
|
||||||
|
if err := a.hci.sendAclPkt(handle, attCID, response[:pos]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case attributeTypeDescriptor:
|
||||||
|
if _debug {
|
||||||
|
println("att.handleReadReq: reading descriptor", attrHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := a.findCharacteristic(attr.parent)
|
||||||
|
if c != nil && c.chr != nil {
|
||||||
|
cccd, err := c.chr.readCCCD()
|
||||||
|
if err != nil {
|
||||||
|
return a.sendError(handle, attOpReadReq, attrHandle, attErrorReadNotPermitted)
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint16(response[pos:], cccd)
|
||||||
|
pos += 2
|
||||||
|
|
||||||
|
if err := a.hci.sendAclPkt(handle, attCID, response[:pos]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.sendError(handle, attOpReadReq, attrHandle, attErrorReadNotPermitted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) handleWriteReq(handle, attrHandle uint16, data []byte) error {
|
||||||
|
attr := a.findAttribute(attrHandle)
|
||||||
|
if attr == nil {
|
||||||
|
if _debug {
|
||||||
|
println("att.handleWriteReq: attribute not found", attrHandle)
|
||||||
|
}
|
||||||
|
return a.sendError(handle, attOpWriteReq, attrHandle, attErrorAttrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch attr.typ {
|
||||||
|
case attributeTypeCharacteristicValue:
|
||||||
|
if _debug {
|
||||||
|
println("att.handleWriteReq: writing characteristic value", attrHandle, hex.EncodeToString(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
c := a.findCharacteristic(attr.parent)
|
||||||
|
if c != nil && c.chr != nil {
|
||||||
|
if _, err := c.chr.Write(data); err != nil {
|
||||||
|
return a.sendError(handle, attOpWriteReq, attrHandle, attErrorWriteNotPermitted)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.hci.sendAclPkt(handle, attCID, []byte{attOpWriteResponse}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case attributeTypeDescriptor:
|
||||||
|
if _debug {
|
||||||
|
println("att.handleWriteReq: writing descriptor", attrHandle, hex.EncodeToString(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
c := a.findCharacteristic(attr.parent)
|
||||||
|
if c != nil && c.chr != nil {
|
||||||
|
if err := c.chr.writeCCCD(binary.LittleEndian.Uint16(data)); err != nil {
|
||||||
|
return a.sendError(handle, attOpWriteReq, attrHandle, attErrorWriteNotPermitted)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.hci.sendAclPkt(handle, attCID, []byte{attOpWriteResponse}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.sendError(handle, attOpWriteReq, attrHandle, attErrorWriteNotPermitted)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *att) clearResponse() {
|
func (a *att) clearResponse() {
|
||||||
a.responded = false
|
a.responded = false
|
||||||
a.errored = false
|
a.errored = false
|
||||||
|
@ -581,3 +1045,71 @@ func (a *att) poll() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *att) addConnection(handle uint16) {
|
||||||
|
a.connections = append(a.connections, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) removeConnection(handle uint16) {
|
||||||
|
for i := range a.connections {
|
||||||
|
if a.connections[i] == handle {
|
||||||
|
a.connections = append(a.connections[:i], a.connections[i+1:]...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) addLocalAttribute(typ attributeType, parent uint16, uuid UUID, permissions CharacteristicPermissions, value []byte) uint16 {
|
||||||
|
handle := a.lastHandle
|
||||||
|
a.attributes = append(a.attributes,
|
||||||
|
rawAttribute{
|
||||||
|
typ: typ,
|
||||||
|
parent: parent,
|
||||||
|
handle: handle,
|
||||||
|
uuid: uuid,
|
||||||
|
permissions: permissions,
|
||||||
|
value: append([]byte{}, value...),
|
||||||
|
})
|
||||||
|
a.lastHandle++
|
||||||
|
|
||||||
|
return handle
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) addLocalService(start, end uint16, uuid UUID) {
|
||||||
|
a.localServices = append(a.localServices, rawService{
|
||||||
|
startHandle: start,
|
||||||
|
endHandle: end,
|
||||||
|
uuid: uuid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) addLocalCharacteristic(startHandle uint16, properties CharacteristicPermissions, valueHandle uint16, uuid UUID, chr *Characteristic) {
|
||||||
|
a.characteristics = append(a.characteristics,
|
||||||
|
rawCharacteristic{
|
||||||
|
startHandle: startHandle,
|
||||||
|
properties: uint8(properties),
|
||||||
|
valueHandle: valueHandle,
|
||||||
|
uuid: uuid,
|
||||||
|
chr: chr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) findAttribute(hdl uint16) *rawAttribute {
|
||||||
|
for i := range a.attributes {
|
||||||
|
if a.attributes[i].handle == hdl {
|
||||||
|
return &a.attributes[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *att) findCharacteristic(hdl uint16) *rawCharacteristic {
|
||||||
|
for i := range a.characteristics {
|
||||||
|
if a.characteristics[i].startHandle == hdl {
|
||||||
|
return &a.characteristics[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
136
gap_ninafw.go
136
gap_ninafw.go
|
@ -3,7 +3,9 @@
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -261,3 +263,137 @@ func (d Device) addNotificationRegistration(handle uint16, callback func([]byte)
|
||||||
func (d Device) startNotifications() {
|
func (d Device) startNotifications() {
|
||||||
d.adapter.startNotifications()
|
d.adapter.startNotifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultAdvertisement Advertisement
|
||||||
|
|
||||||
|
// Advertisement encapsulates a single advertisement instance.
|
||||||
|
type Advertisement struct {
|
||||||
|
adapter *Adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultAdvertisement returns the default advertisement instance but does not
|
||||||
|
// configure it.
|
||||||
|
func (a *Adapter) DefaultAdvertisement() *Advertisement {
|
||||||
|
if defaultAdvertisement.adapter == nil {
|
||||||
|
defaultAdvertisement.adapter = a
|
||||||
|
|
||||||
|
a.AddService(
|
||||||
|
&Service{
|
||||||
|
UUID: ServiceUUIDGenericAccess,
|
||||||
|
Characteristics: []CharacteristicConfig{
|
||||||
|
{
|
||||||
|
UUID: CharacteristicUUIDDeviceName,
|
||||||
|
Flags: CharacteristicReadPermission,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UUID: CharacteristicUUIDAppearance,
|
||||||
|
Flags: CharacteristicReadPermission,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
a.AddService(
|
||||||
|
&Service{
|
||||||
|
UUID: ServiceUUIDGenericAttribute,
|
||||||
|
Characteristics: []CharacteristicConfig{
|
||||||
|
{
|
||||||
|
UUID: CharacteristicUUIDServiceChanged,
|
||||||
|
Flags: CharacteristicIndicatePermission,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &defaultAdvertisement
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure this advertisement.
|
||||||
|
func (a *Advertisement) Configure(options AdvertisementOptions) error {
|
||||||
|
// uint8_t type = (_connectable) ? 0x00 : (_localName ? 0x02 : 0x03);
|
||||||
|
typ := uint8(0x00)
|
||||||
|
|
||||||
|
if err := a.adapter.hci.leSetAdvertisingParameters(uint16(options.Interval), uint16(options.Interval),
|
||||||
|
typ, 0x00, 0x00, [6]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 0x07, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var advertisingData [31]byte
|
||||||
|
advertisingDataLen := uint8(0)
|
||||||
|
|
||||||
|
advertisingData[0] = 0x02
|
||||||
|
advertisingData[1] = 0x01
|
||||||
|
advertisingData[2] = 0x06
|
||||||
|
advertisingDataLen += 3
|
||||||
|
|
||||||
|
// TODO: handle multiple service UUIDs
|
||||||
|
if len(options.ServiceUUIDs) > 0 {
|
||||||
|
uuid := options.ServiceUUIDs[0]
|
||||||
|
var sz uint8
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case uuid.Is16Bit():
|
||||||
|
sz = 2
|
||||||
|
binary.LittleEndian.PutUint16(advertisingData[5:], uuid.Get16Bit())
|
||||||
|
case uuid.Is32Bit():
|
||||||
|
sz = 6
|
||||||
|
data := uuid.Bytes()
|
||||||
|
slices.Reverse(data[:])
|
||||||
|
copy(advertisingData[5:], data[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
advertisingData[3] = sz + 1
|
||||||
|
advertisingData[4] = sz
|
||||||
|
advertisingDataLen += sz + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle manufacturer data
|
||||||
|
|
||||||
|
if err := a.adapter.hci.leSetAdvertisingData(advertisingData[:advertisingDataLen]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var scanResponseData [31]byte
|
||||||
|
scanResponseDataLen := uint8(0)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(options.LocalName) > 29:
|
||||||
|
scanResponseData[1] = 0x08
|
||||||
|
scanResponseData[0] = 1 + 29
|
||||||
|
copy(scanResponseData[2:], options.LocalName[:29])
|
||||||
|
scanResponseDataLen = 31
|
||||||
|
case len(options.LocalName) > 0:
|
||||||
|
scanResponseData[1] = 0x09
|
||||||
|
scanResponseData[0] = uint8(1 + len(options.LocalName))
|
||||||
|
copy(scanResponseData[2:], options.LocalName)
|
||||||
|
scanResponseDataLen = uint8(2 + len(options.LocalName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.adapter.hci.leSetScanResponseData(scanResponseData[:scanResponseDataLen])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start advertisement. May only be called after it has been configured.
|
||||||
|
func (a *Advertisement) Start() error {
|
||||||
|
if err := a.adapter.hci.leSetAdvertiseEnable(true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// go routine to poll for HCI events while advertising
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if err := a.adapter.att.poll(); err != nil {
|
||||||
|
// TODO: handle error
|
||||||
|
if _debug {
|
||||||
|
println("error polling while advertising:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop advertisement. May only be called after it has been started.
|
||||||
|
func (a *Advertisement) Stop() error {
|
||||||
|
return a.adapter.hci.leSetAdvertiseEnable(false)
|
||||||
|
}
|
||||||
|
|
5
gatts.go
5
gatts.go
|
@ -61,3 +61,8 @@ func (p CharacteristicPermissions) WriteWithoutResponse() bool {
|
||||||
func (p CharacteristicPermissions) Notify() bool {
|
func (p CharacteristicPermissions) Notify() bool {
|
||||||
return p&CharacteristicNotifyPermission != 0
|
return p&CharacteristicNotifyPermission != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Indicate returns whether indications are permitted.
|
||||||
|
func (p CharacteristicPermissions) Indicate() bool {
|
||||||
|
return p&CharacteristicIndicatePermission != 0
|
||||||
|
}
|
||||||
|
|
102
gatts_ninafw.go
102
gatts_ninafw.go
|
@ -3,4 +3,106 @@
|
||||||
package bluetooth
|
package bluetooth
|
||||||
|
|
||||||
type Characteristic struct {
|
type Characteristic struct {
|
||||||
|
adapter *Adapter
|
||||||
|
handle uint16
|
||||||
|
permissions CharacteristicPermissions
|
||||||
|
value []byte
|
||||||
|
cccd uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddService creates a new service with the characteristics listed in the
|
||||||
|
// Service struct.
|
||||||
|
func (a *Adapter) AddService(service *Service) error {
|
||||||
|
uuid := service.UUID.Bytes()
|
||||||
|
serviceHandle := a.att.addLocalAttribute(attributeTypeService, 0, shortUUID(gattServiceUUID).UUID(), 0, uuid[:])
|
||||||
|
valueHandle := serviceHandle
|
||||||
|
endHandle := serviceHandle
|
||||||
|
|
||||||
|
for i := range service.Characteristics {
|
||||||
|
data := service.Characteristics[i].UUID.Bytes()
|
||||||
|
cuuid := append([]byte{}, data[:]...)
|
||||||
|
|
||||||
|
// add characteristic declaration
|
||||||
|
charHandle := a.att.addLocalAttribute(attributeTypeCharacteristic, serviceHandle, shortUUID(gattCharacteristicUUID).UUID(), CharacteristicReadPermission, cuuid[:])
|
||||||
|
|
||||||
|
// add characteristic value
|
||||||
|
vf := CharacteristicPermissions(0)
|
||||||
|
if service.Characteristics[i].Flags.Read() {
|
||||||
|
vf |= CharacteristicReadPermission
|
||||||
|
}
|
||||||
|
if service.Characteristics[i].Flags.Write() {
|
||||||
|
vf |= CharacteristicWritePermission
|
||||||
|
}
|
||||||
|
valueHandle = a.att.addLocalAttribute(attributeTypeCharacteristicValue, charHandle, service.Characteristics[i].UUID, vf, service.Characteristics[i].Value)
|
||||||
|
endHandle = valueHandle
|
||||||
|
|
||||||
|
// add characteristic descriptor
|
||||||
|
if service.Characteristics[i].Flags.Notify() ||
|
||||||
|
service.Characteristics[i].Flags.Indicate() {
|
||||||
|
endHandle = a.att.addLocalAttribute(attributeTypeDescriptor, charHandle, shortUUID(gattClientCharacteristicConfigUUID).UUID(), CharacteristicReadPermission|CharacteristicWritePermission, []byte{0, 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.Characteristics[i].Handle != nil {
|
||||||
|
service.Characteristics[i].Handle.adapter = a
|
||||||
|
service.Characteristics[i].Handle.handle = valueHandle
|
||||||
|
service.Characteristics[i].Handle.permissions = service.Characteristics[i].Flags
|
||||||
|
service.Characteristics[i].Handle.value = service.Characteristics[i].Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if _debug {
|
||||||
|
println("added characteristic", charHandle, valueHandle, service.Characteristics[i].UUID.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
a.att.addLocalCharacteristic(charHandle, service.Characteristics[i].Flags, valueHandle, service.Characteristics[i].UUID, service.Characteristics[i].Handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _debug {
|
||||||
|
println("added service", serviceHandle, endHandle, service.UUID.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
a.att.addLocalService(serviceHandle, endHandle, service.UUID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write replaces the characteristic value with a new value.
|
||||||
|
func (c *Characteristic) Write(p []byte) (n int, err error) {
|
||||||
|
if !c.permissions.Notify() {
|
||||||
|
return 0, errNoNotify
|
||||||
|
}
|
||||||
|
|
||||||
|
c.value = append([]byte{}, p...)
|
||||||
|
|
||||||
|
if c.cccd&0x01 != 0 {
|
||||||
|
// send notification
|
||||||
|
c.adapter.att.sendNotification(c.handle, c.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(c.value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Characteristic) readCCCD() (uint16, error) {
|
||||||
|
if !c.permissions.Notify() {
|
||||||
|
return 0, errNoNotify
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.cccd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Characteristic) writeCCCD(val uint16) error {
|
||||||
|
if !c.permissions.Notify() {
|
||||||
|
return errNoNotify
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cccd = val
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Characteristic) readValue() ([]byte, error) {
|
||||||
|
if !c.permissions.Read() {
|
||||||
|
return nil, errNoRead
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,7 +288,41 @@ func (h *hci) leSetAdvertiseEnable(enabled bool) error {
|
||||||
data[0] = 1
|
data[0] = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertiseEnable, data[:])
|
return h.sendWithoutResponse(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertiseEnable, data[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hci) leSetAdvertisingParameters(minInterval, maxInterval uint16,
|
||||||
|
advType, ownBdaddrType uint8,
|
||||||
|
directBdaddrType uint8, directBdaddr [6]byte,
|
||||||
|
chanMap, filter uint8) error {
|
||||||
|
|
||||||
|
var b [15]byte
|
||||||
|
binary.LittleEndian.PutUint16(b[0:], minInterval)
|
||||||
|
binary.LittleEndian.PutUint16(b[2:], maxInterval)
|
||||||
|
b[4] = advType
|
||||||
|
b[5] = ownBdaddrType
|
||||||
|
b[6] = directBdaddrType
|
||||||
|
copy(b[7:], directBdaddr[:])
|
||||||
|
b[13] = chanMap
|
||||||
|
b[14] = filter
|
||||||
|
|
||||||
|
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertisingParameters, b[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hci) leSetAdvertisingData(data []byte) error {
|
||||||
|
var b [32]byte
|
||||||
|
b[0] = byte(len(data))
|
||||||
|
copy(b[1:], data)
|
||||||
|
|
||||||
|
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertisingData, b[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hci) leSetScanResponseData(data []byte) error {
|
||||||
|
var b [32]byte
|
||||||
|
b[0] = byte(len(data))
|
||||||
|
copy(b[1:], data)
|
||||||
|
|
||||||
|
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetScanResponseData, b[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hci) leCreateConn(interval, window uint16,
|
func (h *hci) leCreateConn(interval, window uint16,
|
||||||
|
@ -360,6 +394,26 @@ func (h *hci) sendCommandWithParams(opcode uint16, params []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *hci) sendWithoutResponse(opcode uint16, params []byte) error {
|
||||||
|
if _debug {
|
||||||
|
println("hci send without response command", opcode, hex.EncodeToString(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
h.buf[0] = hciCommandPkt
|
||||||
|
binary.LittleEndian.PutUint16(h.buf[1:], opcode)
|
||||||
|
h.buf[3] = byte(len(params))
|
||||||
|
copy(h.buf[4:], params)
|
||||||
|
|
||||||
|
if _, err := h.write(h.buf[:4+len(params)]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.cmdCompleteOpcode = 0xffff
|
||||||
|
h.cmdCompleteStatus = 0xff
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *hci) sendAclPkt(handle uint16, cid uint8, data []byte) error {
|
func (h *hci) sendAclPkt(handle uint16, cid uint8, data []byte) error {
|
||||||
h.buf[0] = hciACLDataPkt
|
h.buf[0] = hciACLDataPkt
|
||||||
binary.LittleEndian.PutUint16(h.buf[1:], handle)
|
binary.LittleEndian.PutUint16(h.buf[1:], handle)
|
||||||
|
@ -450,12 +504,9 @@ func (h *hci) handleEventData(buf []byte) error {
|
||||||
if _debug {
|
if _debug {
|
||||||
println("evtDisconnComplete")
|
println("evtDisconnComplete")
|
||||||
}
|
}
|
||||||
// TODO: something with this data?
|
|
||||||
// status := buf[2]
|
handle := binary.LittleEndian.Uint16(buf[3:])
|
||||||
// handle := buf[3] | (buf[4] << 8)
|
h.att.removeConnection(handle)
|
||||||
// reason := buf[5]
|
|
||||||
// ATT.removeConnection(disconnComplete->handle, disconnComplete->reason);
|
|
||||||
// L2CAPSignaling.removeConnection(disconnComplete->handle, disconnComplete->reason);
|
|
||||||
|
|
||||||
return h.leSetAdvertiseEnable(true)
|
return h.leSetAdvertiseEnable(true)
|
||||||
|
|
||||||
|
@ -512,7 +563,9 @@ func (h *hci) handleEventData(buf []byte) error {
|
||||||
h.connectData.peerBdaddrType = buf[7]
|
h.connectData.peerBdaddrType = buf[7]
|
||||||
copy(h.connectData.peerBdaddr[0:], buf[8:])
|
copy(h.connectData.peerBdaddr[0:], buf[8:])
|
||||||
|
|
||||||
return nil
|
h.att.addConnection(h.connectData.handle)
|
||||||
|
|
||||||
|
return h.leSetAdvertiseEnable(false)
|
||||||
|
|
||||||
case leMetaEventAdvertisingReport:
|
case leMetaEventAdvertisingReport:
|
||||||
h.advData.reported = true
|
h.advData.reported = true
|
||||||
|
@ -567,7 +620,7 @@ func (h *hci) handleEventData(buf []byte) error {
|
||||||
binary.LittleEndian.PutUint16(b[10:], 0x000F)
|
binary.LittleEndian.PutUint16(b[10:], 0x000F)
|
||||||
binary.LittleEndian.PutUint16(b[12:], 0x0FFF)
|
binary.LittleEndian.PutUint16(b[12:], 0x0FFF)
|
||||||
|
|
||||||
return h.sendCommandWithParams(ogfLECtrl<<10|ocfLEParamRequestReply, b[:])
|
return h.sendWithoutResponse(ogfLECtrl<<10|ocfLEParamRequestReply, b[:])
|
||||||
|
|
||||||
case leMetaEventConnectionUpdateComplete:
|
case leMetaEventConnectionUpdateComplete:
|
||||||
if _debug {
|
if _debug {
|
||||||
|
@ -584,6 +637,11 @@ func (h *hci) handleEventData(buf []byte) error {
|
||||||
println("leMetaEventGenerateDHKeyComplete")
|
println("leMetaEventGenerateDHKeyComplete")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case leMetaEventDataLengthChange:
|
||||||
|
if _debug {
|
||||||
|
println("leMetaEventDataLengthChange")
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if _debug {
|
if _debug {
|
||||||
println("unknown metaevent", buf[2], buf[3], buf[4], buf[5])
|
println("unknown metaevent", buf[2], buf[3], buf[4], buf[5])
|
||||||
|
|
Loading…
Reference in a new issue