diff --git a/adapter_hci.go b/adapter_hci.go new file mode 100644 index 0000000..c5c5ce3 --- /dev/null +++ b/adapter_hci.go @@ -0,0 +1,176 @@ +//go:build hci || ninafw + +package bluetooth + +import ( + "machine" + "runtime" + + "time" +) + +// hciAdapter represents the implementation for the UART connection to the HCI controller. +type hciAdapter struct { + uart *machine.UART + hci *hci + att *att + + isDefault bool + scanning bool + + connectHandler func(device Device, connected bool) + + connectedDevices []Device + notificationsStarted bool +} + +func (a *hciAdapter) enable() error { + a.hci.start() + + if err := a.hci.reset(); err != nil { + return err + } + + time.Sleep(150 * time.Millisecond) + + if err := a.hci.setEventMask(0x3FFFFFFFFFFFFFFF); err != nil { + return err + } + + return a.hci.setLeEventMask(0x00000000000003FF) +} + +func (a *hciAdapter) Address() (MACAddress, error) { + if err := a.hci.readBdAddr(); err != nil { + return MACAddress{}, err + } + + return MACAddress{MAC: makeAddress(a.hci.address)}, nil +} + +func newBLEStack(uart *machine.UART) (*hci, *att) { + h := newHCI(uart) + a := newATT(h) + h.att = a + + l := newL2CAP(h) + h.l2cap = l + + return h, a +} + +// Convert a NINA MAC address into a Go MAC address. +func makeAddress(mac [6]uint8) MAC { + return MAC{ + uint8(mac[0]), + uint8(mac[1]), + uint8(mac[2]), + uint8(mac[3]), + uint8(mac[4]), + uint8(mac[5]), + } +} + +// Convert a Go MAC address into a NINA MAC Address. +func makeNINAAddress(mac MAC) [6]uint8 { + return [6]uint8{ + uint8(mac[0]), + uint8(mac[1]), + uint8(mac[2]), + uint8(mac[3]), + uint8(mac[4]), + uint8(mac[5]), + } +} + +func (a *hciAdapter) startNotifications() { + if a.notificationsStarted { + return + } + + if debug { + println("starting notifications...") + } + + a.notificationsStarted = true + + // go routine to poll for HCI events for ATT notifications + go func() { + for { + if err := a.att.poll(); err != nil { + // TODO: handle error + if debug { + println("error polling for notifications:", err.Error()) + } + } + + time.Sleep(5 * time.Millisecond) + } + }() + + // go routine to handle characteristic notifications + go func() { + for { + select { + case not := <-a.att.notifications: + if debug { + println("notification received", not.connectionHandle, not.handle, not.data) + } + + d := a.findConnection(not.connectionHandle) + if d.deviceInternal == nil { + if debug { + println("no device found for handle", not.connectionHandle) + } + continue + } + + n := d.findNotificationRegistration(not.handle) + if n == nil { + if debug { + println("no notification registered for handle", not.handle) + } + continue + } + + if n.callback != nil { + n.callback(not.data) + } + + default: + } + + runtime.Gosched() + } + }() +} + +func (a *hciAdapter) addConnection(d Device) { + a.connectedDevices = append(a.connectedDevices, d) +} + +func (a *hciAdapter) removeConnection(d Device) { + for i := range a.connectedDevices { + if d.handle == a.connectedDevices[i].handle { + a.connectedDevices[i] = a.connectedDevices[len(a.connectedDevices)-1] + a.connectedDevices[len(a.connectedDevices)-1] = Device{} + a.connectedDevices = a.connectedDevices[:len(a.connectedDevices)-1] + + return + } + } +} + +func (a *hciAdapter) findConnection(handle uint16) Device { + for _, d := range a.connectedDevices { + if d.handle == handle { + if debug { + println("found device", handle, d.Address.String(), "with notifications registered", len(d.notificationRegistrations)) + } + + return d + } + } + + return Device{} +} diff --git a/adapter_hci_uart.go b/adapter_hci_uart.go new file mode 100644 index 0000000..ec0eb66 --- /dev/null +++ b/adapter_hci_uart.go @@ -0,0 +1,64 @@ +//go:build hci && hci_uart + +package bluetooth + +import ( + "machine" +) + +const maxConnections = 1 + +// Adapter represents the UART connection to the HCI controller. +type Adapter struct { + hciAdapter + + // used for software flow control + cts, rts machine.Pin +} + +// DefaultAdapter is the default adapter on the current system. +// +// Make sure to call Enable() before using it to initialize the adapter. +var DefaultAdapter = &Adapter{ + isDefault: true, + connectHandler: func(device Device, connected bool) { + return + }, + connectedDevices: make([]Device, 0, maxConnections), +} + +// SetUART sets the UART to use for the HCI connection. +// It must be called before calling Enable(). +// Note that the UART must be configured with hardware flow control, or +// SetSoftwareFlowControl() must be called. +func (a *Adapter) SetUART(uart *machine.UART) error { + a.uart = uart + + return nil +} + +// SetSoftwareFlowControl sets the pins to use for software flow control, +// if hardware flow control is not available. +func (a *Adapter) SetSoftwareFlowControl(cts, rts machine.Pin) error { + a.cts = cts + a.rts = rts + + return nil +} + +// Enable configures the BLE stack. It must be called before any +// Bluetooth-related calls (unless otherwise indicated). +func (a *Adapter) Enable() error { + a.hci, a.att = newBLEStack(a.uart) + + if a.cts != 0 && a.rts != 0 { + a.hci.softRTS = a.rts + a.hci.softRTS.Configure(machine.PinConfig{Mode: machine.PinOutput}) + a.hci.softRTS.High() + + a.hci.softCTS = a.cts + a.cts.Configure(machine.PinConfig{Mode: machine.PinInput}) + } + + a.enable() +} diff --git a/adapter_ninafw.go b/adapter_ninafw.go index 739b8c8..2d06b21 100644 --- a/adapter_ninafw.go +++ b/adapter_ninafw.go @@ -4,36 +4,27 @@ package bluetooth import ( "machine" - "runtime" - "time" ) const maxConnections = 1 -// Adapter represents the UART connection to the NINA fw. +// Adapter represents the HCI connection to the NINA fw. type Adapter struct { - hci *hci - att *att - - isDefault bool - scanning bool - - connectHandler func(device Device, connected bool) - - connectedDevices []Device - notificationsStarted bool + hciAdapter } // DefaultAdapter is the default adapter on the current system. // // Make sure to call Enable() before using it to initialize the adapter. var DefaultAdapter = &Adapter{ - isDefault: true, - connectHandler: func(device Device, connected bool) { - return + hciAdapter: hciAdapter{ + isDefault: true, + connectHandler: func(device Device, connected bool) { + return + }, + connectedDevices: make([]Device, 0, maxConnections), }, - connectedDevices: make([]Device, 0, maxConnections), } // Enable configures the BLE stack. It must be called before any @@ -73,66 +64,7 @@ func (a *Adapter) Enable() error { machine.NINA_CTS.Configure(machine.PinConfig{Mode: machine.PinInput}) } - a.hci.start() - - if err := a.hci.reset(); err != nil { - return err - } - - time.Sleep(150 * time.Millisecond) - - if err := a.hci.setEventMask(0x3FFFFFFFFFFFFFFF); err != nil { - return err - } - - if err := a.hci.setLeEventMask(0x00000000000003FF); err != nil { - return err - } - - return nil -} - -func (a *Adapter) Address() (MACAddress, error) { - if err := a.hci.readBdAddr(); err != nil { - return MACAddress{}, err - } - - return MACAddress{MAC: makeAddress(a.hci.address)}, nil -} - -func newBLEStack(uart *machine.UART) (*hci, *att) { - h := newHCI(uart) - a := newATT(h) - h.att = a - - l := newL2CAP(h) - h.l2cap = l - - return h, a -} - -// Convert a NINA MAC address into a Go MAC address. -func makeAddress(mac [6]uint8) MAC { - return MAC{ - uint8(mac[0]), - uint8(mac[1]), - uint8(mac[2]), - uint8(mac[3]), - uint8(mac[4]), - uint8(mac[5]), - } -} - -// Convert a Go MAC address into a NINA MAC Address. -func makeNINAAddress(mac MAC) [6]uint8 { - return [6]uint8{ - uint8(mac[0]), - uint8(mac[1]), - uint8(mac[2]), - uint8(mac[3]), - uint8(mac[4]), - uint8(mac[5]), - } + return a.enable() } func resetNINA() { @@ -152,95 +84,3 @@ func resetNINAInverted() { machine.NINA_RESETN.High() time.Sleep(1000 * time.Millisecond) } - -func (a *Adapter) startNotifications() { - if a.notificationsStarted { - return - } - - if debug { - println("starting notifications...") - } - - a.notificationsStarted = true - - // go routine to poll for HCI events for ATT notifications - go func() { - for { - if err := a.att.poll(); err != nil { - // TODO: handle error - if debug { - println("error polling for notifications:", err.Error()) - } - } - - time.Sleep(5 * time.Millisecond) - } - }() - - // go routine to handle characteristic notifications - go func() { - for { - select { - case not := <-a.att.notifications: - if debug { - println("notification received", not.connectionHandle, not.handle, not.data) - } - - d := a.findConnection(not.connectionHandle) - if d.deviceInternal == nil { - if debug { - println("no device found for handle", not.connectionHandle) - } - continue - } - - n := d.findNotificationRegistration(not.handle) - if n == nil { - if debug { - println("no notification registered for handle", not.handle) - } - continue - } - - if n.callback != nil { - n.callback(not.data) - } - - default: - } - - runtime.Gosched() - } - }() -} - -func (a *Adapter) addConnection(d Device) { - a.connectedDevices = append(a.connectedDevices, d) -} - -func (a *Adapter) removeConnection(d Device) { - for i := range a.connectedDevices { - if d.handle == a.connectedDevices[i].handle { - a.connectedDevices[i] = a.connectedDevices[len(a.connectedDevices)-1] - a.connectedDevices[len(a.connectedDevices)-1] = Device{} - a.connectedDevices = a.connectedDevices[:len(a.connectedDevices)-1] - - return - } - } -} - -func (a *Adapter) findConnection(handle uint16) Device { - for _, d := range a.connectedDevices { - if d.handle == handle { - if debug { - println("found device", handle, d.Address.String(), "with notifications registered", len(d.notificationRegistrations)) - } - - return d - } - } - - return Device{} -} diff --git a/att_ninafw.go b/att_hci.go similarity index 99% rename from att_ninafw.go rename to att_hci.go index ef34228..bed3d25 100644 --- a/att_ninafw.go +++ b/att_hci.go @@ -1,4 +1,4 @@ -//go:build ninafw +//go:build hci || ninafw package bluetooth diff --git a/gap_ninafw.go b/gap_hci.go similarity index 99% rename from gap_ninafw.go rename to gap_hci.go index 06f82bc..8364bfb 100644 --- a/gap_ninafw.go +++ b/gap_hci.go @@ -1,4 +1,4 @@ -//go:build ninafw +//go:build hci || ninafw package bluetooth diff --git a/gattc_ninafw.go b/gattc_hci.go similarity index 99% rename from gattc_ninafw.go rename to gattc_hci.go index ec42e59..48affc5 100644 --- a/gattc_ninafw.go +++ b/gattc_hci.go @@ -1,4 +1,4 @@ -//go:build ninafw +//go:build hci || ninafw package bluetooth diff --git a/gatts_ninafw.go b/gatts_hci.go similarity index 99% rename from gatts_ninafw.go rename to gatts_hci.go index 3bf454a..6f8935a 100644 --- a/gatts_ninafw.go +++ b/gatts_hci.go @@ -1,4 +1,4 @@ -//go:build ninafw +//go:build hci || ninafw package bluetooth diff --git a/hci_ninafw.go b/hci.go similarity index 100% rename from hci_ninafw.go rename to hci.go diff --git a/uuid_ninafw.go b/uuid_hci.go similarity index 93% rename from uuid_ninafw.go rename to uuid_hci.go index 107e11f..9c3369d 100644 --- a/uuid_ninafw.go +++ b/uuid_hci.go @@ -1,4 +1,4 @@ -//go:build ninafw +//go:build hci || ninafw package bluetooth