2023-12-21 01:29:23 +03:00
|
|
|
//go:build ninafw
|
|
|
|
|
|
|
|
package bluetooth
|
|
|
|
|
|
|
|
import (
|
|
|
|
"machine"
|
|
|
|
"runtime"
|
|
|
|
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const maxConnections = 1
|
|
|
|
|
|
|
|
// Adapter represents the UART connection to the NINA fw.
|
|
|
|
type Adapter struct {
|
|
|
|
hci *hci
|
|
|
|
att *att
|
|
|
|
|
|
|
|
isDefault bool
|
|
|
|
scanning bool
|
|
|
|
|
2023-12-25 16:52:10 +03:00
|
|
|
connectHandler func(device Device, connected bool)
|
2023-12-21 01:29:23 +03:00
|
|
|
|
2023-12-25 16:28:56 +03:00
|
|
|
connectedDevices []Device
|
2023-12-21 01:29:23 +03:00
|
|
|
notificationsStarted bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
2023-12-25 16:52:10 +03:00
|
|
|
connectHandler: func(device Device, connected bool) {
|
2023-12-21 01:29:23 +03:00
|
|
|
return
|
|
|
|
},
|
2023-12-25 16:28:56 +03:00
|
|
|
connectedDevices: make([]Device, 0, maxConnections),
|
2023-12-21 01:29:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Enable configures the BLE stack. It must be called before any
|
|
|
|
// Bluetooth-related calls (unless otherwise indicated).
|
|
|
|
func (a *Adapter) Enable() error {
|
|
|
|
// reset the NINA in BLE mode
|
|
|
|
machine.NINA_CS.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
|
|
machine.NINA_CS.Low()
|
2024-01-05 19:16:14 +03:00
|
|
|
|
|
|
|
if machine.NINA_RESET_INVERTED {
|
|
|
|
resetNINAInverted()
|
|
|
|
} else {
|
|
|
|
resetNINA()
|
|
|
|
}
|
2023-12-21 01:29:23 +03:00
|
|
|
|
|
|
|
// serial port for nina chip
|
2024-01-06 21:07:18 +03:00
|
|
|
uart := machine.UART_NINA
|
|
|
|
cfg := machine.UARTConfig{
|
2023-12-21 01:29:23 +03:00
|
|
|
TX: machine.NINA_TX,
|
|
|
|
RX: machine.NINA_RX,
|
2024-01-05 19:16:14 +03:00
|
|
|
BaudRate: machine.NINA_BAUDRATE,
|
2024-01-06 21:07:18 +03:00
|
|
|
}
|
|
|
|
if !machine.NINA_SOFT_FLOWCONTROL {
|
|
|
|
cfg.CTS = machine.NINA_CTS
|
|
|
|
cfg.RTS = machine.NINA_RTS
|
|
|
|
}
|
|
|
|
|
|
|
|
uart.Configure(cfg)
|
2023-12-21 01:29:23 +03:00
|
|
|
|
|
|
|
a.hci, a.att = newBLEStack(uart)
|
2024-01-06 21:07:18 +03:00
|
|
|
if machine.NINA_SOFT_FLOWCONTROL {
|
|
|
|
a.hci.softRTS = machine.NINA_RTS
|
|
|
|
a.hci.softRTS.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
|
|
a.hci.softRTS.High()
|
|
|
|
|
|
|
|
a.hci.softCTS = machine.NINA_CTS
|
|
|
|
machine.NINA_CTS.Configure(machine.PinConfig{Mode: machine.PinInput})
|
|
|
|
}
|
2023-12-21 01:29:23 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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 resetNINA() {
|
2024-01-06 21:07:18 +03:00
|
|
|
machine.NINA_RESETN.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
|
|
|
2023-12-21 01:29:23 +03:00
|
|
|
machine.NINA_RESETN.High()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
machine.NINA_RESETN.Low()
|
|
|
|
time.Sleep(1000 * time.Millisecond)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resetNINAInverted() {
|
2024-01-06 21:07:18 +03:00
|
|
|
machine.NINA_RESETN.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
|
|
|
2023-12-21 01:29:23 +03:00
|
|
|
machine.NINA_RESETN.Low()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
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(250 * 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.findDevice(not.connectionHandle)
|
2023-12-25 16:28:56 +03:00
|
|
|
if d.deviceInternal == nil {
|
2023-12-21 01:29:23 +03:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2023-12-25 16:28:56 +03:00
|
|
|
func (a *Adapter) findDevice(handle uint16) Device {
|
2023-12-21 01:29:23 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-25 16:28:56 +03:00
|
|
|
return Device{}
|
2023-12-21 01:29:23 +03:00
|
|
|
}
|