Søren Rasmussen
07a23c1845
Some checks reported errors
continuous-integration/drone/push Build encountered an error
404 lines
9.9 KiB
Go
404 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
|
||
}
|