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