Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/ninafw-peripheral' into ninafw-c…
Browse files Browse the repository at this point in the history
…ustom-config
  • Loading branch information
bgould committed Jan 15, 2024
2 parents 3fccf08 + 46c28d8 commit b3e3cf8
Show file tree
Hide file tree
Showing 32 changed files with 1,345 additions and 194 deletions.
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ smoketest-tinygo:
@md5sum test.hex
$(TINYGO) build -o test.uf2 -size=short -target=circuitplay-bluefruit ./examples/circuitplay
@md5sum test.hex
$(TINYGO) build -o test.hex -size=short -target=circuitplay-bluefruit ./examples/connparams
@md5sum test.hex
$(TINYGO) build -o test.uf2 -size=short -target=circuitplay-bluefruit ./examples/discover
@md5sum test.hex
$(TINYGO) build -o test.hex -size=short -target=pca10040-s132v6 ./examples/heartrate
Expand All @@ -32,14 +34,19 @@ smoketest-tinygo:
@md5sum test.hex
$(TINYGO) build -o test.hex -size=short -target=microbit-v2-s113v7 ./examples/nusserver
@md5sum test.hex
$(TINYGO) build -o test.uf2 -size=short -target=nano-rp2040 ./examples/scanner
@md5sum test.hex
$(TINYGO) build -o test.uf2 -size=short -target=nano-rp2040 ./examples/discover
@md5sum test.hex
$(TINYGO) build -o test.uf2 -size=short -target=arduino-nano33 ./examples/discover
@md5sum test.hex
$(TINYGO) build -o test.uf2 -size=short -target=pyportal ./examples/discover
@md5sum test.hex
$(TINYGO) build -o test.uf2 -size=short -target=nano-rp2040 ./examples/advertisement
@md5sum test.hex

