fermentord/vendor/github.com/JuulLabs-OSS/ble/darwin/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

460 lines
11 KiB
Go

package darwin
import (
"fmt"
"github.com/JuulLabs-OSS/ble"
"github.com/JuulLabs-OSS/cbgo"
)
// A Client is a GATT client.
type Client struct {
cbgo.PeripheralDelegateBase
profile *ble.Profile
pc profCache
name string
cm cbgo.CentralManager
id ble.UUID
conn *conn
}
// NewClient ...
func NewClient(cm cbgo.CentralManager, c ble.Conn) (*Client, error) {
as := c.RemoteAddr().String()
id, err := ble.Parse(as)
if err != nil {
return nil, fmt.Errorf("connection has invalid address: addr=%s", as)
}
cln := &Client{
conn: c.(*conn),
pc: newProfCache(),
cm: cm,
id: id,
}
cln.conn.prph.SetDelegate(cln)
return cln, nil
}
// Addr returns UUID of the remote peripheral.
func (cln *Client) Addr() ble.Addr {
return cln.conn.RemoteAddr()
}
// Name returns the name of the remote peripheral.
// This can be the advertised name, if exists, or the GAP device name, which takes priority.
func (cln *Client) Name() string {
return cln.name
}
// Profile returns the discovered profile.
func (cln *Client) Profile() *ble.Profile {
return cln.profile
}
// DiscoverProfile discovers the whole hierarchy of a server.
func (cln *Client) DiscoverProfile(force bool) (*ble.Profile, error) {
if cln.profile != nil && !force {
return cln.profile, nil
}
ss, err := cln.DiscoverServices(nil)
if err != nil {
return nil, fmt.Errorf("can't discover services: %s", err)
}
for _, s := range ss {
cs, err := cln.DiscoverCharacteristics(nil, s)
if err != nil {
return nil, fmt.Errorf("can't discover characteristics: %s", err)
}
for _, c := range cs {
_, err := cln.DiscoverDescriptors(nil, c)
if err != nil {
return nil, fmt.Errorf("can't discover descriptors: %s", err)
}
}
}
cln.profile = &ble.Profile{Services: ss}
return cln.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 (cln *Client) DiscoverServices(ss []ble.UUID) ([]*ble.Service, error) {
ch := cln.conn.evl.svcsDiscovered.Listen()
defer cln.conn.evl.svcsDiscovered.Close()
cbuuids := uuidsToCbgoUUIDs(ss)
cln.conn.prph.DiscoverServices(cbuuids)
select {
case itf := <-ch:
if itf != nil {
return nil, itf.(error)
}
case <-cln.Disconnected():
return nil, fmt.Errorf("disconnected")
}
svcs := []*ble.Service{}
for _, dsvc := range cln.conn.prph.Services() {
svc := &ble.Service{
UUID: ble.UUID(dsvc.UUID()),
}
cln.pc.addSvc(svc, dsvc)
svcs = append(svcs, svc)
}
if cln.profile == nil {
cln.profile = &ble.Profile{Services: svcs}
}
return svcs, nil
}
// 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 (cln *Client) DiscoverIncludedServices(ss []ble.UUID, s *ble.Service) ([]*ble.Service, error) {
return nil, ble.ErrNotImplemented
}
// 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 (cln *Client) DiscoverCharacteristics(cs []ble.UUID, s *ble.Service) ([]*ble.Characteristic, error) {
cbsvc, err := cln.pc.findCbSvc(s)
if err != nil {
return nil, err
}
ch := cln.conn.evl.chrsDiscovered.Listen()
defer cln.conn.evl.chrsDiscovered.Close()
cbuuids := uuidsToCbgoUUIDs(cs)
cln.conn.prph.DiscoverCharacteristics(cbuuids, cbsvc)
select {
case itf := <-ch:
if itf != nil {
return nil, itf.(error)
}
case <-cln.Disconnected():
return nil, fmt.Errorf("disconnected")
}
for _, dchr := range cbsvc.Characteristics() {
chr := &ble.Characteristic{
UUID: ble.UUID(dchr.UUID()),
Property: ble.Property(dchr.Properties()),
}
cln.pc.addChr(chr, dchr)
s.Characteristics = append(s.Characteristics, chr)
}
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 (cln *Client) DiscoverDescriptors(ds []ble.UUID, c *ble.Characteristic) ([]*ble.Descriptor, error) {
cbchr, err := cln.pc.findCbChr(c)
if err != nil {
return nil, err
}
ch := cln.conn.evl.dscsDiscovered.Listen()
defer cln.conn.evl.dscsDiscovered.Close()
cln.conn.prph.DiscoverDescriptors(cbchr)
if err != nil {
return nil, err
}
select {
case itf := <-ch:
if itf != nil {
return nil, itf.(error)
}
case <-cln.Disconnected():
return nil, fmt.Errorf("disconnected")
}
for _, ddsc := range cbchr.Descriptors() {
dsc := &ble.Descriptor{
UUID: ble.UUID(ddsc.UUID()),
}
c.Descriptors = append(c.Descriptors, dsc)
cln.pc.addDsc(dsc, ddsc)
}
return c.Descriptors, nil
}
// ReadCharacteristic reads a characteristic value from a server. [Vol 3, Part G, 4.8.1]
func (cln *Client) ReadCharacteristic(c *ble.Characteristic) ([]byte, error) {
cbchr, err := cln.pc.findCbChr(c)
if err != nil {
return nil, err
}
ch, err := cln.conn.addChrReader(c)
if err != nil {
return nil, fmt.Errorf("failed to read characteristic: %v", err)
}
defer cln.conn.delChrReader(c)
cln.conn.prph.ReadCharacteristic(cbchr)
select {
case itf := <-ch:
if itf != nil {
return nil, itf.(error)
}
case <-cln.Disconnected():
return nil, fmt.Errorf("disconnected")
}
c.Value = cbchr.Value()
return c.Value, nil
}
// ReadLongCharacteristic reads a characteristic value which is longer than the MTU. [Vol 3, Part G, 4.8.3]
func (cln *Client) ReadLongCharacteristic(c *ble.Characteristic) ([]byte, error) {
return cln.ReadCharacteristic(c)
}
// WriteCharacteristic writes a characteristic value to a server. [Vol 3, Part G, 4.9.3]
func (cln *Client) WriteCharacteristic(c *ble.Characteristic, b []byte, noRsp bool) error {
cbchr, err := cln.pc.findCbChr(c)
if err != nil {
return err
}
if noRsp {
cln.conn.prph.WriteCharacteristic(b, cbchr, false)
return nil
}
ch := cln.conn.evl.chrWritten.Listen()
defer cln.conn.evl.chrWritten.Close()
cln.conn.prph.WriteCharacteristic(b, cbchr, true)
select {
case itf := <-ch:
if itf != nil {
return itf.(error)
}
case <-cln.Disconnected():
return fmt.Errorf("disconnected")
}
return nil
}
// ReadDescriptor reads a characteristic descriptor from a server. [Vol 3, Part G, 4.12.1]
func (cln *Client) ReadDescriptor(d *ble.Descriptor) ([]byte, error) {
cbdsc, err := cln.pc.findCbDsc(d)
if err != nil {
return nil, err
}
ch := cln.conn.evl.dscRead.Listen()
defer cln.conn.evl.dscRead.Close()
cln.conn.prph.ReadDescriptor(cbdsc)
select {
case itf := <-ch:
if itf != nil {
return nil, itf.(error)
}
case <-cln.Disconnected():
return nil, fmt.Errorf("disconnected")
}
d.Value = cbdsc.Value()
return d.Value, nil
}
// WriteDescriptor writes a characteristic descriptor to a server. [Vol 3, Part G, 4.12.3]
func (cln *Client) WriteDescriptor(d *ble.Descriptor, b []byte) error {
cbdsc, err := cln.pc.findCbDsc(d)
if err != nil {
return err
}
ch := cln.conn.evl.dscWritten.Listen()
defer cln.conn.evl.dscWritten.Close()
cln.conn.prph.WriteDescriptor(b, cbdsc)
if err != nil {
return err
}
select {
case itf := <-ch:
if itf != nil {
return itf.(error)
}
case <-cln.Disconnected():
return fmt.Errorf("disconnected")
}
return nil
}
// ReadRSSI retrieves the current RSSI value of remote peripheral. [Vol 2, Part E, 7.5.4]
func (cln *Client) ReadRSSI() int {
ch := cln.conn.evl.rssiRead.Listen()
defer cln.conn.evl.rssiRead.Close()
cln.conn.prph.ReadRSSI()
select {
case itf := <-ch:
ev := itf.(*eventRSSIRead)
if ev.err != nil {
return 0
}
return ev.rssi
case <-cln.Disconnected():
return 0
}
}
// ExchangeMTU set the ATT_MTU to the maximum possible value that can be
// supported by both devices [Vol 3, Part G, 4.3.1]
func (cln *Client) ExchangeMTU(mtu int) (int, error) {
// TODO: find the xpc command to tell OS X the rxMTU we can handle.
return cln.conn.TxMTU(), nil
}
// Subscribe subscribes to indication (if ind is set true), or notification of a
// characteristic value. [Vol 3, Part G, 4.10 & 4.11]
func (cln *Client) Subscribe(c *ble.Characteristic, ind bool, fn ble.NotificationHandler) error {
cbchr, err := cln.pc.findCbChr(c)
if err != nil {
return err
}
cln.conn.addSub(c, fn)
ch := cln.conn.evl.notifyChanged.Listen()
defer cln.conn.evl.notifyChanged.Close()
cln.conn.prph.SetNotify(true, cbchr)
select {
case itf := <-ch:
if itf != nil {
cln.conn.delSub(c)
return itf.(error)
}
case <-cln.Disconnected():
cln.conn.delSub(c)
return fmt.Errorf("disconnected")
}
return nil
}
// 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 (cln *Client) Unsubscribe(c *ble.Characteristic, ind bool) error {
cbchr, err := cln.pc.findCbChr(c)
if err != nil {
return err
}
ch := cln.conn.evl.notifyChanged.Listen()
defer cln.conn.evl.notifyChanged.Close()
cln.conn.prph.SetNotify(false, cbchr)
select {
case itf := <-ch:
if itf != nil {
return itf.(error)
}
case <-cln.Disconnected():
return fmt.Errorf("disconnected")
}
cln.conn.delSub(c)
return nil
}
// ClearSubscriptions clears all subscriptions to notifications and indications.
func (cln *Client) ClearSubscriptions() error {
for _, s := range cln.conn.subs {
if err := cln.Unsubscribe(s.char, false); err != nil {
return err
}
}
return nil
}
// CancelConnection disconnects the connection.
func (cln *Client) CancelConnection() error {
cln.cm.CancelConnect(cln.conn.prph)
return nil
}
// Disconnected returns a receiving channel, which is closed when the client disconnects.
func (cln *Client) Disconnected() <-chan struct{} {
return cln.conn.Disconnected()
}
// Conn returns the client's current connection.
func (cln *Client) Conn() ble.Conn {
return cln.conn
}
type sub struct {
fn ble.NotificationHandler
char *ble.Characteristic
}
func (cln *Client) DidDiscoverServices(prph cbgo.Peripheral, err error) {
cln.conn.evl.svcsDiscovered.RxSignal(err)
}
func (cln *Client) DidDiscoverCharacteristics(prph cbgo.Peripheral, svc cbgo.Service, err error) {
cln.conn.evl.chrsDiscovered.RxSignal(err)
}
func (cln *Client) DidDiscoverDescriptors(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
cln.conn.evl.dscsDiscovered.RxSignal(err)
}
func (cln *Client) DidUpdateValueForCharacteristic(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
cln.conn.processChrRead(err, chr)
}
func (cln *Client) DidUpdateValueForDescriptor(prph cbgo.Peripheral, dsc cbgo.Descriptor, err error) {
cln.conn.evl.dscRead.RxSignal(err)
}
func (cln *Client) DidWriteValueForCharacteristic(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
cln.conn.evl.chrWritten.RxSignal(err)
}
func (cln *Client) DidWriteValueForDescriptor(prph cbgo.Peripheral, dsc cbgo.Descriptor, err error) {
cln.conn.evl.dscWritten.RxSignal(err)
}
func (cln *Client) DidUpdateNotificationState(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
cln.conn.evl.notifyChanged.RxSignal(err)
}
func (cln *Client) DidReadRSSI(prph cbgo.Peripheral, rssi int, err error) {
cln.conn.evl.rssiRead.RxSignal(&eventRSSIRead{
err: err,
rssi: int(rssi),
})
}