405 lines
9.9 KiB
Go
405 lines
9.9 KiB
Go
|
package gatt
|
|||
|
|
|||
|
import (
|
|||
|
"encoding/binary"
|
|||
|
"fmt"
|
|||
|
"log"
|
|||
|
"sync"
|
|||
|
|
|||
|
"github.com/JuulLabs-OSS/ble"
|
|||
|
"github.com/JuulLabs-OSS/ble/linux/att"
|
|||
|
)
|
|||
|
|
|||
|
const (
|
|||
|
cccNotify = 0x0001
|
|||
|
cccIndicate = 0x0002
|
|||
|
)
|
|||
|
|
|||
|
// NewClient returns a GATT Client.
|
|||
|
func NewClient(conn ble.Conn) (*Client, error) {
|
|||
|
p := &Client{
|
|||
|
subs: make(map[uint16]*sub),
|
|||
|
conn: conn,
|
|||
|
}
|
|||
|
p.ac = att.NewClient(conn, p)
|
|||
|
go p.ac.Loop()
|
|||
|
return p, nil
|
|||
|
}
|
|||
|
|
|||
|
// A Client is a GATT Client.
|
|||
|
type Client struct {
|
|||
|
sync.RWMutex
|
|||
|
|
|||
|
profile *ble.Profile
|
|||
|
name string
|
|||
|
subs map[uint16]*sub
|
|||
|
|
|||
|
ac *att.Client
|
|||
|
conn ble.Conn
|
|||
|
}
|
|||
|
|
|||
|
// Addr returns the address of the client.
|
|||
|
func (p *Client) Addr() ble.Addr {
|
|||
|
p.RLock()
|
|||
|
defer p.RUnlock()
|
|||
|
return p.conn.RemoteAddr()
|
|||
|
}
|
|||
|
|
|||
|
// Name returns the name of the client.
|
|||
|
func (p *Client) Name() string {
|
|||
|
p.RLock()
|
|||
|
defer p.RUnlock()
|
|||
|
return p.name
|
|||
|
}
|
|||
|
|
|||
|
// Profile returns the discovered profile.
|
|||
|
func (p *Client) Profile() *ble.Profile {
|
|||
|
p.RLock()
|
|||
|
defer p.RUnlock()
|
|||
|
return p.profile
|
|||
|
}
|
|||
|
|
|||
|
// DiscoverProfile discovers the whole hierarchy of a server.
|
|||
|
func (p *Client) DiscoverProfile(force bool) (*ble.Profile, error) {
|
|||
|
if p.profile != nil && !force {
|
|||
|
return p.profile, nil
|
|||
|
}
|
|||
|
ss, err := p.DiscoverServices(nil)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("can't discover services: %s", err)
|
|||
|
}
|
|||
|
for _, s := range ss {
|
|||
|
cs, err := p.DiscoverCharacteristics(nil, s)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("can't discover characteristics: %s", err)
|
|||
|
}
|
|||
|
for _, c := range cs {
|
|||
|
_, err := p.DiscoverDescriptors(nil, c)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("can't discover descriptors: %s", err)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
p.profile = &ble.Profile{Services: ss}
|
|||
|
return p.profile, nil
|
|||
|
}
|
|||
|
|
|||
|
// DiscoverServices finds all the primary services on a server. [Vol 3, Part G, 4.4.1]
|
|||
|
// If filter is specified, only filtered services are returned.
|
|||
|
func (p *Client) DiscoverServices(filter []ble.UUID) ([]*ble.Service, error) {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
if p.profile == nil {
|
|||
|
p.profile = &ble.Profile{}
|
|||
|
}
|
|||
|
start := uint16(0x0001)
|
|||
|
for {
|
|||
|
length, b, err := p.ac.ReadByGroupType(start, 0xFFFF, ble.PrimaryServiceUUID)
|
|||
|
if err == ble.ErrAttrNotFound {
|
|||
|
return p.profile.Services, nil
|
|||
|
}
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
for len(b) != 0 {
|
|||
|
h := binary.LittleEndian.Uint16(b[:2])
|
|||
|
endh := binary.LittleEndian.Uint16(b[2:4])
|
|||
|
u := ble.UUID(b[4:length])
|
|||
|
if filter == nil || ble.Contains(filter, u) {
|
|||
|
s := &ble.Service{
|
|||
|
UUID: u,
|
|||
|
Handle: h,
|
|||
|
EndHandle: endh,
|
|||
|
}
|
|||
|
p.profile.Services = append(p.profile.Services, s)
|
|||
|
}
|
|||
|
if endh == 0xFFFF {
|
|||
|
return p.profile.Services, nil
|
|||
|
}
|
|||
|
start = endh + 1
|
|||
|
b = b[length:]
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// DiscoverIncludedServices finds the included services of a service. [Vol 3, Part G, 4.5.1]
|
|||
|
// If filter is specified, only filtered services are returned.
|
|||
|
func (p *Client) DiscoverIncludedServices(ss []ble.UUID, s *ble.Service) ([]*ble.Service, error) {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
return nil, nil
|
|||
|
}
|
|||
|
|
|||
|
// DiscoverCharacteristics finds all the characteristics within a service. [Vol 3, Part G, 4.6.1]
|
|||
|
// If filter is specified, only filtered characteristics are returned.
|
|||
|
func (p *Client) DiscoverCharacteristics(filter []ble.UUID, s *ble.Service) ([]*ble.Characteristic, error) {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
start := s.Handle
|
|||
|
var lastChar *ble.Characteristic
|
|||
|
for start <= s.EndHandle {
|
|||
|
length, b, err := p.ac.ReadByType(start, s.EndHandle, ble.CharacteristicUUID)
|
|||
|
if err == ble.ErrAttrNotFound {
|
|||
|
break
|
|||
|
} else if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
for len(b) != 0 {
|
|||
|
h := binary.LittleEndian.Uint16(b[:2])
|
|||
|
p := ble.Property(b[2])
|
|||
|
vh := binary.LittleEndian.Uint16(b[3:5])
|
|||
|
u := ble.UUID(b[5:length])
|
|||
|
c := &ble.Characteristic{
|
|||
|
UUID: u,
|
|||
|
Property: p,
|
|||
|
Handle: h,
|
|||
|
ValueHandle: vh,
|
|||
|
EndHandle: s.EndHandle,
|
|||
|
}
|
|||
|
if filter == nil || ble.Contains(filter, u) {
|
|||
|
s.Characteristics = append(s.Characteristics, c)
|
|||
|
}
|
|||
|
if lastChar != nil {
|
|||
|
lastChar.EndHandle = c.Handle - 1
|
|||
|
}
|
|||
|
lastChar = c
|
|||
|
start = vh + 1
|
|||
|
b = b[length:]
|
|||
|
}
|
|||
|
}
|
|||
|
return s.Characteristics, nil
|
|||
|
}
|
|||
|
|
|||
|
// DiscoverDescriptors finds all the descriptors within a characteristic. [Vol 3, Part G, 4.7.1]
|
|||
|
// If filter is specified, only filtered descriptors are returned.
|
|||
|
func (p *Client) DiscoverDescriptors(filter []ble.UUID, c *ble.Characteristic) ([]*ble.Descriptor, error) {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
start := c.ValueHandle + 1
|
|||
|
for start <= c.EndHandle {
|
|||
|
fmt, b, err := p.ac.FindInformation(start, c.EndHandle)
|
|||
|
if err == ble.ErrAttrNotFound {
|
|||
|
break
|
|||
|
} else if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
length := 2 + 2
|
|||
|
if fmt == 0x02 {
|
|||
|
length = 2 + 16
|
|||
|
}
|
|||
|
for len(b) != 0 {
|
|||
|
h := binary.LittleEndian.Uint16(b[:2])
|
|||
|
u := ble.UUID(b[2:length])
|
|||
|
d := &ble.Descriptor{UUID: u, Handle: h}
|
|||
|
if filter == nil || ble.Contains(filter, u) {
|
|||
|
c.Descriptors = append(c.Descriptors, d)
|
|||
|
}
|
|||
|
if u.Equal(ble.ClientCharacteristicConfigUUID) {
|
|||
|
c.CCCD = d
|
|||
|
}
|
|||
|
start = h + 1
|
|||
|
b = b[length:]
|
|||
|
}
|
|||
|
}
|
|||
|
return c.Descriptors, nil
|
|||
|
}
|
|||
|
|
|||
|
// ReadCharacteristic reads a characteristic value from a server. [Vol 3, Part G, 4.8.1]
|
|||
|
func (p *Client) ReadCharacteristic(c *ble.Characteristic) ([]byte, error) {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
val, err := p.ac.Read(c.ValueHandle)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
|
|||
|
c.Value = val
|
|||
|
return val, nil
|
|||
|
}
|
|||
|
|
|||
|
// ReadLongCharacteristic reads a characteristic value which is longer than the MTU. [Vol 3, Part G, 4.8.3]
|
|||
|
func (p *Client) ReadLongCharacteristic(c *ble.Characteristic) ([]byte, error) {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
|
|||
|
// The maximum length of an attribute value shall be 512 octects [Vol 3, 3.2.9]
|
|||
|
buffer := make([]byte, 0, 512)
|
|||
|
|
|||
|
read, err := p.ac.Read(c.ValueHandle)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
buffer = append(buffer, read...)
|
|||
|
|
|||
|
for len(read) >= p.conn.TxMTU()-1 {
|
|||
|
if read, err = p.ac.ReadBlob(c.ValueHandle, uint16(len(buffer))); err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
buffer = append(buffer, read...)
|
|||
|
}
|
|||
|
|
|||
|
c.Value = buffer
|
|||
|
return buffer, nil
|
|||
|
}
|
|||
|
|
|||
|
// WriteCharacteristic writes a characteristic value to a server. [Vol 3, Part G, 4.9.3]
|
|||
|
func (p *Client) WriteCharacteristic(c *ble.Characteristic, v []byte, noRsp bool) error {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
if noRsp {
|
|||
|
return p.ac.WriteCommand(c.ValueHandle, v)
|
|||
|
}
|
|||
|
return p.ac.Write(c.ValueHandle, v)
|
|||
|
}
|
|||
|
|
|||
|
// ReadDescriptor reads a characteristic descriptor from a server. [Vol 3, Part G, 4.12.1]
|
|||
|
func (p *Client) ReadDescriptor(d *ble.Descriptor) ([]byte, error) {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
val, err := p.ac.Read(d.Handle)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
|
|||
|
d.Value = val
|
|||
|
return val, nil
|
|||
|
}
|
|||
|
|
|||
|
// WriteDescriptor writes a characteristic descriptor to a server. [Vol 3, Part G, 4.12.3]
|
|||
|
func (p *Client) WriteDescriptor(d *ble.Descriptor, v []byte) error {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
return p.ac.Write(d.Handle, v)
|
|||
|
}
|
|||
|
|
|||
|
// ReadRSSI retrieves the current RSSI value of remote peripheral. [Vol 2, Part E, 7.5.4]
|
|||
|
func (p *Client) ReadRSSI() int {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
// TODO:
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
// ExchangeMTU informs the server of the client’s maximum receive MTU size and
|
|||
|
// request the server to respond with its maximum receive MTU size. [Vol 3, Part F, 3.4.2.1]
|
|||
|
func (p *Client) ExchangeMTU(mtu int) (int, error) {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
return p.ac.ExchangeMTU(mtu)
|
|||
|
}
|
|||
|
|
|||
|
// Subscribe subscribes to indication (if ind is set true), or notification of a
|
|||
|
// characteristic value. [Vol 3, Part G, 4.10 & 4.11]
|
|||
|
func (p *Client) Subscribe(c *ble.Characteristic, ind bool, h ble.NotificationHandler) error {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
if c.CCCD == nil {
|
|||
|
return fmt.Errorf("CCCD not found")
|
|||
|
}
|
|||
|
if ind {
|
|||
|
return p.setHandlers(c.CCCD.Handle, c.ValueHandle, cccIndicate, h)
|
|||
|
}
|
|||
|
return p.setHandlers(c.CCCD.Handle, c.ValueHandle, cccNotify, h)
|
|||
|
}
|
|||
|
|
|||
|
// Unsubscribe unsubscribes to indication (if ind is set true), or notification
|
|||
|
// of a specified characteristic value. [Vol 3, Part G, 4.10 & 4.11]
|
|||
|
func (p *Client) Unsubscribe(c *ble.Characteristic, ind bool) error {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
if c.CCCD == nil {
|
|||
|
return fmt.Errorf("CCCD not found")
|
|||
|
}
|
|||
|
if ind {
|
|||
|
return p.setHandlers(c.CCCD.Handle, c.ValueHandle, cccIndicate, nil)
|
|||
|
}
|
|||
|
return p.setHandlers(c.CCCD.Handle, c.ValueHandle, cccNotify, nil)
|
|||
|
}
|
|||
|
|
|||
|
func (p *Client) setHandlers(cccdh, vh, flag uint16, h ble.NotificationHandler) error {
|
|||
|
s, ok := p.subs[vh]
|
|||
|
if !ok {
|
|||
|
s = &sub{cccdh, 0x0000, nil, nil}
|
|||
|
p.subs[vh] = s
|
|||
|
}
|
|||
|
switch {
|
|||
|
case h == nil && (s.ccc&flag) == 0:
|
|||
|
return nil
|
|||
|
case h != nil && (s.ccc&flag) != 0:
|
|||
|
return nil
|
|||
|
case h == nil && (s.ccc&flag) != 0:
|
|||
|
s.ccc &= ^uint16(flag)
|
|||
|
case h != nil && (s.ccc&flag) == 0:
|
|||
|
s.ccc |= flag
|
|||
|
}
|
|||
|
|
|||
|
v := make([]byte, 2)
|
|||
|
binary.LittleEndian.PutUint16(v, s.ccc)
|
|||
|
if flag == cccNotify {
|
|||
|
s.nHandler = h
|
|||
|
} else {
|
|||
|
s.iHandler = h
|
|||
|
}
|
|||
|
return p.ac.Write(s.cccdh, v)
|
|||
|
}
|
|||
|
|
|||
|
// ClearSubscriptions clears all subscriptions to notifications and indications.
|
|||
|
func (p *Client) ClearSubscriptions() error {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
zero := make([]byte, 2)
|
|||
|
for vh, s := range p.subs {
|
|||
|
if err := p.ac.Write(s.cccdh, zero); err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
delete(p.subs, vh)
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// CancelConnection disconnects the connection.
|
|||
|
func (p *Client) CancelConnection() error {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
return p.conn.Close()
|
|||
|
}
|
|||
|
|
|||
|
// Disconnected returns a receiving channel, which is closed when the client disconnects.
|
|||
|
func (p *Client) Disconnected() <-chan struct{} {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
return p.conn.Disconnected()
|
|||
|
}
|
|||
|
|
|||
|
// Conn returns the client's current connection.
|
|||
|
func (p *Client) Conn() ble.Conn {
|
|||
|
return p.conn
|
|||
|
}
|
|||
|
|
|||
|
// HandleNotification ...
|
|||
|
func (p *Client) HandleNotification(req []byte) {
|
|||
|
p.Lock()
|
|||
|
defer p.Unlock()
|
|||
|
vh := att.HandleValueIndication(req).AttributeHandle()
|
|||
|
sub, ok := p.subs[vh]
|
|||
|
if !ok {
|
|||
|
// FIXME: disconnects and propagate an error to the user.
|
|||
|
log.Printf("Got an unregistered notification")
|
|||
|
return
|
|||
|
}
|
|||
|
fn := sub.nHandler
|
|||
|
if req[0] == att.HandleValueIndicationCode {
|
|||
|
fn = sub.iHandler
|
|||
|
}
|
|||
|
if fn != nil {
|
|||
|
fn(req[3:])
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
type sub struct {
|
|||
|
cccdh uint16
|
|||
|
ccc uint16
|
|||
|
nHandler ble.NotificationHandler
|
|||
|
iHandler ble.NotificationHandler
|
|||
|
}
|