smoketest-linux:
# Test on Linux.
GOOS=linux go build -o /tmp/go-build-discard ./examples/advertisement
GOOS=linux go build -o /tmp/go-build-discard ./examples/connparams
GOOS=linux go build -o /tmp/go-build-discard ./examples/heartrate
GOOS=linux go build -o /tmp/go-build-discard ./examples/heartrate-monitor
GOOS=linux go build -o /tmp/go-build-discard ./examples/nusserver
Expand Down
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ func must(action string, err error) {
| Connect to peripheral | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Write peripheral characteristics | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Receive notifications | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Advertisement | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :x: |
| Local services | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :x: |
| Local characteristics | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :x: |
| Send notifications | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :x: |
| Advertisement | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: |
| Local services | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: |
| Local characteristics | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: |
| Send notifications | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: |

## Linux

Expand Down Expand Up @@ -268,11 +268,23 @@ Flashing will normally reset the board.

Go Bluetooth has bare metal support for boards that include a separate ESP32 Bluetooth Low Energy radio co-processor. The ESP32 must be running the Arduino or Adafruit `nina_fw` firmware.

See https://github.com/arduino/nina-fw for more information.
Several boards created by Adafruit and Arduino already have the `nina-fw` firmware pre-loaded. This means you can use TinyGo and the Go Bluetooth package without any additional steps required.

The only currently supported board is the Arduino Nano RP2040 Connect.
Currently supported boards include:

More info soon...
* [Adafruit Metro M4 AirLift](https://www.adafruit.com/product/4000)
* [Adafruit PyBadge](https://www.adafruit.com/product/4200) with [AirLift WiFi FeatherWing](https://www.adafruit.com/product/4264)
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Arduino Nano 33 IoT](https://docs.arduino.cc/hardware/nano-33-iot)
* [Arduino Nano RP2040 Connect](https://docs.arduino.cc/hardware/nano-rp2040-connect)

After you have installed TinyGo and the Go Bluetooth package, you should be able to compile/run code for your device.

For example, this command can be used to compile and flash an Arduino Nano RP2040 Connect board with the example we provide that turns it into a BLE peripheral to act like a heart rate monitor:

tinygo flash -target nano-rp2040 ./examples/heartrate

If you want more information about the `nina-fw` firmware, or want to add support for other ESP32-equipped boards, please see https://github.com/arduino/nina-fw

## API stability

Expand Down
2 changes: 1 addition & 1 deletion adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ const debug = false
// SetConnectHandler sets a handler function to be called whenever the adaptor connects
// or disconnects. You must call this before you call adaptor.Connect() for centrals
// or adaptor.Start() for peripherals in order for it to work.
func (a *Adapter) SetConnectHandler(c func(device Address, connected bool)) {
func (a *Adapter) SetConnectHandler(c func(device Device, connected bool)) {
a.connectHandler = c
}
6 changes: 3 additions & 3 deletions adapter_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type Adapter struct {
// used to allow multiple callers to call Connect concurrently.
connectMap sync.Map

connectHandler func(device Address, connected bool)
connectHandler func(device Device, connected bool)
}

// DefaultAdapter is the default adapter on the system.
Expand All @@ -35,7 +35,7 @@ var DefaultAdapter = &Adapter{
pm: cbgo.NewPeripheralManager(nil),
connectMap: sync.Map{},

connectHandler: func(device Address, connected bool) {
connectHandler: func(device Device, connected bool) {
return
},
}
Expand Down Expand Up @@ -106,7 +106,7 @@ func (cmd *centralManagerDelegate) DidDisconnectPeripheral(cmgr cbgo.CentralMana
addr := Address{}
uuid, _ := ParseUUID(id)
addr.UUID = uuid
cmd.a.connectHandler(addr, false)
cmd.a.connectHandler(Device{Address: addr}, false)

// like with DidConnectPeripheral, check if we have a chan allocated for this and send through the peripheral
// this will only be true if the receiving side is still waiting for a connection to complete
Expand Down
4 changes: 2 additions & 2 deletions adapter_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type Adapter struct {
address string
defaultAdvertisement *Advertisement

connectHandler func(device Address, connected bool)
connectHandler func(device Device, connected bool)
}

// DefaultAdapter is the default adapter on the system. On Linux, it is the
Expand All @@ -32,7 +32,7 @@ type Adapter struct {
// Make sure to call Enable() before using it to initialize the adapter.
var DefaultAdapter = &Adapter{
id: defaultAdapter,
connectHandler: func(device Address, connected bool) {
connectHandler: func(device Device, connected bool) {
},
}

Expand Down
52 changes: 37 additions & 15 deletions adapter_ninafw.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ type Adapter struct {
isDefault bool
scanning bool

connectHandler func(device Address, connected bool)
connectHandler func(device Device, connected bool)

connectedDevices []*Device
connectedDevices []Device
notificationsStarted bool
}

Expand All @@ -49,19 +49,18 @@ type Adapter struct {
// Make sure to call Enable() before using it to initialize the adapter.
var DefaultAdapter = &Adapter{
isDefault: true,
connectHandler: func(device Address, connected bool) {
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
// Bluetooth-related calls (unless otherwise indicated).
func (a *Adapter) Enable() error {
// reset the NINA in BLE mode
NINA_CS.Configure(machine.PinConfig{Mode: machine.PinOutput})
NINA_RESETN.Configure(machine.PinConfig{Mode: machine.PinOutput})
NINA_CS.Low()
machine.NINA_CS.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.NINA_CS.Low()

if _debug {
println("tx:", NINA_TX, "rx:", NINA_RX, "baudrate:", NINA_BAUDRATE, "cts:", NINA_CTS, "rts:", NINA_RTS)
Expand All @@ -86,7 +85,29 @@ func (a *Adapter) Enable() error {
resetNINA()
}

// serial port for nina chip
uart := machine.UART_NINA
cfg := machine.UARTConfig{
TX: machine.NINA_TX,
RX: machine.NINA_RX,
BaudRate: machine.NINA_BAUDRATE,
}
if !machine.NINA_SOFT_FLOWCONTROL {
cfg.CTS = machine.NINA_CTS
cfg.RTS = machine.NINA_RTS
}

uart.Configure(cfg)

a.hci, a.att = newBLEStack(uart)
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})
}

if _debug {
println("starting hci")
Expand Down Expand Up @@ -158,17 +179,18 @@ func makeNINAAddress(mac MAC) [6]uint8 {
}

func resetNINA() {
NINA_RESETN.High()
machine.NINA_RESETN.Configure(machine.PinConfig{Mode: machine.PinOutput})

machine.NINA_RESETN.High()
time.Sleep(100 * time.Millisecond)
NINA_RESETN.Low()
time.Sleep(1000 * time.Millisecond)
}

func resetNINAInverted() {
if _debug {
println("resetNINAInverted")
}
NINA_RESETN.Low()
machine.NINA_RESETN.Configure(machine.PinConfig{Mode: machine.PinOutput})

machine.NINA_RESETN.Low()
time.Sleep(100 * time.Millisecond)
NINA_RESETN.High()
time.Sleep(1000 * time.Millisecond)
Expand Down Expand Up @@ -209,7 +231,7 @@ func (a *Adapter) startNotifications() {
}

d := a.findDevice(not.connectionHandle)
if d == nil {
if d.deviceInternal == nil {
if _debug {
println("no device found for handle", not.connectionHandle)
}
Expand All @@ -236,7 +258,7 @@ func (a *Adapter) startNotifications() {
}()
}

func (a *Adapter) findDevice(handle uint16) *Device {
func (a *Adapter) findDevice(handle uint16) Device {
for _, d := range a.connectedDevices {
if d.handle == handle {
if _debug {
Expand All @@ -247,5 +269,5 @@ func (a *Adapter) findDevice(handle uint16) *Device {
}
}

return nil
return Device{}
}
20 changes: 18 additions & 2 deletions adapter_nrf51.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ func handleEvent() {
switch id {
case C.BLE_GAP_EVT_CONNECTED:
currentConnection.handle.Reg = uint16(gapEvent.conn_handle)
DefaultAdapter.connectHandler(Address{}, true)
connectEvent := gapEvent.params.unionfield_connected()
device := Device{
Address: Address{makeMACAddress(connectEvent.peer_addr)},
connectionHandle: gapEvent.conn_handle,
}
DefaultAdapter.connectHandler(device, true)
case C.BLE_GAP_EVT_DISCONNECTED:
if defaultAdvertisement.isAdvertising.Get() != 0 {
// The advertisement was running but was automatically stopped
Expand All @@ -55,7 +60,10 @@ func handleEvent() {
defaultAdvertisement.start()
}
currentConnection.handle.Reg = C.BLE_CONN_HANDLE_INVALID
DefaultAdapter.connectHandler(Address{}, false)
device := Device{
connectionHandle: gapEvent.conn_handle,
}
DefaultAdapter.connectHandler(device, false)
case C.BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
// Respond with the default PPCP connection parameters by passing
// nil:
Expand Down Expand Up @@ -111,3 +119,11 @@ func (a *Adapter) Address() (MACAddress, error) {
}
return MACAddress{MAC: makeAddress(addr.addr)}, nil
}

// Convert a C.ble_gap_addr_t to a MACAddress struct.
func makeMACAddress(addr C.ble_gap_addr_t) MACAddress {
return MACAddress{
MAC: makeAddress(addr.addr),
isRandom: addr.addr_type != 0,
}
}
24 changes: 19 additions & 5 deletions adapter_nrf528xx-full.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,24 @@ func handleEvent() {
switch id {
case C.BLE_GAP_EVT_CONNECTED:
connectEvent := gapEvent.params.unionfield_connected()
device := Device{
Address: Address{makeMACAddress(connectEvent.peer_addr)},
connectionHandle: gapEvent.conn_handle,
}
switch connectEvent.role {
case C.BLE_GAP_ROLE_PERIPH:
if debug {
println("evt: connected in peripheral role")
}
currentConnection.handle.Reg = uint16(gapEvent.conn_handle)
DefaultAdapter.connectHandler(Address{}, true)
DefaultAdapter.connectHandler(device, true)
case C.BLE_GAP_ROLE_CENTRAL:
if debug {
println("evt: connected in central role")
}
connectionAttempt.connectionHandle = gapEvent.conn_handle
connectionAttempt.state.Set(2) // connection was successful
DefaultAdapter.connectHandler(Address{}, true)
DefaultAdapter.connectHandler(device, true)
}
case C.BLE_GAP_EVT_DISCONNECTED:
if debug {
Expand All @@ -61,7 +65,18 @@ func handleEvent() {
// necessary.
C.sd_ble_gap_adv_start(defaultAdvertisement.handle, C.BLE_CONN_CFG_TAG_DEFAULT)
}
DefaultAdapter.connectHandler(Address{}, false)
device := Device{
connectionHandle: gapEvent.conn_handle,
}
DefaultAdapter.connectHandler(device, false)
case C.BLE_GAP_EVT_CONN_PARAM_UPDATE:
if debug {
// Print connection parameters for easy debugging.
params := gapEvent.params.unionfield_conn_param_update().conn_params
interval_ms := params.min_conn_interval * 125 / 100 // min and max are the same here
print("conn param update interval=", interval_ms, "ms latency=", params.slave_latency, " timeout=", params.conn_sup_timeout*10, "ms")
println()
}
case C.BLE_GAP_EVT_ADV_REPORT:
advReport := gapEvent.params.unionfield_adv_report()
if debug && &scanReportBuffer.data[0] != (*byte)(unsafe.Pointer(advReport.data.p_data)) {
Expand All @@ -73,8 +88,7 @@ func handleEvent() {
scanReportBuffer.len = byte(advReport.data.len)
globalScanResult.RSSI = int16(advReport.rssi)
globalScanResult.Address = Address{
MACAddress{MAC: makeAddress(advReport.peer_addr.addr),
isRandom: advReport.peer_addr.bitfield_addr_type() != 0},
makeMACAddress(advReport.peer_addr),
}
globalScanResult.AdvertisementPayload = &scanReportBuffer
// Signal to the main thread that there was a scan report.
Expand Down
12 changes: 10 additions & 2 deletions adapter_nrf528xx-peripheral.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ func handleEvent() {
println("evt: connected in peripheral role")
}
currentConnection.handle.Reg = uint16(gapEvent.conn_handle)
DefaultAdapter.connectHandler(Address{}, true)
connectEvent := gapEvent.params.unionfield_connected()
device := Device{
Address: Address{makeMACAddress(connectEvent.peer_addr)},
connectionHandle: gapEvent.conn_handle,
}
DefaultAdapter.connectHandler(device, true)
case C.BLE_GAP_EVT_DISCONNECTED:
if debug {
println("evt: disconnected")
Expand All @@ -44,7 +49,10 @@ func handleEvent() {
// necessary.
C.sd_ble_gap_adv_start(defaultAdvertisement.handle, C.BLE_CONN_CFG_TAG_DEFAULT)
}
DefaultAdapter.connectHandler(Address{}, false)
device := Device{
connectionHandle: gapEvent.conn_handle,
}
DefaultAdapter.connectHandler(device, false)
case C.BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST:
// We need to respond with sd_ble_gap_data_length_update. Setting
// both parameters to nil will make sure we send the default values.
Expand Down
8 changes: 8 additions & 0 deletions adapter_nrf528xx.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,11 @@ func (a *Adapter) Address() (MACAddress, error) {
}
return MACAddress{MAC: makeAddress(addr.addr)}, nil
}

// Convert a C.ble_gap_addr_t to a MACAddress struct.
func makeMACAddress(addr C.ble_gap_addr_t) MACAddress {
return MACAddress{
MAC: makeAddress(addr.addr),
isRandom: addr.bitfield_addr_type() != 0,
}
}
4 changes: 2 additions & 2 deletions adapter_sd.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ type Adapter struct {
scanning bool
charWriteHandlers []charWriteHandler

connectHandler func(device Address, connected bool)
connectHandler func(device Device, connected bool)
}

// DefaultAdapter is the default adapter on the current system. On Nordic chips,
// it will return the SoftDevice interface.
//
// Make sure to call Enable() before using it to initialize the adapter.
var DefaultAdapter = &Adapter{isDefault: true,
connectHandler: func(device Address, connected bool) {
connectHandler: func(device Device, connected bool) {
return
}}

Expand Down
Loading

0 comments on commit b3e3cf8

Please sign in to comment.