blob: e138d9e09735df69a5ce65e066b398363e5021e6 [file] [log] [blame]
package gatt
import (
"encoding/binary"
"fmt"
"log"
"sync"
"github.com/go-ble/ble"
"github.com/go-ble/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
}