fermentord/vendor/github.com/JuulLabs-OSS/ble/darwin/conn.go

226 lines
4.6 KiB
Go
Raw Normal View History

package darwin
import (
"context"
"fmt"
"log"
"sync"
"github.com/JuulLabs-OSS/ble"
"github.com/JuulLabs-OSS/cbgo"
)
// newGenConn creates a new generic (role-less) connection. This should not be
// called directly; use newCentralConn or newPeripheralConn instead.
func newGenConn(d *Device, a ble.Addr) (*conn, error) {
c := &conn{
dev: d,
rxMTU: 23,
txMTU: 23,
addr: a,
done: make(chan struct{}),
notifiers: make(map[cbgo.Characteristic]ble.Notifier),
subs: make(map[string]*sub),
chrReads: make(map[string]chan error),
}
err := d.addConn(c)
if err != nil {
return nil, err
}
go func() {
<-c.done
d.delConn(c.addr)
}()
return c, nil
}
// newCentralConn creates a new connection with us acting as central
// (peer=peripheral).
func newCentralConn(d *Device, prph cbgo.Peripheral) (*conn, error) {
c, err := newGenConn(d, ble.NewAddr(prph.Identifier().String()))
if err != nil {
return nil, err
}
// -3 to account for WriteCommand base.
c.txMTU = prph.MaximumWriteValueLength(false) - 3
c.prph = prph
return c, nil
}
// newCentralConn creates a new connection with us acting as peripheral
// (peer=central).
func newPeripheralConn(d *Device, cent cbgo.Central) (*conn, error) {
c, err := newGenConn(d, ble.NewAddr(cent.Identifier().String()))
if err != nil {
return nil, err
}
// -3 to account for ATT_HANDLE_VALUE_NTF base.
c.txMTU = cent.MaximumUpdateValueLength() - 3
c.cent = cent
return c, nil
}
type conn struct {
sync.RWMutex
dev *Device
ctx context.Context
rxMTU int
txMTU int
addr ble.Addr
done chan struct{}
evl clientEventListener
prph cbgo.Peripheral
cent cbgo.Central
notifiers map[cbgo.Characteristic]ble.Notifier // central connection only
subs map[string]*sub
chrReads map[string](chan error)
}
func (c *conn) Context() context.Context {
return c.ctx
}
func (c *conn) SetContext(ctx context.Context) {
c.ctx = ctx
}
func (c *conn) LocalAddr() ble.Addr {
// return c.dev.Address()
return c.addr // FIXME
}
func (c *conn) RemoteAddr() ble.Addr {
return c.addr
}
func (c *conn) RxMTU() int {
return c.rxMTU
}
func (c *conn) SetRxMTU(mtu int) {
c.rxMTU = mtu
}
func (c *conn) TxMTU() int {
return c.txMTU
}
func (c *conn) SetTxMTU(mtu int) {
c.Lock()
c.txMTU = mtu
c.Unlock()
}
func (c *conn) Read(b []byte) (int, error) {
return 0, nil
}
func (c *conn) Write(b []byte) (int, error) {
return 0, nil
}
func (c *conn) Close() error {
c.evl.Close()
return nil
}
// Disconnected returns a receiving channel, which is closed when the connection disconnects.
func (c *conn) Disconnected() <-chan struct{} {
return c.done
}
// processChrRead handles an incoming read response. CoreBluetooth does not
// distinguish explicit reads from unsolicited notifications. This function
// identifies which type the incoming message is.
func (c *conn) processChrRead(err error, cbchr cbgo.Characteristic) {
c.RLock()
defer c.RUnlock()
uuidStr := uuidStrWithDashes(cbchr.UUID().String())
found := false
ch := c.chrReads[uuidStr]
if ch != nil {
ch <- err
found = true
}
s := c.subs[uuidStr]
if s != nil {
s.fn(cbchr.Value())
found = true
}
if !found {
log.Printf("received characteristic read response without corresponding request: uuid=%s", uuidStr)
}
}
// addChrReader starts listening for a solicited read response.
func (c *conn) addChrReader(char *ble.Characteristic) (chan error, error) {
uuidStr := uuidStrWithDashes(char.UUID.String())
c.Lock()
defer c.Unlock()
if c.chrReads[uuidStr] != nil {
return nil, fmt.Errorf("cannot read from the same attribute twice: uuid=%s", uuidStr)
}
ch := make(chan error)
c.chrReads[uuidStr] = ch
return ch, nil
}
// delChrReader stops listening for a solicited read response.
func (c *conn) delChrReader(char *ble.Characteristic) {
c.Lock()
defer c.Unlock()
uuidStr := uuidStrWithDashes(char.UUID.String())
delete(c.chrReads, uuidStr)
}
// addSub starts listening for unsolicited notifications and indications for a
// particular characteristic.
func (c *conn) addSub(char *ble.Characteristic, fn ble.NotificationHandler) {
uuidStr := uuidStrWithDashes(char.UUID.String())
c.Lock()
defer c.Unlock()
// It feels like we should return an error if we are already subscribed to
// this characteristic. Just quietly overwrite the existing handler to
// preserve backwards compatibility.
c.subs[uuidStr] = &sub{
fn: fn,
char: char,
}
}
// delSub stops listening for unsolicited notifications and indications for a
// particular characteristic.
func (c *conn) delSub(char *ble.Characteristic) {
uuidStr := uuidStrWithDashes(char.UUID.String())
c.Lock()
defer c.Unlock()
delete(c.subs, uuidStr)
}