Søren Rasmussen
07a23c1845
Some checks reported errors
continuous-integration/drone/push Build encountered an error
265 lines
6.6 KiB
Go
265 lines
6.6 KiB
Go
package hci
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/JuulLabs-OSS/ble"
|
|
"github.com/JuulLabs-OSS/ble/linux/adv"
|
|
"github.com/JuulLabs-OSS/ble/linux/gatt"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Addr ...
|
|
func (h *HCI) Addr() ble.Addr { return h.addr }
|
|
|
|
// SetAdvHandler ...
|
|
func (h *HCI) SetAdvHandler(ah ble.AdvHandler) error {
|
|
h.advHandler = ah
|
|
return nil
|
|
}
|
|
|
|
// Scan starts scanning.
|
|
func (h *HCI) Scan(allowDup bool) error {
|
|
h.params.scanEnable.FilterDuplicates = 1
|
|
if allowDup {
|
|
h.params.scanEnable.FilterDuplicates = 0
|
|
}
|
|
h.params.scanEnable.LEScanEnable = 1
|
|
h.adHist = make([]*Advertisement, 128)
|
|
h.adLast = 0
|
|
return h.Send(&h.params.scanEnable, nil)
|
|
}
|
|
|
|
// StopScanning stops scanning.
|
|
func (h *HCI) StopScanning() error {
|
|
h.params.scanEnable.LEScanEnable = 0
|
|
return h.Send(&h.params.scanEnable, nil)
|
|
}
|
|
|
|
// AdvertiseAdv advertises a given Advertisement
|
|
func (h *HCI) AdvertiseAdv(a ble.Advertisement) error {
|
|
ad, err := adv.NewPacket(adv.Flags(adv.FlagGeneralDiscoverable | adv.FlagLEOnly))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f := adv.AllUUID
|
|
|
|
// Current length of ad packet plus two bytes of length and tag.
|
|
l := ad.Len() + 1 + 1
|
|
for _, u := range a.Services() {
|
|
l += u.Len()
|
|
}
|
|
if l > adv.MaxEIRPacketLength {
|
|
f = adv.SomeUUID
|
|
}
|
|
for _, u := range a.Services() {
|
|
if err := ad.Append(f(u)); err != nil {
|
|
if err == adv.ErrNotFit {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
sr, _ := adv.NewPacket()
|
|
switch {
|
|
case ad.Append(adv.CompleteName(a.LocalName())) == nil:
|
|
case sr.Append(adv.CompleteName(a.LocalName())) == nil:
|
|
case sr.Append(adv.ShortName(a.LocalName())) == nil:
|
|
}
|
|
|
|
if a.ManufacturerData() != nil {
|
|
manufacuturerData := adv.ManufacturerData(1337, a.ManufacturerData())
|
|
switch {
|
|
case ad.Append(manufacuturerData) == nil:
|
|
case sr.Append(manufacuturerData) == nil:
|
|
}
|
|
}
|
|
if err := h.SetAdvertisement(ad.Bytes(), sr.Bytes()); err != nil {
|
|
return nil
|
|
}
|
|
return h.Advertise()
|
|
|
|
}
|
|
|
|
// AdvertiseNameAndServices advertises device name, and specified service UUIDs.
|
|
// It tries to fit the UUIDs in the advertising data as much as possible.
|
|
// If name doesn't fit in the advertising data, it will be put in scan response.
|
|
func (h *HCI) AdvertiseNameAndServices(name string, uuids ...ble.UUID) error {
|
|
ad, err := adv.NewPacket(adv.Flags(adv.FlagGeneralDiscoverable | adv.FlagLEOnly))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f := adv.AllUUID
|
|
|
|
// Current length of ad packet plus two bytes of length and tag.
|
|
l := ad.Len() + 1 + 1
|
|
for _, u := range uuids {
|
|
l += u.Len()
|
|
}
|
|
if l > adv.MaxEIRPacketLength {
|
|
f = adv.SomeUUID
|
|
}
|
|
for _, u := range uuids {
|
|
if err := ad.Append(f(u)); err != nil {
|
|
if err == adv.ErrNotFit {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
sr, _ := adv.NewPacket()
|
|
switch {
|
|
case ad.Append(adv.CompleteName(name)) == nil:
|
|
case sr.Append(adv.CompleteName(name)) == nil:
|
|
case sr.Append(adv.ShortName(name)) == nil:
|
|
}
|
|
if err := h.SetAdvertisement(ad.Bytes(), sr.Bytes()); err != nil {
|
|
return nil
|
|
}
|
|
return h.Advertise()
|
|
}
|
|
|
|
// AdvertiseMfgData avertises the given manufacturer data.
|
|
func (h *HCI) AdvertiseMfgData(id uint16, md []byte) error {
|
|
ad, err := adv.NewPacket(adv.ManufacturerData(id, md))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := h.SetAdvertisement(ad.Bytes(), nil); err != nil {
|
|
return nil
|
|
}
|
|
return h.Advertise()
|
|
}
|
|
|
|
// AdvertiseServiceData16 advertises data associated with a 16bit service uuid
|
|
func (h *HCI) AdvertiseServiceData16(id uint16, b []byte) error {
|
|
ad, err := adv.NewPacket(adv.ServiceData16(id, b))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := h.SetAdvertisement(ad.Bytes(), nil); err != nil {
|
|
return nil
|
|
}
|
|
return h.Advertise()
|
|
}
|
|
|
|
// AdvertiseIBeaconData advertise iBeacon with given manufacturer data.
|
|
func (h *HCI) AdvertiseIBeaconData(md []byte) error {
|
|
ad, err := adv.NewPacket(adv.IBeaconData(md))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := h.SetAdvertisement(ad.Bytes(), nil); err != nil {
|
|
return nil
|
|
}
|
|
return h.Advertise()
|
|
}
|
|
|
|
// AdvertiseIBeacon advertises iBeacon with specified parameters.
|
|
func (h *HCI) AdvertiseIBeacon(u ble.UUID, major, minor uint16, pwr int8) error {
|
|
ad, err := adv.NewPacket(adv.IBeacon(u, major, minor, pwr))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := h.SetAdvertisement(ad.Bytes(), nil); err != nil {
|
|
return nil
|
|
}
|
|
return h.Advertise()
|
|
}
|
|
|
|
// StopAdvertising stops advertising.
|
|
func (h *HCI) StopAdvertising() error {
|
|
h.params.advEnable.AdvertisingEnable = 0
|
|
return h.Send(&h.params.advEnable, nil)
|
|
}
|
|
|
|
// Accept starts advertising and accepts connection.
|
|
func (h *HCI) Accept() (ble.Conn, error) {
|
|
var tmo <-chan time.Time
|
|
if h.listenerTmo != time.Duration(0) {
|
|
tmo = time.After(h.listenerTmo)
|
|
}
|
|
select {
|
|
case <-h.done:
|
|
return nil, h.err
|
|
case c := <-h.chSlaveConn:
|
|
return c, nil
|
|
case <-tmo:
|
|
return nil, fmt.Errorf("listner timed out")
|
|
}
|
|
}
|
|
|
|
// Dial ...
|
|
func (h *HCI) Dial(ctx context.Context, a ble.Addr) (ble.Client, error) {
|
|
b, err := net.ParseMAC(a.String())
|
|
if err != nil {
|
|
return nil, ErrInvalidAddr
|
|
}
|
|
h.params.connParams.PeerAddress = [6]byte{b[5], b[4], b[3], b[2], b[1], b[0]}
|
|
if _, ok := a.(RandomAddress); ok {
|
|
h.params.connParams.PeerAddressType = 1
|
|
}
|
|
if err = h.Send(&h.params.connParams, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
var tmo <-chan time.Time
|
|
if h.dialerTmo != time.Duration(0) {
|
|
tmo = time.After(h.dialerTmo)
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return h.cancelDial()
|
|
case <-tmo:
|
|
return h.cancelDial()
|
|
case <-h.done:
|
|
return nil, h.err
|
|
case c := <-h.chMasterConn:
|
|
return gatt.NewClient(c)
|
|
|
|
}
|
|
}
|
|
|
|
// cancelDial cancels the Dialing
|
|
func (h *HCI) cancelDial() (ble.Client, error) {
|
|
err := h.Send(&h.params.connCancel, nil)
|
|
if err == nil {
|
|
// The pending connection was canceled successfully.
|
|
return nil, fmt.Errorf("connection canceled")
|
|
}
|
|
// The connection has been established, the cancel command
|
|
// failed with ErrDisallowed.
|
|
if err == ErrDisallowed {
|
|
return gatt.NewClient(<-h.chMasterConn)
|
|
}
|
|
return nil, errors.Wrap(err, "cancel connection failed")
|
|
}
|
|
|
|
// Advertise starts advertising.
|
|
func (h *HCI) Advertise() error {
|
|
h.params.advEnable.AdvertisingEnable = 1
|
|
return h.Send(&h.params.advEnable, nil)
|
|
}
|
|
|
|
// SetAdvertisement sets advertising data and scanResp.
|
|
func (h *HCI) SetAdvertisement(ad []byte, sr []byte) error {
|
|
if len(ad) > adv.MaxEIRPacketLength || len(sr) > adv.MaxEIRPacketLength {
|
|
return ble.ErrEIRPacketTooLong
|
|
}
|
|
|
|
h.params.advData.AdvertisingDataLength = uint8(len(ad))
|
|
copy(h.params.advData.AdvertisingData[:], ad)
|
|
if err := h.Send(&h.params.advData, nil); err != nil {
|
|
return err
|
|
}
|
|
|
|
h.params.scanResp.ScanResponseDataLength = uint8(len(sr))
|
|
copy(h.params.scanResp.ScanResponseData[:], sr)
|
|
if err := h.Send(&h.params.scanResp, nil); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|