blob: cb52a06d9fafca48b0bf66f6b980e881001981a3 [file] [log] [blame]
package gatt
import (
"encoding/binary"
"net"
"github.com/paypal/gatt/linux"
"github.com/paypal/gatt/linux/cmd"
)
type device struct {
deviceHandler
hci *linux.HCI
state State
// All the following fields are only used peripheralManager (server) implementation.
svcs []*Service
attrs *attrRange
devID int
chkLE bool
maxConn int
advData *cmd.LESetAdvertisingData
scanResp *cmd.LESetScanResponseData
advParam *cmd.LESetAdvertisingParameters
scanParam *cmd.LESetScanParameters
}
func NewDevice(opts ...Option) (Device, error) {
d := &device{
maxConn: 1, // Support 1 connection at a time.
devID: -1, // Find an available HCI device.
chkLE: true, // Check if the device supports LE.
advParam: &cmd.LESetAdvertisingParameters{
AdvertisingIntervalMin: 0x800, // [0x0800]: 0.625 ms * 0x0800 = 1280.0 ms
AdvertisingIntervalMax: 0x800, // [0x0800]: 0.625 ms * 0x0800 = 1280.0 ms
AdvertisingType: 0x00, // [0x00]: ADV_IND, 0x01: DIRECT(HIGH), 0x02: SCAN, 0x03: NONCONN, 0x04: DIRECT(LOW)
OwnAddressType: 0x00, // [0x00]: public, 0x01: random
DirectAddressType: 0x00, // [0x00]: public, 0x01: random
DirectAddress: [6]byte{}, // Public or Random Address of the device to be connected
AdvertisingChannelMap: 0x7, // [0x07] 0x01: ch37, 0x2: ch38, 0x4: ch39
AdvertisingFilterPolicy: 0x00,
},
scanParam: &cmd.LESetScanParameters{
LEScanType: 0x01, // [0x00]: passive, 0x01: active
LEScanInterval: 0x0010, // [0x10]: 0.625ms * 16
LEScanWindow: 0x0010, // [0x10]: 0.625ms * 16
OwnAddressType: 0x00, // [0x00]: public, 0x01: random
ScanningFilterPolicy: 0x00, // [0x00]: accept all, 0x01: ignore non-white-listed.
},
}
d.Option(opts...)
h, err := linux.NewHCI(d.devID, d.chkLE, d.maxConn)
if err != nil {
return nil, err
}
d.hci = h
return d, nil
}
func (d *device) Init(f func(Device, State)) error {
d.hci.AcceptMasterHandler = func(pd *linux.PlatData) {
a := pd.Address
c := newCentral(d.attrs, net.HardwareAddr([]byte{a[5], a[4], a[3], a[2], a[1], a[0]}), pd.Conn)
if d.centralConnected != nil {
d.centralConnected(c)
}
c.loop()
if d.centralDisconnected != nil {
d.centralDisconnected(c)
}
}
d.hci.AcceptSlaveHandler = func(pd *linux.PlatData) {
p := &peripheral{
d: d,
pd: pd,
l2c: pd.Conn,
reqc: make(chan message),
quitc: make(chan struct{}),
sub: newSubscriber(),
}
if d.peripheralConnected != nil {
go d.peripheralConnected(p, nil)
}
p.loop()
if d.peripheralDisconnected != nil {
d.peripheralDisconnected(p, nil)
}
}
d.hci.AdvertisementHandler = func(pd *linux.PlatData) {
a := &Advertisement{}
a.unmarshall(pd.Data)
a.Connectable = pd.Connectable
p := &peripheral{pd: pd, d: d}
if d.peripheralDiscovered != nil {
pd.Name = a.LocalName
d.peripheralDiscovered(p, a, int(pd.RSSI))
}
}
d.state = StatePoweredOn
d.stateChanged = f
go d.stateChanged(d, d.state)
return nil
}
func (d *device) Stop() error {
d.state = StatePoweredOff
defer d.stateChanged(d, d.state)
return d.hci.Close()
}
func (d *device) AddService(s *Service) error {
d.svcs = append(d.svcs, s)
d.attrs = generateAttributes(d.svcs, uint16(1)) // ble attrs start at 1
return nil
}
func (d *device) RemoveAllServices() error {
d.svcs = nil
d.attrs = nil
return nil
}
func (d *device) SetServices(s []*Service) error {
d.RemoveAllServices()
d.svcs = append(d.svcs, s...)
d.attrs = generateAttributes(d.svcs, uint16(1)) // ble attrs start at 1
return nil
}
func (d *device) AdvertiseNameAndServices(name string, uu []UUID) error {
a := &AdvPacket{}
a.AppendFlags(flagGeneralDiscoverable | flagLEOnly)
for _, u := range uu {
if u.Equal(attrGAPUUID) || u.Equal(attrGATTUUID) {
continue
}
if ok := a.AppendUUIDFit(u); !ok {
break
}
}
if len(a.b)+len(name)+2 < MaxEIRPacketLength {
a.AppendName(name)
d.scanResp = nil
} else {
a := &AdvPacket{}
a.AppendName(name)
d.scanResp = &cmd.LESetScanResponseData{
ScanResponseDataLength: uint8(a.Len()),
ScanResponseData: a.Bytes(),
}
}
d.advData = &cmd.LESetAdvertisingData{
AdvertisingDataLength: uint8(a.Len()),
AdvertisingData: a.Bytes(),
}
if err := d.update(); err != nil {
return err
}
return d.hci.SetAdvertiseEnable(true)
}
func (d *device) AdvertiseIBeaconData(b []byte) error {
a := &AdvPacket{}
a.AppendFlags(flagGeneralDiscoverable | flagLEOnly)
a.AppendManufacturerData(0x004C, b)
d.advData = &cmd.LESetAdvertisingData{
AdvertisingDataLength: uint8(a.Len()),
AdvertisingData: a.Bytes(),
}
if err := d.update(); err != nil {
return err
}
return d.hci.SetAdvertiseEnable(true)
}
func (d *device) AdvertiseIBeacon(u UUID, major, minor uint16, pwr int8) error {
b := make([]byte, 23)
b[0] = 0x02 // Data type: iBeacon
b[1] = 0x15 // Data length: 21 bytes
copy(b[2:], reverse(u.b)) // Big endian
binary.BigEndian.PutUint16(b[18:], major) // Big endian
binary.BigEndian.PutUint16(b[20:], minor) // Big endian
b[22] = uint8(pwr) // Measured Tx Power
return d.AdvertiseIBeaconData(b)
}
func (d *device) StopAdvertising() error {
return d.hci.SetAdvertiseEnable(false)
}
func (d *device) Scan(ss []UUID, dup bool) {
// TODO: filter
d.hci.SetScanEnable(true, dup)
}
func (d *device) StopScanning() {
d.hci.SetScanEnable(false, true)
}
func (d *device) Connect(p Peripheral) {
d.hci.Connect(p.(*peripheral).pd)
}
func (d *device) CancelConnection(p Peripheral) {
d.hci.CancelConnection(p.(*peripheral).pd)
}
func (d *device) SendHCIRawCommand(c cmd.CmdParam) ([]byte, error) {
return d.hci.SendRawCommand(c)
}
// Flush pending advertising settings to the device.
func (d *device) update() error {
if d.advParam != nil {
if err := d.hci.SendCmdWithAdvOff(d.advParam); err != nil {
return err
}
d.advParam = nil
}
if d.scanResp != nil {
if err := d.hci.SendCmdWithAdvOff(d.scanResp); err != nil {
return err
}
d.scanResp = nil
}
if d.advData != nil {
if err := d.hci.SendCmdWithAdvOff(d.advData); err != nil {
return err
}
d.advData = nil
}
return nil
}