diff --git a/.gitignore b/.gitignore index 410e6226..7edfaead 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ -.*.swp -.tags -.tags1 - -/examples/bin/* -vendor -.idea \ No newline at end of file +.idea +.uuid +*DS_Store diff --git a/device.go b/device.go index ced686eb..7eab8cf6 100644 --- a/device.go +++ b/device.go @@ -1,6 +1,8 @@ package ble -import "context" +import ( + "context" +) // Device ... type Device interface { @@ -40,6 +42,9 @@ type Device interface { // Scan starts scanning. Duplicated advertisements will be filtered out if allowDup is set to false. Scan(ctx context.Context, allowDup bool, h AdvHandler) error + // ExtendedScan starts scanning. Duplicated advertisements will be filtered out if allowDup is set to false. + ExtendedScan(ctx context.Context, allowDup bool, h ExtendedAdvHandler) error + // Dial ... Dial(ctx context.Context, a Addr) (Client, error) } diff --git a/examples/basic/extended_scanner/main.go b/examples/basic/extended_scanner/main.go new file mode 100644 index 00000000..1d0756ef --- /dev/null +++ b/examples/basic/extended_scanner/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "context" + "flag" + "fmt" + "github.com/go-ble/ble/examples/lib/dev" + "log" + "time" + + "github.com/go-ble/ble" + "github.com/pkg/errors" +) + +var ( + device = flag.String("device", "default", "implementation of ble") + du = flag.Duration("du", 5*time.Second, "scanning duration") + dup = flag.Bool("dup", true, "allow duplicate reported") +) + +func main() { + flag.Parse() + + d, err := dev.AdvertisingExtensionsDevice() + if err != nil { + log.Fatalf("can't new device : %s", err) + } + ble.SetDefaultDevice(d) + // ExtendedScan for specified durantion, or until interrupted by user. + fmt.Printf("ExtendedScaning for %s...\n", *du) + ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), *du)) + chkErr(ble.ExtendedScan(ctx, *dup, advHandler, nil)) +} + +func advHandler(a ble.ExtendedAdvertisement) { + if a.Connectable() { + fmt.Printf("[%s] C %3d:", a.Addr(), a.RSSI()) + } else { + fmt.Printf("[%s] N %3d:", a.Addr(), a.RSSI()) + } + comma := "" + if len(a.LocalName()) > 0 { + fmt.Printf(" Name: %s", a.LocalName()) + comma = "," + } + if len(a.Services()) > 0 { + fmt.Printf("%s Svcs: %v", comma, a.Services()) + comma = "," + } + if len(a.ManufacturerData()) > 0 { + fmt.Printf("%s MD: %X", comma, a.ManufacturerData()) + } + fmt.Printf("\n") +} + +func chkErr(err error) { + switch errors.Cause(err) { + case nil: + case context.DeadlineExceeded: + fmt.Printf("done\n") + case context.Canceled: + fmt.Printf("canceled\n") + default: + log.Fatalf(err.Error()) + } +} diff --git a/examples/blesh/lnx.go b/examples/blesh/lnx.go index e1d2cb0f..cc2d506f 100644 --- a/examples/blesh/lnx.go +++ b/examples/blesh/lnx.go @@ -31,6 +31,20 @@ func updateLinuxParam(d *linux.Device) error { return errors.Wrap(err, "can't set scan param") } + if err := d.HCI.Send(&cmd.LESetExtendedScanParameters{ + OwnAddressType: 0x00, // 0x00: public, 0x01: random + ScanningFilterPolicy: 0x00, // 0x00: accept all, 0x01: ignore non-white-listed + ScanningPHYs: 0x01, // 0x01: Scan on the LE 1M PHY, 0x04: Scan on the LE Coded PHY + ScanType1M: 0x01, // 0x00: passive scan, 0x01: active scan + ScanInterval1M: 0x0004, // 0x0004 - 0x4000; N * 0.625 msec (2.5 ms) + ScanWindow1M: 0x0004, // 0x0004 - 0x4000; N * 0.625 msec (2.5 ms) + ScanTypeCoded: 0x01, // 0x00: passive scan, 0x01: active scan (for Coded PHY) + ScanIntervalCoded: 0x0004, // N * 0.625 msec, scan interval for Coded PHY + ScanWindowCoded: 0x0004, // N * 0.625 msec, scan window for Coded PHY + }, nil); err != nil { + return errors.Wrap(err, "can't set scan param") + } + if err := d.HCI.Option(ble.OptConnParams( cmd.LECreateConnection{ LEScanInterval: 0x0004, // 0x0004 - 0x4000; N * 0.625 msec diff --git a/examples/lib/dev/default_linux.go b/examples/lib/dev/default_linux.go index eca7bf60..3c8b2a82 100644 --- a/examples/lib/dev/default_linux.go +++ b/examples/lib/dev/default_linux.go @@ -9,3 +9,8 @@ import ( func DefaultDevice(opts ...ble.Option) (d ble.Device, err error) { return linux.NewDevice(opts...) } + +// AdvertisingExtensionsDevice ... +func AdvertisingExtensionsDevice(opts ...ble.Option) (d ble.Device, err error) { + return linux.NewAdvertisingExtensionsDevice(opts...) +} diff --git a/examples/lib/dev/dev.go b/examples/lib/dev/dev.go index cd197e19..6455c82e 100644 --- a/examples/lib/dev/dev.go +++ b/examples/lib/dev/dev.go @@ -8,3 +8,8 @@ import ( func NewDevice(impl string, opts ...ble.Option) (d ble.Device, err error) { return DefaultDevice(opts...) } + +// NewAdvertisingExtensionsDevice ... +func NewAdvertisingExtensionsDevice(impl string, opts ...ble.Option) (d ble.Device, err error) { + return AdvertisingExtensionsDevice(opts...) +} diff --git a/extended_adv.go b/extended_adv.go new file mode 100644 index 00000000..dda93064 --- /dev/null +++ b/extended_adv.go @@ -0,0 +1,22 @@ +package ble + +// ExtendedAdvHandler handles extended advertisements. +type ExtendedAdvHandler func(a ExtendedAdvertisement) + +// ExtendedAdvFilter returns true if the extended advertisement matches specified condition. +type ExtendedAdvFilter func(a ExtendedAdvertisement) bool + +// ExtendedAdvertisement ... +type ExtendedAdvertisement interface { + LocalName() string + ManufacturerData() []byte + ServiceData() []ServiceData + Services() []UUID + OverflowService() []UUID + TxPowerLevel() int + Connectable() bool + SolicitedService() []UUID + + RSSI() int + Addr() Addr +} diff --git a/gatt.go b/gatt.go index ae8fcc00..3c450af2 100644 --- a/gatt.go +++ b/gatt.go @@ -100,6 +100,26 @@ func Scan(ctx context.Context, allowDup bool, h AdvHandler, f AdvFilter) error { return defaultDevice.Scan(ctx, allowDup, h2) } +// ExtendedScan starts scanning. Duplicated advertisements will be filtered out if allowDup is set to false. +func ExtendedScan(ctx context.Context, allowDup bool, h ExtendedAdvHandler, f AdvFilter) error { + if defaultDevice == nil { + return ErrDefaultDevice + } + defer untrap(trap(ctx)) + + if f == nil { + return defaultDevice.ExtendedScan(ctx, allowDup, h) + } + + h2 := func(a ExtendedAdvertisement) { + if f(a) { + h(a) + } + } + + return defaultDevice.ExtendedScan(ctx, allowDup, h2) +} + // Find ... func Find(ctx context.Context, allowDup bool, f AdvFilter) ([]Advertisement, error) { if defaultDevice == nil { diff --git a/linux/adv/packet.go b/linux/adv/packet.go index ce555f0d..b4d6391f 100644 --- a/linux/adv/packet.go +++ b/linux/adv/packet.go @@ -2,7 +2,6 @@ package adv import ( "encoding/binary" - "github.com/go-ble/ble" ) diff --git a/linux/device.go b/linux/device.go index 4876c50c..52d30a85 100644 --- a/linux/device.go +++ b/linux/device.go @@ -50,6 +50,44 @@ func NewDeviceWithNameAndHandler(name string, handler ble.NotifyHandler, opts .. return &Device{HCI: dev, Server: srv}, nil } +// NewAdvertisingExtensionsDevice returns the default HCI device. +func NewAdvertisingExtensionsDevice(opts ...ble.Option) (*Device, error) { + return NewAdvertisingExtensionsDeviceWithName("Gopher", opts...) +} + +// NewAdvertisingExtensionsDeviceWithName returns the default HCI device. +func NewAdvertisingExtensionsDeviceWithName(name string, opts ...ble.Option) (*Device, error) { + return NewAdvertisingExtensionsDeviceWithNameAndHandler(name, nil, opts...) +} + +func NewAdvertisingExtensionsDeviceWithNameAndHandler(name string, handler ble.NotifyHandler, opts ...ble.Option) (*Device, error) { + dev, err := hci.NewHCIForAdvertisingExtensions(opts...) + if err != nil { + return nil, errors.Wrap(err, "can't create hci") + } + if err = dev.Init(); err != nil { + dev.Close() + return nil, errors.Wrap(err, "can't init hci") + } + + srv, err := gatt.NewServerWithNameAndHandler(name, handler) + if err != nil { + dev.Close() + return nil, errors.Wrap(err, "can't create server") + } + + // mtu := ble.DefaultMTU + mtu := ble.MaxMTU // TODO: get this from user using Option. + if mtu > ble.MaxMTU { + dev.Close() + return nil, errors.Wrapf(err, "maximum ATT_MTU is %d", ble.MaxMTU) + } + + go loop(dev, srv, mtu) + + return &Device{HCI: dev, Server: srv}, nil +} + func loop(dev *hci.HCI, s *gatt.Server, mtu int) { for { l2c, err := dev.Accept() @@ -105,6 +143,11 @@ func (d *Device) Stop() error { return d.HCI.Close() } +// Reset +func (d *Device) Reset(ctx context.Context) error { + return d.HCI.ResetHCI() +} + func (d *Device) Advertise(ctx context.Context, adv ble.Advertisement) error { if err := d.HCI.AdvertiseAdv(adv); err != nil { return err @@ -208,6 +251,19 @@ func (d *Device) Scan(ctx context.Context, allowDup bool, h ble.AdvHandler) erro return ctx.Err() } +// ExtendedScan starts scanning. Duplicated advertisements will be filtered out if allowDup is set to false. +func (d *Device) ExtendedScan(ctx context.Context, allowDup bool, h ble.ExtendedAdvHandler) error { + if err := d.HCI.SetExtendedAdvHandler(h); err != nil { + return err + } + if err := d.HCI.ExtendedScan(allowDup); err != nil { + return err + } + <-ctx.Done() + d.HCI.StopExtendedScan() + return ctx.Err() +} + // Dial ... func (d *Device) Dial(ctx context.Context, a ble.Addr) (ble.Client, error) { // d.HCI.Dial is a blocking call, although most of time it should return immediately. diff --git a/linux/hci/cmd/cmd_gen.go b/linux/hci/cmd/cmd_gen.go index 8b10a151..06444661 100644 --- a/linux/hci/cmd/cmd_gen.go +++ b/linux/hci/cmd/cmd_gen.go @@ -1560,3 +1560,140 @@ type LERemoteConnectionParameterRequestNegativeReplyRP struct { func (c *LERemoteConnectionParameterRequestNegativeReplyRP) Unmarshal(b []byte) error { return unmarshal(c, b) } + +// LESetExtendedScanParameters implements LE Set Extended Scan Parameters (0x08|0x0041) [Vol 4, Part E, 7.8.64] +type LESetExtendedScanParameters struct { + OwnAddressType uint8 + ScanningFilterPolicy uint8 + ScanningPHYs uint8 + ScanType1M uint8 + ScanInterval1M uint16 + ScanWindow1M uint16 + ScanTypeCoded uint8 + ScanIntervalCoded uint16 + ScanWindowCoded uint16 +} + +func (c *LESetExtendedScanParameters) String() string { + return "LE Set Extended Scan Parameters (0x08|0x0041)" +} + +// OpCode returns the opcode of the command. +func (c *LESetExtendedScanParameters) OpCode() int { return 0x08<<10 | 0x0041 } + +// Len returns the length of the command. +func (c *LESetExtendedScanParameters) Len() int { return 13 } + +// Marshal serializes the command parameters into binary form. +func (c *LESetExtendedScanParameters) Marshal(b []byte) error { + return marshal(c, b) +} + +// LESetExtendedScanParametersRP returns the return parameter of LE Set Extended Scan Parameters +type LESetExtendedScanParametersRP struct { + Status uint8 +} + +// Unmarshal de-serializes the binary data and stores the result in the receiver. +func (c *LESetExtendedScanParametersRP) Unmarshal(b []byte) error { + return unmarshal(c, b) +} + +// LESetExtendedScanEnable implements LE Set Extended Scan Enable (0x08|0x0042) [Vol 4, Part E, 7.8.65] +type LESetExtendedScanEnable struct { + Enable uint8 + FilterDuplicates uint8 + Duration uint16 + Period uint16 +} + +func (c *LESetExtendedScanEnable) String() string { + return "LE Set Extended Scan Enable (0x08|0x0042)" +} + +// OpCode returns the opcode of the command. +func (c *LESetExtendedScanEnable) OpCode() int { return 0x08<<10 | 0x0042 } + +// Len returns the length of the command. +func (c *LESetExtendedScanEnable) Len() int { return 6 } + +// Marshal serializes the command parameters into binary form. +func (c *LESetExtendedScanEnable) Marshal(b []byte) error { + return marshal(c, b) +} + +// LESetExtendedScanEnableRP returns the return parameter of LE Set Extended Scan Enable +type LESetExtendedScanEnableRP struct { + Status uint8 +} + +// Unmarshal de-serializes the binary data and stores the result in the receiver. +func (c *LESetExtendedScanEnableRP) Unmarshal(b []byte) error { + return unmarshal(c, b) +} + +// LESetDefaultPHY implements LE Set Default PHY (0x08|0x0031) [Vol 4, Part E, 7.8.48] +type LESetDefaultPHY struct { + AllPHYs uint8 + TXPHYs uint8 + RXPHYs uint8 +} + +func (c *LESetDefaultPHY) String() string { + return "LE Set Default PHY (0x08|0x0031)" +} + +// OpCode returns the opcode of the command. +func (c *LESetDefaultPHY) OpCode() int { return 0x08<<10 | 0x0031 } + +// Len returns the length of the command. +func (c *LESetDefaultPHY) Len() int { return 3 } + +// Marshal serializes the command parameters into binary form. +func (c *LESetDefaultPHY) Marshal(b []byte) error { + return marshal(c, b) +} + +// LESetExtendedAdvertisingParameters implements LE Set Extended Advertising Parameters (0x08|0x0036) [Vol 4, Part E, 7.8.53] +type LESetExtendedAdvertisingParameters struct { + AdvertisingHandle uint8 + AdvertisingEventProperties uint16 + PrimaryAdvertisingIntervalMin [3]byte + PrimaryAdvertisingIntervalMax [3]byte + PrimaryAdvertisingChannelMap uint8 + OwnAddressType uint8 + PeerAddressType uint8 + PeerAddress [6]byte + AdvertisingFilterPolicy uint8 + AdvertisingTXPower int8 + PrimaryAdvertisingPHY uint8 + SecondaryAdvertisingMaxSkip uint8 + SecondaryAdvertisingPHY uint8 + AdvertisingSID uint8 + ScanRequestNotificationEnable uint8 +} + +func (c *LESetExtendedAdvertisingParameters) String() string { + return "LE Set Extended Advertising Parameters (0x08|0x0036)" +} + +// OpCode returns the opcode of the command. +func (c *LESetExtendedAdvertisingParameters) OpCode() int { return 0x08<<10 | 0x0036 } + +// Len returns the length of the command. +func (c *LESetExtendedAdvertisingParameters) Len() int { return 25 } + +// Marshal serializes the command parameters into binary form. +func (c *LESetExtendedAdvertisingParameters) Marshal(b []byte) error { + return marshal(c, b) +} + +// LESetExtendedAdvertisingParametersRP returns the return parameter of LE Set Extended Advertising Parameters +type LESetExtendedAdvertisingParametersRP struct { + Status uint8 +} + +// Unmarshal de-serializes the binary data and stores the result in the receiver. +func (c *LESetExtendedAdvertisingParametersRP) Unmarshal(b []byte) error { + return unmarshal(c, b) +} diff --git a/linux/hci/evt/evt.go b/linux/hci/evt/evt.go index 4ce88d52..1be2e6d4 100644 --- a/linux/hci/evt/evt.go +++ b/linux/hci/evt/evt.go @@ -1,6 +1,9 @@ package evt -import "encoding/binary" +import ( + "encoding/binary" + "fmt" +) func (e CommandComplete) NumHCICommandPackets() uint8 { return e[0] } func (e CommandComplete) CommandOpcode() uint16 { return binary.LittleEndian.Uint16(e[1:]) } @@ -53,3 +56,120 @@ func (e LEAdvertisingReport) RSSI(i int) int8 { } return int8(e[2+int(e.NumReports())*9+l+i]) } + +// LEExtendedAdvertisingReport +// 7.7.65.13 LE Extended Advertising Report +// sample +// [0] Subevent_Code +// 0x0d +// [1] Num_Reports +// 0x01 +// [2,3] Num_Reports[0].Event_Type +// 0x10 0x00 +// [4] Num_Reports[0].Address_Type +// 0x01 +// [5,6,7,8,9,10] Num_Reports[0].Address +// 0x87 0xe8 0xdb 0x97 0x13 0x75 +// [11] Num_Reports[0].Primary_PHY +// 0x01 +// [12] Num_Reports[0].Secondary_PHY +// 0x00 +// [13] Num_Reports[0].Advertising_SID +// 0xff +// [14] Num_Reports[0].TX_Power +// 0x7f +// [15] Num_Reports[0].RSSI +// 0xde +// [16,17] Num_Reports[0].Periodic_Advertising_Interval +// 0x00 0x00 +// [18] Num_Reports[0].Direct_Address_Type +// 0x00 +// [19,20,21,22,23,24] Num_Reports[0].Direct_Address_Type +// 0x00 0x00 0x00 0x00 0x00 0x00 +// [25] Num_Reports[0].Data_Length +// 0x1b +// [26~x] Num_Reports[0].Data +// 0x02 0x01 0x1a 0x17 0xff 0x4c 0x00 0x09 0x08 0x13 0x01 0xc0 0xa8 0x00 0x03 0x1b 0x58 0x16 0x08 0x00 0x4e 0x6e 0xe9 0x0b 0x53 0x48 0xdf + +const ( + LEExtendedAdvertisingReportMetaDataLength = 2 + LEExtendedAdvertisingReportFixedDataLength = 24 +) + +type ExtendedAdvertisingReport struct { + SubeventCode uint8 + NumReports uint8 + Reports []ExtendedAdvertisingData +} + +// AdvertisingReport represents an individual report within the LE Extended Advertising Report +type ExtendedAdvertisingData struct { + EventType uint16 + AddressType uint8 + Address [6]byte + PrimaryPHY uint8 + SecondaryPHY uint8 + AdvertisingSID uint8 + TXPower int8 + RSSI int8 + PeriodicAdvertisingInterval uint16 + DirectAddressType uint8 + DirectAddress [6]byte + DataLength uint8 + Data []byte + ScanResp *ExtendedAdvertisingData +} + +// ParseLEExtendedAdvertisingReport parses a byte array into a leExtendedAdvertisingReport structure +func NewExtendedAdvertisingReport(data LEExtendedAdvertisingReport) ( + *ExtendedAdvertisingReport, + error, +) { + if len(data) < 2 { + return nil, fmt.Errorf("invalid data length") + } + + report := &ExtendedAdvertisingReport{ + SubeventCode: data[0], + NumReports: data[1], + } + + offset := 2 + for i := 0; i < int(report.NumReports); i++ { + if offset+26 > len(data) { + return nil, fmt.Errorf("invalid data length for report %d", i) + } + + // Parse individual report + r := ExtendedAdvertisingData{ + EventType: uint16(data[offset]) | uint16(data[offset+1])<<8, + AddressType: data[offset+2], + PrimaryPHY: data[offset+11], + SecondaryPHY: data[offset+12], + AdvertisingSID: data[offset+13], + TXPower: int8(data[offset+14]), + RSSI: int8(data[offset+15]), + PeriodicAdvertisingInterval: uint16(data[offset+16]) | uint16(data[offset+17])<<8, + DirectAddressType: data[offset+18], + DataLength: data[offset+25], + } + copy(r.Address[:], data[offset+3:offset+9]) + copy(r.DirectAddress[:], data[offset+19:offset+25]) + + // Parse data field + dataEnd := offset + 26 + int(r.DataLength) + if dataEnd > len(data) { + return nil, fmt.Errorf("invalid data length for report %d data", i) + } + r.Data = data[offset+26 : dataEnd] + + // Add to report list + report.Reports = append(report.Reports, r) + offset = dataEnd + } + + return report, nil +} + +func (e LEExtendedAdvertisingReport) SubeventCode() uint8 { return e[0] } +func (e LEExtendedAdvertisingReport) NumReports() uint8 { return e[1] } diff --git a/linux/hci/evt/evt_gen.go b/linux/hci/evt/evt_gen.go index 7582d7f3..11ccfe58 100644 --- a/linux/hci/evt/evt_gen.go +++ b/linux/hci/evt/evt_gen.go @@ -225,3 +225,10 @@ type AuthenticatedPayloadTimeoutExpired []byte func (r AuthenticatedPayloadTimeoutExpired) ConnectionHandle() uint16 { return binary.LittleEndian.Uint16(r[0:]) } + +const LEExtendedAdvertisingReportCode = 0x3E + +const LEExtendedAdvertisingReportSubCode = 0x0D + +// LEExtendedAdvertisingReport implements LE Extended Advertising Report (0x3E:0x0D) [Vol 4, Part E, 7.7.65.13]. +type LEExtendedAdvertisingReport []byte diff --git a/linux/hci/extended_adv.go b/linux/hci/extended_adv.go new file mode 100644 index 00000000..f209ba94 --- /dev/null +++ b/linux/hci/extended_adv.go @@ -0,0 +1,212 @@ +package hci + +import ( + "fmt" + "github.com/go-ble/ble" + "github.com/go-ble/ble/linux/adv" + "github.com/go-ble/ble/linux/hci/evt" + "net" +) + +const ( + evtTypExtendedAdvInd = 0b00100011 // ADV_IND (Connectable and Scannable Undirected Advertising) + evtTypExtendedAdvDirectInd = 0b00100101 // ADV_DIRECT_IND (Connectable Directed Advertising) + evtTypExtendedAdvScanInd = 0b00100100 // ADV_SCAN_IND (Scannable Undirected Advertising) + evtTypExtendedAdvNonConnInd = 0b00100000 // ADV_NONCONN_IND (Non-Connectable Undirected Advertising) + evtTypScanRspToExtendedAdvInd = 0b00101111 // SCAN_RSP to ADV_IND (Scan Response to ADV_IND) + evtTypScanRspToExtendedAdvScanInd = 0b00101110 // SCAN_RSP to ADV_SCAN_IND (Scan Response to ADV_SCAN_IND) +) + +type LEExtendedAdvertisingReport struct { + SubeventCode uint8 + NumReports uint8 + Reports []ExtendedAdvertisingData +} + +// AdvertisingReport represents an individual report within the LE Extended Advertising Report +type ExtendedAdvertisingData struct { + eventType uint16 + addressType uint8 + address []byte + primaryPHY uint8 + secondaryPHY uint8 + advertisingSID uint8 + txPower int8 + rssi int8 + periodicAdvertisingInterval uint16 + directAddressType uint8 + directAddress []byte + dataLength uint8 + data []byte + scanResp *ExtendedAdvertisingData + + // cached packets. + p *adv.Packet +} + +// sample + +// 0x0d Subevent_Code +// 0x01 Num_Reports +// 0x10 0x00 Event_Type +// 0x01 Address_Type +// 0xde 0xb4 0xc0 0xf9 0x90 0xfa Address +// 0x01 Primary_PHY +// 0x00 Secondary_PHY +// 0xff Advertising_SID +// 0x7f TX_Power +// 0xdb RSSI +// 0x00 0x00 Periodic_Advertising_Interval +// 0x00 Direct_Address_Type +// 0x00 +// 0x00 0x00 0x00 0x00 0x00 0x08 Direct_Address +// 0x07 +// 0xff 0x4c 0x00 0x12 0x02 0x00 0x03 + +const reportDataStaticLength = 24 + +func newLEExtendedAdvertisingReport(data evt.LEExtendedAdvertisingReport) ( + *LEExtendedAdvertisingReport, + error, +) { + if len(data) < 2 { + return nil, fmt.Errorf("invalid data length") + } + + report := &LEExtendedAdvertisingReport{ + SubeventCode: data[0], + NumReports: data[1], + } + + offset := 2 + for i := 0; i < int(report.NumReports); i++ { + if offset+reportDataStaticLength > len(data) { + return nil, fmt.Errorf("invalid data length %d for report %d", len(data), i) + } + + // Parse individual report + r := ExtendedAdvertisingData{ + eventType: uint16(data[offset]) | uint16(data[offset+1])<<8, + addressType: data[offset+2], + address: data[offset+3 : offset+9], + primaryPHY: data[offset+9], + secondaryPHY: data[offset+10], + advertisingSID: data[offset+11], + txPower: int8(data[offset+12]), + rssi: int8(data[offset+13]), + periodicAdvertisingInterval: uint16(data[offset+14]) | uint16(data[offset+15])<<8, + directAddressType: data[offset+16], + directAddress: data[offset+19 : offset+22], + dataLength: data[offset+23], + scanResp: &ExtendedAdvertisingData{}, + } + + // Parse data field + dataEnd := offset + reportDataStaticLength + int(r.dataLength) + if dataEnd > len(data) { + return nil, fmt.Errorf("invalid data length %d, dataEnd %d, for report %d data", len(data), dataEnd, i) + } + r.data = data[offset+reportDataStaticLength : dataEnd] + + // Add to report list + report.Reports = append(report.Reports, r) + offset = dataEnd + } + + return report, nil +} + +// packets returns the combined extended advertising packet and scan response (if present). +func (a *ExtendedAdvertisingData) packets() *adv.Packet { + if a.p != nil { + return a.p + } + b := a.data + if a.scanResp != nil { + b = append(b, a.scanResp.data...) + } + return adv.NewRawPacket(b) +} + +func (a *ExtendedAdvertisingData) EventType() uint16 { + return a.eventType +} + +func (a *ExtendedAdvertisingData) SetScanResp(d *ExtendedAdvertisingData) { + a.scanResp = d +} + +// LocalName returns the LocalName of the remote peripheral. +func (a *ExtendedAdvertisingData) LocalName() string { + if a.packets().LocalName() != "" { + return a.packets().LocalName() + } + if a.scanResp != nil && a.scanResp.LocalName() != "" { + return a.scanResp.LocalName() + } + return "" +} + +// ManufacturerData returns the ManufacturerData of the advertisement. +func (a *ExtendedAdvertisingData) ManufacturerData() []byte { + return a.packets().ManufacturerData() +} + +// ServiceData returns the service data of the advertisement. +func (a *ExtendedAdvertisingData) ServiceData() []ble.ServiceData { + return a.packets().ServiceData() +} + +// Services returns the service UUIDs of the advertisement. +func (a *ExtendedAdvertisingData) Services() []ble.UUID { + return a.packets().UUIDs() +} + +// OverflowService returns the UUIDs of overflowed services. +func (a *ExtendedAdvertisingData) OverflowService() []ble.UUID { + return a.packets().UUIDs() +} + +// TxPowerLevel returns the tx power level of the remote peripheral. +func (a *ExtendedAdvertisingData) TxPowerLevel() int { + pwr, _ := a.packets().TxPower() + return pwr +} + +// SolicitedService returns UUIDs of solicited services. +func (a *ExtendedAdvertisingData) SolicitedService() []ble.UUID { + return a.packets().ServiceSol() +} + +// Connectable indicates whether the remote peripheral is connectable. +func (a *ExtendedAdvertisingData) Connectable() bool { + return a.eventType == evtTypAdvDirectInd || a.eventType == evtTypAdvInd +} + +func (a *ExtendedAdvertisingData) RSSI() int { + return int(a.rssi) +} + +// Addr returns the address of the remote peripheral. +func (a *ExtendedAdvertisingData) Addr() ble.Addr { + addr := net.HardwareAddr([]byte{a.address[5], a.address[4], a.address[3], a.address[2], a.address[1], a.address[0]}) + if a.addressType == 1 { + return RandomAddress{addr} + } + return addr +} + +// Data returns the extended advertising data of the packet. +// This is Linux specific. +func (a *ExtendedAdvertisingData) Data() []byte { + return a.data +} + +// ScanResponse returns the scan response of the extended packet, if present. +// This is Linux specific. +func (a *ExtendedAdvertisingData) ScanResponse() []byte { + if a.scanResp == nil { + return nil + } + return a.scanResp.Data() +} diff --git a/linux/hci/gap.go b/linux/hci/gap.go index 85ea57fe..38f01df3 100644 --- a/linux/hci/gap.go +++ b/linux/hci/gap.go @@ -4,6 +4,7 @@ import ( "context" "encoding/binary" "fmt" + "github.com/go-ble/ble/linux/hci/cmd" "net" "time" @@ -22,6 +23,12 @@ func (h *HCI) SetAdvHandler(ah ble.AdvHandler) error { return nil } +// SetExtendedAdvHandler ... +func (h *HCI) SetExtendedAdvHandler(ah ble.ExtendedAdvHandler) error { + h.extendedAdvHandler = ah + return nil +} + // Scan starts scanning. func (h *HCI) Scan(allowDup bool) error { h.params.scanEnable.FilterDuplicates = 1 @@ -34,12 +41,65 @@ func (h *HCI) Scan(allowDup bool) error { return h.Send(&h.params.scanEnable, nil) } +// StopScanning stops scanning. +func (h *HCI) Reset() error { + return h.ResetHCI() +} + // StopScanning stops scanning. func (h *HCI) StopScanning() error { h.params.scanEnable.LEScanEnable = 0 return h.Send(&h.params.scanEnable, nil) } +// LESetExtendedScanParameters starts scanning. +func (h *HCI) LESetExtendedScanParameters() error { + //h.params.extendedScanParams = cmd.LESetExtendedScanParameters{ + // OwnAddressType: 0x00, // 0x00: public, 0x01: random + // ScanningFilterPolicy: 0x00, // 0x00: accept all, 0x01: ignore non-white-listed + // ScanningPHYs: 0x05, // 0x01: Scan on the LE 1M PHY, 0x04: Scan on the LE Coded PHY + // ScanType1M: 0x01, // 0x00: passive scan, 0x01: active scan + // ScanInterval1M: 0x0064, // 0x0004 - 0x4000; N * 0.625 msec (2.5 ms) + // ScanWindow1M: 0x0032, // 0x0004 - 0x4000; N * 0.625 msec (2.5 ms) + // ScanTypeCoded: 0x01, // 0x00: passive scan, 0x01: active scan (for Coded PHY) + // ScanIntervalCoded: 0x0BB8, // N * 0.625 msec, scan interval for Coded PHY + // ScanWindowCoded: 0x0032, // N * 0.625 msec, scan window for Coded PHY + //} + return h.Send(&h.params.extendedScanParams, nil) +} + +// LeSetDefaultPHYForExtendedScan starts scanning. +func (h *HCI) LeSetDefaultPHYForExtendedScan() error { + h.params.leSetDefaultPHY = cmd.LESetDefaultPHY{ + AllPHYs: 0x00, // 0x00: No preference, use all PHYs + TXPHYs: 0x07, // 0x01: LE 1M, 0x02: LE 2M, 0x04: LE Coded (0x07 to use all PHYs) + RXPHYs: 0x07, // 0x01: LE 1M, 0x02: LE 2M, 0x04: LE Coded (uses all PHYs at 0x07) + } + return h.Send(&h.params.leSetDefaultPHY, nil) +} + +// ExtendedScan starts scanning. +func (h *HCI) ExtendedScan(allowDup bool) error { + h.params.extendedScanEnable = cmd.LESetExtendedScanEnable{ + FilterDuplicates: 0x01, + Duration: 0xBB8, + Period: 0x12C, + } + if allowDup { + h.params.extendedScanEnable.FilterDuplicates = 0 + } + h.params.extendedScanEnable.Enable = 1 + h.extendedAdHist = make([]*ExtendedAdvertisingData, 128) + h.extendedAdLast = 0 + return h.Send(&h.params.extendedScanEnable, nil) +} + +// StopExtendedScan stops scanning. +func (h *HCI) StopExtendedScan() error { + h.params.extendedScanEnable.Enable = 0 + return h.Send(&h.params.extendedScanEnable, nil) +} + // AdvertiseAdv advertises a given Advertisement func (h *HCI) AdvertiseAdv(a ble.Advertisement) error { ad, err := adv.NewPacket(adv.Flags(adv.FlagGeneralDiscoverable | adv.FlagLEOnly)) diff --git a/linux/hci/hci.go b/linux/hci/hci.go index bd9b3a80..4dac0c2b 100644 --- a/linux/hci/hci.go +++ b/linux/hci/hci.go @@ -63,6 +63,34 @@ func NewHCI(opts ...ble.Option) (*HCI, error) { return h, nil } +// NewHCI returns a hci device. +func NewHCIForAdvertisingExtensions(opts ...ble.Option) (*HCI, error) { + h := &HCI{ + id: -1, + + chCmdPkt: make(chan *pkt), + chCmdBufs: make(chan []byte, 16), + sent: make(map[int]*pkt), + muSent: &sync.Mutex{}, + + evth: map[int]handlerFn{}, + subh: map[int]handlerFn{}, + + muConns: &sync.Mutex{}, + conns: make(map[uint16]*Conn), + chMasterConn: make(chan *Conn), + chSlaveConn: make(chan *Conn), + + done: make(chan bool), + } + h.params.initForAdvertisingExtensions() + if err := h.Option(opts...); err != nil { + return nil, errors.Wrap(err, "can't set options") + } + + return h, nil +} + // HCI ... type HCI struct { sync.Mutex @@ -101,6 +129,10 @@ type HCI struct { adHist []*Advertisement adLast int + extendedAdvHandler ble.ExtendedAdvHandler + extendedAdHist []*ExtendedAdvertisingData + extendedAdLast int + // Host to Controller Data Flow Control Packet-based Data flow control for LE-U [Vol 2, Part E, 4.1.1] // Minimum 27 bytes. 4 bytes of L2CAP Header, and 23 bytes Payload from upper layer (ATT) pool *Pool @@ -130,6 +162,7 @@ func (h *HCI) Init() error { h.evth[evt.NumberOfCompletedPacketsCode] = h.handleNumberOfCompletedPackets h.subh[evt.LEAdvertisingReportSubCode] = h.handleLEAdvertisingReport + h.subh[evt.LEExtendedAdvertisingReportSubCode] = h.handleLEExtendedAdvertisingReport h.subh[evt.LEConnectionCompleteSubCode] = h.handleLEConnectionComplete h.subh[evt.LEConnectionUpdateCompleteSubCode] = h.handleLEConnectionUpdateComplete h.subh[evt.LELongTermKeyRequestSubCode] = h.handleLELongTermKeyRequest @@ -151,16 +184,28 @@ func (h *HCI) Init() error { h.setAllowedCommands(1) go h.sktLoop() - if err := h.init(); err != nil { - return err + if h.params.extendedScanParams.ScanningPHYs != 0x00 { + if err := h.initForAdvertisingExtensions(); err != nil { + return err + } + } else { + if err := h.init(); err != nil { + return err + } } // Pre-allocate buffers with additional head room for lower layer headers. // HCI header (1 Byte) + ACL Data Header (4 bytes) + L2CAP PDU (or fragment) h.pool = NewPool(1+4+h.bufSize, h.bufCnt-1) - h.Send(&h.params.advParams, nil) - h.Send(&h.params.scanParams, nil) + if h.params.extendedScanParams.ScanningPHYs != 0x00 { + log.Println("Enable Advertising Extensions") + h.Send(&h.params.leSetDefaultPHY, nil) + h.Send(&h.params.extendedScanParams, nil) + } else { + h.Send(&h.params.advParams, nil) + h.Send(&h.params.scanParams, nil) + } return nil } @@ -188,6 +233,10 @@ func (h *HCI) Option(opts ...ble.Option) error { return err } +func (h *HCI) ResetHCI() error { + return h.init() +} + func (h *HCI) init() error { h.Send(&cmd.Reset{}, nil) @@ -213,6 +262,7 @@ func (h *HCI) init() error { h.bufSize = int(LEReadBufferSizeRP.HCLEDataPacketLength) } + // TODO LEReadAdvertisingChannelTxPowerRP := cmd.LEReadAdvertisingChannelTxPowerRP{} h.Send(&cmd.LEReadAdvertisingChannelTxPower{}, &LEReadAdvertisingChannelTxPowerRP) @@ -230,6 +280,43 @@ func (h *HCI) init() error { return h.err } +func (h *HCI) initForAdvertisingExtensions() error { + h.Send(&cmd.Reset{}, nil) + + ReadBDADDRRP := cmd.ReadBDADDRRP{} + h.Send(&cmd.ReadBDADDR{}, &ReadBDADDRRP) + + a := ReadBDADDRRP.BDADDR + h.addr = net.HardwareAddr([]byte{a[5], a[4], a[3], a[2], a[1], a[0]}) + + ReadBufferSizeRP := cmd.ReadBufferSizeRP{} + h.Send(&cmd.ReadBufferSize{}, &ReadBufferSizeRP) + + // Assume the buffers are shared between ACL-U and LE-U. + h.bufCnt = int(ReadBufferSizeRP.HCTotalNumACLDataPackets) + h.bufSize = int(ReadBufferSizeRP.HCACLDataPacketLength) + + LEReadBufferSizeRP := cmd.LEReadBufferSizeRP{} + h.Send(&cmd.LEReadBufferSize{}, &LEReadBufferSizeRP) + + if LEReadBufferSizeRP.HCTotalNumLEDataPackets != 0 { + // Okay, LE-U do have their own buffers. + h.bufCnt = int(LEReadBufferSizeRP.HCTotalNumLEDataPackets) + h.bufSize = int(LEReadBufferSizeRP.HCLEDataPacketLength) + } + + LESetEventMaskRP := cmd.LESetEventMaskRP{} + h.Send(&cmd.LESetEventMask{LEEventMask: 0x000000000000101F}, &LESetEventMaskRP) + + SetEventMaskRP := cmd.SetEventMaskRP{} + h.Send(&cmd.SetEventMask{EventMask: 0x3dbff807fffbffff}, &SetEventMaskRP) + + WriteLEHostSupportRP := cmd.WriteLEHostSupportRP{} + h.Send(&cmd.WriteLEHostSupport{LESupportedHost: 1, SimultaneousLEHost: 0}, &WriteLEHostSupportRP) + + return h.err +} + // Send ... func (h *HCI) Send(c Command, r CommandRP) error { // Only allow one send after another to prevent race condition @@ -316,6 +403,7 @@ func (h *HCI) sktLoop() { } p := make([]byte, n) copy(p, b) + if err := h.handlePkt(p); err != nil { // Some bluetooth devices may append vendor specific packets at the last, // in this case, simply ignore them. @@ -446,6 +534,56 @@ func (h *HCI) handleLEAdvertisingReport(b []byte) error { return nil } +func (h *HCI) handleLEExtendedAdvertisingReport(b []byte) error { + if h.extendedAdvHandler == nil { + return nil + } + + report, err := newLEExtendedAdvertisingReport(evt.LEExtendedAdvertisingReport(b)) + + if err != nil { + panic(err) + } + + for i := 0; i < int(report.NumReports); i++ { + var a *ExtendedAdvertisingData + r := &report.Reports[i] + switch r.EventType() { + case evtTypExtendedAdvInd, evtTypExtendedAdvScanInd: + a = &report.Reports[i] + h.extendedAdHist[h.extendedAdLast] = a + h.extendedAdLast++ + if h.extendedAdLast == len(h.extendedAdHist) { + h.extendedAdLast = 0 + } + case evtTypScanRspToExtendedAdvInd, evtTypScanRspToExtendedAdvScanInd: + sr := &report.Reports[i] + for idx := h.extendedAdLast - 1; idx != h.extendedAdLast; idx-- { + if idx == -1 { + idx = len(h.extendedAdHist) - 1 + } + if h.extendedAdHist[idx] == nil { + break + } + if h.extendedAdHist[idx].Addr().String() == sr.Addr().String() { + h.extendedAdHist[idx].SetScanResp(sr) + a = h.extendedAdHist[idx] + break + } + } + if a == nil { + return fmt.Errorf("received scan response %x with no associated Advertising Data packet", sr.Addr().String()) + } + default: + a = &report.Reports[i] + } + + go h.extendedAdvHandler(a) + } + + return nil +} + func (h *HCI) handleCommandComplete(b []byte) error { e := evt.CommandComplete(b) h.setAllowedCommands(int(e.NumHCICommandPackets())) diff --git a/linux/hci/params.go b/linux/hci/params.go index fceacab8..7ad82c54 100644 --- a/linux/hci/params.go +++ b/linux/hci/params.go @@ -9,15 +9,19 @@ import ( type params struct { sync.RWMutex - advEnable cmd.LESetAdvertiseEnable - scanEnable cmd.LESetScanEnable - connCancel cmd.LECreateConnectionCancel - - advData cmd.LESetAdvertisingData - scanResp cmd.LESetScanResponseData - advParams cmd.LESetAdvertisingParameters - scanParams cmd.LESetScanParameters - connParams cmd.LECreateConnection + advEnable cmd.LESetAdvertiseEnable + scanEnable cmd.LESetScanEnable + extendedScanEnable cmd.LESetExtendedScanEnable + connCancel cmd.LECreateConnectionCancel + + leSetDefaultPHY cmd.LESetDefaultPHY + + advData cmd.LESetAdvertisingData + scanResp cmd.LESetScanResponseData + advParams cmd.LESetAdvertisingParameters + scanParams cmd.LESetScanParameters + extendedScanParams cmd.LESetExtendedScanParameters + connParams cmd.LECreateConnection } func (p *params) init() { @@ -28,6 +32,7 @@ func (p *params) init() { OwnAddressType: 0x00, // 0x00: public, 0x01: random ScanningFilterPolicy: 0x00, // 0x00: accept all, 0x01: ignore non-white-listed. } + p.advParams = cmd.LESetAdvertisingParameters{ AdvertisingIntervalMin: 0x0020, // 0x0020 - 0x4000; N * 0.625 msec AdvertisingIntervalMax: 0x0020, // 0x0020 - 0x4000; N * 0.625 msec @@ -38,6 +43,7 @@ func (p *params) init() { AdvertisingChannelMap: 0x7, // 0x07 0x01: ch37, 0x2: ch38, 0x4: ch39 AdvertisingFilterPolicy: 0x00, } + p.connParams = cmd.LECreateConnection{ LEScanInterval: 0x0004, // 0x0004 - 0x4000; N * 0.625 msec LEScanWindow: 0x0004, // 0x0004 - 0x4000; N * 0.625 msec @@ -53,3 +59,35 @@ func (p *params) init() { MaximumCELength: 0x0000, // 0x0000 - 0xFFFF; N * 0.625 msec } } + +func (p *params) initForAdvertisingExtensions() { + p.extendedScanParams = cmd.LESetExtendedScanParameters{ + OwnAddressType: 0x00, // 0x00: public, 0x01: random + ScanningFilterPolicy: 0x00, // 0x00: accept all, 0x01: ignore non-white-listed + ScanningPHYs: 0x05, // 0x01: Scan on the LE 1M PHY, 0x04: Scan on the LE Coded PHY + ScanType1M: 0x01, // 0x00: passive scan, 0x01: active scan + ScanInterval1M: 0x0064, // 0x0004 - 0x4000; N * 0.625 msec (2.5 ms) + ScanWindow1M: 0x0032, // 0x0004 - 0x4000; N * 0.625 msec (2.5 ms) + ScanTypeCoded: 0x01, // 0x00: passive scan, 0x01: active scan (for Coded PHY) + ScanIntervalCoded: 0x0BB8, // N * 0.625 msec, scan interval for Coded PHY + ScanWindowCoded: 0x0032, // N * 0.625 msec, scan window for Coded PHY + } + + p.extendedScanParams = cmd.LESetExtendedScanParameters{ + OwnAddressType: 0x00, // 0x00: public, 0x01: random + ScanningFilterPolicy: 0x00, // 0x00: accept all, 0x01: ignore non-white-listed + ScanningPHYs: 0x05, // 0x01: Scan on the LE 1M PHY, 0x04: Scan on the LE Coded PHY + ScanType1M: 0x01, // 0x00: passive scan, 0x01: active scan + ScanInterval1M: 0x0064, // 0x0004 - 0x4000; N * 0.625 msec (2.5 ms) + ScanWindow1M: 0x0032, // 0x0004 - 0x4000; N * 0.625 msec (2.5 ms) + ScanTypeCoded: 0x01, // 0x00: passive scan, 0x01: active scan (for Coded PHY) + ScanIntervalCoded: 0x0BB8, // N * 0.625 msec, scan interval for Coded PHY + ScanWindowCoded: 0x0032, // N * 0.625 msec, scan window for Coded PHY + } + + p.leSetDefaultPHY = cmd.LESetDefaultPHY{ + AllPHYs: 0x00, // 0x00: No preference, use all PHYs + TXPHYs: 0x07, // 0x01: LE 1M, 0x02: LE 2M, 0x04: LE Coded (0x07 to use all PHYs) + RXPHYs: 0x07, // 0x01: LE 1M, 0x02: LE 2M, 0x04: LE Coded (uses all PHYs at 0x07) + } +} diff --git a/linux/tools/codegen/Makefile b/linux/tools/codegen/Makefile index 72412d3d..ef8b3470 100644 --- a/linux/tools/codegen/Makefile +++ b/linux/tools/codegen/Makefile @@ -1,9 +1,8 @@ -signal_out="../../l2cap/signal_gen.go" cmd_out="../../hci/cmd/cmd_gen.go" evt_out="../../hci/evt/evt_gen.go" att_out="../../att/att_gen.go" -targets := signal cmd evt att +targets := cmd evt att all: ${targets} diff --git a/linux/tools/codegen/cmd.json b/linux/tools/codegen/cmd.json index 73e4cce2..cd62811b 100644 --- a/linux/tools/codegen/cmd.json +++ b/linux/tools/codegen/cmd.json @@ -1241,6 +1241,98 @@ "Events": [ "Command Complete" ] + }, + { + "Name": "LE Set Extended Scan Parameters", + "Spec": "Vol 4, Part E, 7.8.64", + "OGF": "0x08", + "OCF": "0x0041", + "Len": 13, + "Param": [ + { "Own_Address_Type": "uint8" }, + { "Scanning_Filter_Policy": "uint8" }, + { "Scanning_PHYs": "uint8" }, + { "Scan_Type_1M": "uint8" }, + { "Scan_Interval_1M": "uint16" }, + { "Scan_Window_1M": "uint16" }, + { "Scan_Type_Coded": "uint8" }, + { "Scan_Interval_Coded": "uint16" }, + { "Scan_Window_Coded": "uint16" } + ], + "Return": [ + { "Status": "uint8" } + ], + "Events": [ + "Command Complete" + ] + }, + { + "Name": "LE Set Extended Scan Enable", + "Spec": "Vol 4, Part E, 7.8.65", + "OGF": "0x08", + "OCF": "0x0042", + "Len": 6, + "Param": [ + { "Enable": "uint8" }, + { "Filter_Duplicates": "uint8" }, + { "Duration": "uint16" }, + { "Period": "uint16" } + ], + "Return": [ + { "Status": "uint8" } + ], + "Events": [ + "Command Complete", + "LE Extended Advertising Report", + "LE Scan Timeout" + ] + }, + { + "Name": "LE Set Default PHY", + "Spec": "Vol 4, Part E, 7.8.48", + "OGF": "0x08", + "OCF": "0x0031", + "Len": 3, + "Param": [ + { "All_PHYs": "uint8" }, + { "TX_PHYs": "uint8" }, + { "RX_PHYs": "uint8" } + ], + "Return": [], + "Events": [ + "Command Status", + "LE PHY Update Complete" + ] + }, + { + "Name": "LE Set Extended Advertising Parameters", + "Spec": "Vol 4, Part E, 7.8.53", + "OGF": "0x08", + "OCF": "0x0036", + "Len": 25, + "Param": [ + { "Advertising_Handle": "uint8" }, + { "Advertising_Event_Properties": "uint16" }, + { "Primary_Advertising_Interval_Min": "[3]byte" }, + { "Primary_Advertising_Interval_Max": "[3]byte" }, + { "Primary_Advertising_Channel_Map": "uint8" }, + { "Own_Address_Type": "uint8" }, + { "Peer_Address_Type": "uint8" }, + { "Peer_Address": "[6]byte" }, + { "Advertising_Filter_Policy": "uint8" }, + { "Advertising_TX_Power": "int8" }, + { "Primary_Advertising_PHY": "uint8" }, + { "Secondary_Advertising_Max_Skip": "uint8" }, + { "Secondary_Advertising_PHY": "uint8" }, + { "Advertising_SID": "uint8" }, + { "Scan_Request_Notification_Enable": "uint8" } + ], + "Return": [ + { "Status": "uint8" } + ], + "Events": [ + "Command Complete" + ] } ] } diff --git a/linux/tools/codegen/evt.json b/linux/tools/codegen/evt.json index 5d842e16..b13e9b20 100644 --- a/linux/tools/codegen/evt.json +++ b/linux/tools/codegen/evt.json @@ -322,6 +322,30 @@ } ], "DefaultUnmarshaller": true + }, + { + "Name": "LE Extended Advertising Report", + "Spec": "Vol 4, Part E, 7.7.65.13", + "Code": "0x3E", + "SubCode": "0x0D", + "Param": [ + { "Subevent_Code": "uint8" }, + { "Num_Reports": "uint8" }, + { "Event_Type": "[]uint16" }, + { "Address_Type": "[]uint8" }, + { "Address": "[][6]byte" }, + { "Primary_PHY": "[]uint8" }, + { "Secondary_PHY": "[]uint8" }, + { "Advertising_SID": "[]uint8" }, + { "TX_Power": "[]int8" }, + { "RSSI": "[]int8" }, + { "Periodic_Advertising_Interval": "[]uint16" }, + { "Direct_Address_Type": "[]uint8" }, + { "Direct_Address": "[][6]byte" }, + { "Data_Length": "[]uint8" }, + { "Data": "[][]byte" } + ], + "DefaultUnmarshaller": false } ] }