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