fermentord/vendor/github.com/JuulLabs-OSS/ble/linux/gatt/client.go
Søren Rasmussen 07a23c1845
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Upgrade to go 1.20 and add vendor catalog
2023-04-22 10:37:23 +02:00

404 lines
9.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 clients 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
}