nrf: add GATT client
This is not entirely complete (some errors are not handled properly) but it's a start.
This commit is contained in:
parent
8129f7e092
commit
15b3e8e3e2
12 changed files with 742 additions and 11 deletions
2
Makefile
2
Makefile
|
@ -11,6 +11,8 @@ smoketest-tinygo:
|
||||||
@md5sum test.hex
|
@md5sum test.hex
|
||||||
$(TINYGO) build -o test.hex -size=short -target=reelboard-s140v7 ./examples/ledcolor
|
$(TINYGO) build -o test.hex -size=short -target=reelboard-s140v7 ./examples/ledcolor
|
||||||
@md5sum test.hex
|
@md5sum test.hex
|
||||||
|
$(TINYGO) build -o test.hex -size=short -target=pca10040-s132v6 ./examples/nusclient
|
||||||
|
@md5sum test.hex
|
||||||
$(TINYGO) build -o test.hex -size=short -target=pca10040-s132v6 ./examples/nusserver
|
$(TINYGO) build -o test.hex -size=short -target=pca10040-s132v6 ./examples/nusserver
|
||||||
@md5sum test.hex
|
@md5sum test.hex
|
||||||
$(TINYGO) build -o test.hex -size=short -target=pca10040-s132v6 ./examples/scanner
|
$(TINYGO) build -o test.hex -size=short -target=pca10040-s132v6 ./examples/scanner
|
||||||
|
|
19
README.md
19
README.md
|
@ -5,14 +5,17 @@
|
||||||
|
|
||||||
This package attempts to build a cross-platform Bluetooth Low Energy module for Go. It currently supports the following systems:
|
This package attempts to build a cross-platform Bluetooth Low Energy module for Go. It currently supports the following systems:
|
||||||
|
|
||||||
| | Windows | Linux | Nordic chips |
|
| | Windows | Linux | Nordic chips |
|
||||||
| --------------------- | ------------------ | ------------------ | ------------------ |
|
| -------------------------------- | ------------------ | ------------------ | ------------------ |
|
||||||
| API used | WinRT | BlueZ (over D-Bus) | SoftDevice |
|
| API used | WinRT | BlueZ (over D-Bus) | SoftDevice |
|
||||||
| Scanning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
| Scanning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| Advertisement | :x: | :heavy_check_mark: | :heavy_check_mark: |
|
| Connect to peripheral | :x: | :x: | :heavy_check_mark: |
|
||||||
| Local services | :x: | :heavy_check_mark: | :heavy_check_mark: |
|
| Write peripheral characteristics | :x: | :x: | :heavy_check_mark: |
|
||||||
| Local characteristics | :x: | :heavy_check_mark: | :heavy_check_mark: |
|
| Receive notifications | :x: | :x: | :heavy_check_mark: |
|
||||||
| Send notifications | :x: | :heavy_check_mark: | :heavy_check_mark: |
|
| Advertisement | :x: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
|
| Local services | :x: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
|
| Local characteristics | :x: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
|
| Send notifications | :x: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
|
|
||||||
## Baremetal support
|
## Baremetal support
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ package bluetooth
|
||||||
#define SVCALL_AS_NORMAL_FUNCTION
|
#define SVCALL_AS_NORMAL_FUNCTION
|
||||||
|
|
||||||
#include "nrf_sdm.h"
|
#include "nrf_sdm.h"
|
||||||
|
#include "nrf_nvic.h"
|
||||||
#include "ble.h"
|
#include "ble.h"
|
||||||
#include "ble_gap.h"
|
#include "ble_gap.h"
|
||||||
|
|
||||||
|
@ -49,8 +50,32 @@ func handleEvent() {
|
||||||
gapEvent := eventBuf.evt.unionfield_gap_evt()
|
gapEvent := eventBuf.evt.unionfield_gap_evt()
|
||||||
switch id {
|
switch id {
|
||||||
case C.BLE_GAP_EVT_CONNECTED:
|
case C.BLE_GAP_EVT_CONNECTED:
|
||||||
currentConnection.Reg = gapEvent.conn_handle
|
connectEvent := gapEvent.params.unionfield_connected()
|
||||||
|
switch connectEvent.role {
|
||||||
|
case C.BLE_GAP_ROLE_PERIPH:
|
||||||
|
if debug {
|
||||||
|
println("evt: connected in peripheral role")
|
||||||
|
}
|
||||||
|
currentConnection.Reg = gapEvent.conn_handle
|
||||||
|
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
|
||||||
|
}
|
||||||
case C.BLE_GAP_EVT_DISCONNECTED:
|
case C.BLE_GAP_EVT_DISCONNECTED:
|
||||||
|
if debug {
|
||||||
|
println("evt: disconnected")
|
||||||
|
}
|
||||||
|
// Clean up state for this connection.
|
||||||
|
for i, cb := range gattcNotificationCallbacks {
|
||||||
|
if cb.connectionHandle == currentConnection.Reg {
|
||||||
|
gattcNotificationCallbacks[i].valueHandle = 0 // 0 means invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentConnection.Reg = C.BLE_CONN_HANDLE_INVALID
|
||||||
|
// Auto-restart advertisement if needed.
|
||||||
if defaultAdvertisement.isAdvertising.Get() != 0 {
|
if defaultAdvertisement.isAdvertising.Get() != 0 {
|
||||||
// The advertisement was running but was automatically stopped
|
// The advertisement was running but was automatically stopped
|
||||||
// by the connection event.
|
// by the connection event.
|
||||||
|
@ -60,7 +85,6 @@ func handleEvent() {
|
||||||
// necessary.
|
// necessary.
|
||||||
C.sd_ble_gap_adv_start(defaultAdvertisement.handle, C.BLE_CONN_CFG_TAG_DEFAULT)
|
C.sd_ble_gap_adv_start(defaultAdvertisement.handle, C.BLE_CONN_CFG_TAG_DEFAULT)
|
||||||
}
|
}
|
||||||
currentConnection.Reg = C.BLE_CONN_HANDLE_INVALID
|
|
||||||
case C.BLE_GAP_EVT_ADV_REPORT:
|
case C.BLE_GAP_EVT_ADV_REPORT:
|
||||||
advReport := gapEvent.params.unionfield_adv_report()
|
advReport := gapEvent.params.unionfield_adv_report()
|
||||||
if debug && &scanReportBuffer.data[0] != advReport.data.p_data {
|
if debug && &scanReportBuffer.data[0] != advReport.data.p_data {
|
||||||
|
@ -126,6 +150,84 @@ func handleEvent() {
|
||||||
println("unknown GATTS event:", id, id-C.BLE_GATTS_EVT_BASE)
|
println("unknown GATTS event:", id, id-C.BLE_GATTS_EVT_BASE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case id >= C.BLE_GATTC_EVT_BASE && id <= C.BLE_GATTC_EVT_LAST:
|
||||||
|
gattcEvent := eventBuf.evt.unionfield_gattc_evt()
|
||||||
|
switch id {
|
||||||
|
case C.BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP:
|
||||||
|
discoveryEvent := gattcEvent.params.unionfield_prim_srvc_disc_rsp()
|
||||||
|
if debug {
|
||||||
|
println("evt: discovered primary service", discoveryEvent.count)
|
||||||
|
}
|
||||||
|
discoveringService.state.Set(2) // signal there is a result
|
||||||
|
if discoveryEvent.count >= 1 {
|
||||||
|
// Theoretically there may be more, but as we're only using
|
||||||
|
// sd_ble_gattc_primary_services_discover, there should only be
|
||||||
|
// one discovered service. Use the first as a sensible fallback.
|
||||||
|
discoveringService.startHandle.Set(discoveryEvent.services[0].handle_range.start_handle)
|
||||||
|
discoveringService.endHandle.Set(discoveryEvent.services[0].handle_range.end_handle)
|
||||||
|
} else {
|
||||||
|
// No service found.
|
||||||
|
discoveringService.startHandle.Set(0)
|
||||||
|
}
|
||||||
|
case C.BLE_GATTC_EVT_CHAR_DISC_RSP:
|
||||||
|
discoveryEvent := gattcEvent.params.unionfield_char_disc_rsp()
|
||||||
|
if debug {
|
||||||
|
println("evt: discovered characteristics", discoveryEvent.count)
|
||||||
|
}
|
||||||
|
if discoveryEvent.count >= 1 {
|
||||||
|
// There may be more, but for ease of implementing we only
|
||||||
|
// handle the first.
|
||||||
|
discoveringCharacteristic.handle_value.Set(discoveryEvent.chars[0].handle_value)
|
||||||
|
discoveringCharacteristic.char_props = discoveryEvent.chars[0].char_props
|
||||||
|
discoveringCharacteristic.uuid = discoveryEvent.chars[0].uuid
|
||||||
|
}
|
||||||
|
case C.BLE_GATTC_EVT_DESC_DISC_RSP:
|
||||||
|
discoveryEvent := gattcEvent.params.unionfield_desc_disc_rsp()
|
||||||
|
if debug {
|
||||||
|
println("evt: discovered descriptors", discoveryEvent.count)
|
||||||
|
}
|
||||||
|
if discoveryEvent.count >= 1 {
|
||||||
|
// There may be more, but for ease of implementing we only
|
||||||
|
// handle the first.
|
||||||
|
uuid := discoveryEvent.descs[0].uuid
|
||||||
|
if uuid._type == C.BLE_UUID_TYPE_BLE && uuid.uuid == 0x2902 {
|
||||||
|
// Found a CCCD (Client Characteristic Configuration
|
||||||
|
// Descriptor), which has a 16-bit UUID with value 0x2902).
|
||||||
|
discoveringCharacteristic.handle_value.Set(discoveryEvent.descs[0].handle)
|
||||||
|
} else {
|
||||||
|
// Found something else?
|
||||||
|
// TODO: handle this properly by continuing the scan. For
|
||||||
|
// now, give up if we found something other than a CCCD.
|
||||||
|
if debug {
|
||||||
|
println(" found some other descriptor (unimplemented)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case C.BLE_GATTC_EVT_HVX:
|
||||||
|
hvxEvent := gattcEvent.params.unionfield_hvx()
|
||||||
|
switch hvxEvent._type {
|
||||||
|
case C.BLE_GATT_HVX_NOTIFICATION:
|
||||||
|
if debug {
|
||||||
|
println("evt: notification", hvxEvent.handle)
|
||||||
|
}
|
||||||
|
// Find the callback and call it (if there is any).
|
||||||
|
for _, callbackInfo := range gattcNotificationCallbacks {
|
||||||
|
if callbackInfo.valueHandle == hvxEvent.handle && callbackInfo.connectionHandle == gattcEvent.conn_handle {
|
||||||
|
// Create a Go slice from the data, to pass to the
|
||||||
|
// callback.
|
||||||
|
data := (*[255]byte)(unsafe.Pointer(&hvxEvent.data[0]))[:hvxEvent.len:hvxEvent.len]
|
||||||
|
if callbackInfo.callback != nil {
|
||||||
|
callbackInfo.callback(data)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if debug {
|
||||||
|
println("unknown GATTC event:", id, id-C.BLE_GATTC_EVT_BASE)
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if debug {
|
if debug {
|
||||||
println("unknown event:", id)
|
println("unknown event:", id)
|
||||||
|
|
|
@ -7,4 +7,5 @@
|
||||||
#define static
|
#define static
|
||||||
|
|
||||||
#include "s110_nrf51_8.0.0/s110_nrf51_8.0.0_API/include/nrf_sdm.h"
|
#include "s110_nrf51_8.0.0/s110_nrf51_8.0.0_API/include/nrf_sdm.h"
|
||||||
|
#include "s110_nrf51_8.0.0/s110_nrf51_8.0.0_API/include/nrf_soc.h"
|
||||||
#include "s110_nrf51_8.0.0/s110_nrf51_8.0.0_API/include/ble.h"
|
#include "s110_nrf51_8.0.0/s110_nrf51_8.0.0_API/include/ble.h"
|
||||||
|
|
|
@ -6,5 +6,17 @@
|
||||||
// Discard all 'static' attributes to define functions normally.
|
// Discard all 'static' attributes to define functions normally.
|
||||||
#define static
|
#define static
|
||||||
|
|
||||||
|
// Get rid of all __STATIC_INLINE symbols.
|
||||||
|
// This is a bit less straightforward: we first need to include the header that
|
||||||
|
// defines it, and then redefine it.
|
||||||
|
#include "nrf.h"
|
||||||
|
#undef __STATIC_INLINE
|
||||||
|
#define __STATIC_INLINE
|
||||||
|
|
||||||
#include "s132_nrf52_6.1.1/s132_nrf52_6.1.1_API/include/nrf_sdm.h"
|
#include "s132_nrf52_6.1.1/s132_nrf52_6.1.1_API/include/nrf_sdm.h"
|
||||||
|
#include "s132_nrf52_6.1.1/s132_nrf52_6.1.1_API/include/nrf_nvic.h"
|
||||||
#include "s132_nrf52_6.1.1/s132_nrf52_6.1.1_API/include/ble.h"
|
#include "s132_nrf52_6.1.1/s132_nrf52_6.1.1_API/include/ble.h"
|
||||||
|
|
||||||
|
// Define nrf_nvic_state, which is used by sd_nvic_critical_region_enter and
|
||||||
|
// sd_nvic_critical_region_exit.
|
||||||
|
nrf_nvic_state_t nrf_nvic_state = {0};
|
||||||
|
|
|
@ -6,5 +6,17 @@
|
||||||
// Discard all 'static' attributes to define functions normally.
|
// Discard all 'static' attributes to define functions normally.
|
||||||
#define static
|
#define static
|
||||||
|
|
||||||
|
// Get rid of all __STATIC_INLINE symbols.
|
||||||
|
// This is a bit less straightforward: we first need to include the header that
|
||||||
|
// defines it, and then redefine it.
|
||||||
|
#include "nrf.h"
|
||||||
|
#undef __STATIC_INLINE
|
||||||
|
#define __STATIC_INLINE
|
||||||
|
|
||||||
#include "s140_nrf52_7.0.1/s140_nrf52_7.0.1_API/include/nrf_sdm.h"
|
#include "s140_nrf52_7.0.1/s140_nrf52_7.0.1_API/include/nrf_sdm.h"
|
||||||
|
#include "s140_nrf52_7.0.1/s140_nrf52_7.0.1_API/include/nrf_nvic.h"
|
||||||
#include "s140_nrf52_7.0.1/s140_nrf52_7.0.1_API/include/ble.h"
|
#include "s140_nrf52_7.0.1/s140_nrf52_7.0.1_API/include/ble.h"
|
||||||
|
|
||||||
|
// Define nrf_nvic_state, which is used by sd_nvic_critical_region_enter and
|
||||||
|
// sd_nvic_critical_region_exit.
|
||||||
|
nrf_nvic_state_t nrf_nvic_state = {0};
|
||||||
|
|
|
@ -96,3 +96,19 @@ func (a *Adapter) Enable() error {
|
||||||
errCode = C.sd_ble_gap_ppcp_set(&gapConnParams)
|
errCode = C.sd_ble_gap_ppcp_set(&gapConnParams)
|
||||||
return makeError(errCode)
|
return makeError(errCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisableInterrupts must be used instead of disabling interrupts directly, to
|
||||||
|
// play well with the SoftDevice. Restore interrupts to the previous state with
|
||||||
|
// RestoreInterrupts.
|
||||||
|
func DisableInterrupts() uintptr {
|
||||||
|
var is_nested_critical_region uint8
|
||||||
|
C.sd_nvic_critical_region_enter(&is_nested_critical_region)
|
||||||
|
return uintptr(is_nested_critical_region)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreInterrupts restores interrupts to the state before calling
|
||||||
|
// DisableInterrupts. The mask parameter must be the value returned by
|
||||||
|
// DisableInterrupts.
|
||||||
|
func RestoreInterrupts(mask uintptr) {
|
||||||
|
C.sd_nvic_critical_region_exit(uint8(mask))
|
||||||
|
}
|
||||||
|
|
129
examples/nusclient/main.go
Normal file
129
examples/nusclient/main.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// This example implements a NUS (Nordic UART Service) client. See nusserver for
|
||||||
|
// details.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tinygo-org/bluetooth"
|
||||||
|
"github.com/tinygo-org/bluetooth/rawterm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
serviceUUID = bluetooth.NewUUID([16]byte{0x6E, 0x40, 0x00, 0x01, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E})
|
||||||
|
rxUUID = bluetooth.NewUUID([16]byte{0x6E, 0x40, 0x00, 0x02, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E})
|
||||||
|
txUUID = bluetooth.NewUUID([16]byte{0x6E, 0x40, 0x00, 0x03, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E})
|
||||||
|
)
|
||||||
|
|
||||||
|
var adapter = bluetooth.DefaultAdapter
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Enable BLE interface.
|
||||||
|
err := adapter.Enable()
|
||||||
|
if err != nil {
|
||||||
|
println("could not enable the BLE stack:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The address to connect to. Set during scanning and read afterwards.
|
||||||
|
var foundDevice bluetooth.ScanResult
|
||||||
|
|
||||||
|
// Scan for NUS peripheral.
|
||||||
|
println("Scanning...")
|
||||||
|
err = adapter.Scan(func(adapter *bluetooth.Adapter, result bluetooth.ScanResult) {
|
||||||
|
if !result.AdvertisementPayload.HasServiceUUID(serviceUUID) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
foundDevice = result
|
||||||
|
|
||||||
|
// Stop the scan.
|
||||||
|
err := adapter.StopScan()
|
||||||
|
if err != nil {
|
||||||
|
// Unlikely, but we can't recover from this.
|
||||||
|
println("failed to stop the scan:", err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
println("could not start a scan:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found a device: print this event.
|
||||||
|
if name := foundDevice.LocalName(); name == "" {
|
||||||
|
print("Connecting to ", foundDevice.Address.String(), "...")
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
print("Connecting to ", name, " (", foundDevice.Address.String(), ")...")
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found a NUS peripheral. Connect to it.
|
||||||
|
device, err := adapter.Connect(foundDevice.Address, bluetooth.ConnectionParams{})
|
||||||
|
if err != nil {
|
||||||
|
println("Failed to connect:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connected. Look up the Nordic UART Service.
|
||||||
|
println("Discovering service...")
|
||||||
|
services, err := device.DiscoverServices([]bluetooth.UUID{serviceUUID})
|
||||||
|
if err != nil {
|
||||||
|
println("Failed to discover the Nordic UART Service:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service := services[0]
|
||||||
|
|
||||||
|
// Get the two characteristics present in this service.
|
||||||
|
chars, err := service.DiscoverCharacteristics([]bluetooth.UUID{rxUUID, txUUID})
|
||||||
|
if err != nil {
|
||||||
|
println("Failed to discover RX and TX characteristics:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rx := chars[0]
|
||||||
|
tx := chars[1]
|
||||||
|
|
||||||
|
// Enable notifications to receive incoming data.
|
||||||
|
err = tx.EnableNotifications(func(value []byte) {
|
||||||
|
for _, c := range value {
|
||||||
|
rawterm.Putchar(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
println("Failed to enable TX notifications:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Connected. Exit console using Ctrl-X.")
|
||||||
|
rawterm.Configure()
|
||||||
|
defer rawterm.Restore()
|
||||||
|
var line []byte
|
||||||
|
for {
|
||||||
|
ch := rawterm.Getchar()
|
||||||
|
line = append(line, ch)
|
||||||
|
|
||||||
|
// Send the current line to the central.
|
||||||
|
if ch == '\x18' {
|
||||||
|
// The user pressed Ctrl-X, exit the program.
|
||||||
|
break
|
||||||
|
} else if ch == '\n' {
|
||||||
|
sendbuf := line // copy buffer
|
||||||
|
// Reset the slice while keeping the buffer in place.
|
||||||
|
line = line[:0]
|
||||||
|
|
||||||
|
// Send the sendbuf after breaking it up in pieces.
|
||||||
|
for len(sendbuf) != 0 {
|
||||||
|
// Chop off up to 20 bytes from the sendbuf.
|
||||||
|
partlen := 20
|
||||||
|
if len(sendbuf) < 20 {
|
||||||
|
partlen = len(sendbuf)
|
||||||
|
}
|
||||||
|
part := sendbuf[:partlen]
|
||||||
|
sendbuf = sendbuf[partlen:]
|
||||||
|
// This performs a "write command" aka "write without response".
|
||||||
|
_, err := rx.WriteWithoutResponse(part)
|
||||||
|
if err != nil {
|
||||||
|
println("could not send:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
gap.go
14
gap.go
|
@ -305,3 +305,17 @@ func (buf *rawAdvertisementPayload) addServiceUUID(uuid UUID) (ok bool) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConnectionParams are used when connecting to a peripherals.
|
||||||
|
type ConnectionParams struct {
|
||||||
|
// The timeout for the connection attempt. Not used during the rest of the
|
||||||
|
// connection. If no duration is specified, a default timeout will be used.
|
||||||
|
ConnectionTimeout Duration
|
||||||
|
|
||||||
|
// Minimum and maximum connection interval. The shorter the interval, the
|
||||||
|
// faster data can travel between both devices but also the more power they
|
||||||
|
// will draw. If no intervals are specified, a default connection interval
|
||||||
|
// will be used.
|
||||||
|
MinInterval Duration
|
||||||
|
MaxInterval Duration
|
||||||
|
}
|
||||||
|
|
101
gap_nrf528xx.go
101
gap_nrf528xx.go
|
@ -4,6 +4,7 @@ package bluetooth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"device/arm"
|
"device/arm"
|
||||||
|
"errors"
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -17,6 +18,8 @@ import (
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
|
var errAlreadyConnecting = errors.New("bluetooth: already in a connection attempt")
|
||||||
|
|
||||||
// Memory buffers needed by sd_ble_gap_scan_start.
|
// Memory buffers needed by sd_ble_gap_scan_start.
|
||||||
var (
|
var (
|
||||||
scanReportBuffer rawAdvertisementPayload
|
scanReportBuffer rawAdvertisementPayload
|
||||||
|
@ -83,6 +86,9 @@ func (a *Advertisement) Start() error {
|
||||||
|
|
||||||
// Scan starts a BLE scan. It is stopped by a call to StopScan. A common pattern
|
// Scan starts a BLE scan. It is stopped by a call to StopScan. A common pattern
|
||||||
// is to cancel the scan when a particular device has been found.
|
// is to cancel the scan when a particular device has been found.
|
||||||
|
//
|
||||||
|
// The callback is run on the same goroutine as the Scan function when using a
|
||||||
|
// SoftDevice.
|
||||||
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
||||||
if a.scanning {
|
if a.scanning {
|
||||||
// There is a possible race condition here if Scan() is called from a
|
// There is a possible race condition here if Scan() is called from a
|
||||||
|
@ -140,5 +146,100 @@ func (a *Adapter) StopScan() error {
|
||||||
return errNotScanning
|
return errNotScanning
|
||||||
}
|
}
|
||||||
a.scanning = false
|
a.scanning = false
|
||||||
|
|
||||||
|
// TODO: stop immediately, not when the next scan result arrives.
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Device is a connection to a remote peripheral.
|
||||||
|
type Device struct {
|
||||||
|
connectionHandle uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// In-progress connection attempt.
|
||||||
|
var connectionAttempt struct {
|
||||||
|
state volatile.Register8 // 0 means unused, 1 means connecting, 2 means ready (connected or timeout)
|
||||||
|
connectionHandle uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect starts a connection attempt to the given peripheral device address.
|
||||||
|
//
|
||||||
|
// Limitations on Nordic SoftDevices inclue that you cannot do more than one
|
||||||
|
// connection attempt at once and that the address parameter must have the
|
||||||
|
// IsRandom bit set correctly. This bit is set correctly for scan results, so
|
||||||
|
// you can reuse that address directly.
|
||||||
|
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) {
|
||||||
|
// Construct an address object as used in the SoftDevice.
|
||||||
|
var addr C.ble_gap_addr_t
|
||||||
|
addr.addr = address.MAC
|
||||||
|
if address.IsRandom {
|
||||||
|
switch address.MAC[5] >> 6 {
|
||||||
|
case 0b11:
|
||||||
|
addr.set_bitfield_addr_type(C.BLE_GAP_ADDR_TYPE_RANDOM_STATIC)
|
||||||
|
case 0b01:
|
||||||
|
addr.set_bitfield_addr_type(C.BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE)
|
||||||
|
case 0b00:
|
||||||
|
addr.set_bitfield_addr_type(C.BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick default values if some parameters aren't specified.
|
||||||
|
if params.ConnectionTimeout == 0 {
|
||||||
|
params.ConnectionTimeout = NewDuration(4 * time.Second)
|
||||||
|
}
|
||||||
|
if params.MinInterval == 0 && params.MaxInterval == 0 {
|
||||||
|
// Pick some semi-arbitrary range if these values haven't been
|
||||||
|
// configured. The values have been picked to be compliant with the
|
||||||
|
// guidelines from Apple (section 35.6 Connection Parameters):
|
||||||
|
// https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf
|
||||||
|
params.MinInterval = NewDuration(15 * time.Millisecond)
|
||||||
|
params.MaxInterval = NewDuration(150 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set scan params, presumably these parameters are used to re-scan for the
|
||||||
|
// device to connect to because only right after an advertisement has been
|
||||||
|
// received is the device connectable.
|
||||||
|
scanParams := C.ble_gap_scan_params_t{}
|
||||||
|
scanParams.set_bitfield_extended(0)
|
||||||
|
scanParams.set_bitfield_active(0)
|
||||||
|
scanParams.interval = uint16(NewDuration(40 * time.Millisecond))
|
||||||
|
scanParams.window = uint16(NewDuration(30 * time.Millisecond))
|
||||||
|
scanParams.timeout = uint16(params.ConnectionTimeout)
|
||||||
|
|
||||||
|
connectionParams := C.ble_gap_conn_params_t{
|
||||||
|
min_conn_interval: uint16(params.MinInterval) / 2,
|
||||||
|
max_conn_interval: uint16(params.MaxInterval) / 2,
|
||||||
|
slave_latency: 0, // mostly relevant to connected keyboards etc
|
||||||
|
conn_sup_timeout: 200, // 2 seconds (in 10ms units), the minimum recommended by Apple
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag to the event handler that we are waiting for incoming connections.
|
||||||
|
// This should be safe as long as Connect is not called concurrently. And
|
||||||
|
// even then, it should catch most such race conditions.
|
||||||
|
if connectionAttempt.state.Get() != 0 {
|
||||||
|
return nil, errAlreadyConnecting
|
||||||
|
}
|
||||||
|
connectionAttempt.state.Set(1)
|
||||||
|
|
||||||
|
// Start the connection attempt. We'll get a signal in the event handler.
|
||||||
|
errCode := C.sd_ble_gap_connect(&addr, &scanParams, &connectionParams, C.BLE_CONN_CFG_TAG_DEFAULT)
|
||||||
|
if errCode != 0 {
|
||||||
|
connectionAttempt.state.Set(0)
|
||||||
|
return nil, Error(errCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the connection is established.
|
||||||
|
// TODO: use some sort of condition variable once the scheduler supports
|
||||||
|
// them.
|
||||||
|
for connectionAttempt.state.Get() != 2 {
|
||||||
|
arm.Asm("wfe")
|
||||||
|
}
|
||||||
|
connectionHandle := connectionAttempt.connectionHandle
|
||||||
|
connectionAttempt.state.Set(0)
|
||||||
|
|
||||||
|
// Connection has been established.
|
||||||
|
return &Device{
|
||||||
|
connectionHandle: connectionHandle,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
336
gattc_sd.go
Normal file
336
gattc_sd.go
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
// +build softdevice,!s110v8
|
||||||
|
|
||||||
|
package bluetooth
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Define SoftDevice functions as regular function declarations (not inline
|
||||||
|
// static functions).
|
||||||
|
#define SVCALL_AS_NORMAL_FUNCTION
|
||||||
|
|
||||||
|
#include "ble_gattc.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/arm"
|
||||||
|
"errors"
|
||||||
|
"runtime/volatile"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errAlreadyDiscovering = errors.New("bluetooth: already discovering a service or characteristic")
|
||||||
|
errNotFound = errors.New("bluetooth: not found")
|
||||||
|
errNoNotify = errors.New("bluetooth: no notify permission")
|
||||||
|
)
|
||||||
|
|
||||||
|
// A global used while discovering services, to communicate between the main
|
||||||
|
// program and the event handler.
|
||||||
|
var discoveringService struct {
|
||||||
|
state volatile.Register8 // 0 means nothing happening, 1 means in progress, 2 means found something
|
||||||
|
startHandle volatile.Register16
|
||||||
|
endHandle volatile.Register16
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceService is a BLE service on a connected peripheral device. It is only
|
||||||
|
// valid as long as the device remains connected.
|
||||||
|
type DeviceService struct {
|
||||||
|
connectionHandle uint16
|
||||||
|
startHandle uint16
|
||||||
|
endHandle uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscoverServices starts a service discovery procedure. Pass a list of service
|
||||||
|
// UUIDs you are interested in to this function. Either a slice of all services
|
||||||
|
// is returned (of the same length as the requested UUIDs and in the same
|
||||||
|
// order), or if some services could not be discovered an error is returned.
|
||||||
|
//
|
||||||
|
// Passing a nil slice of UUIDs will currently result in zero services being
|
||||||
|
// returned, but this may be changed in the future to return a complete list of
|
||||||
|
// services.
|
||||||
|
//
|
||||||
|
// On the Nordic SoftDevice, only one service discovery procedure may be done at
|
||||||
|
// a time.
|
||||||
|
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||||
|
if discoveringService.state.Get() != 0 {
|
||||||
|
// Not concurrency safe, but should catch most concurrency misuses.
|
||||||
|
return nil, errAlreadyDiscovering
|
||||||
|
}
|
||||||
|
|
||||||
|
services := make([]DeviceService, len(uuids))
|
||||||
|
for i, uuid := range uuids {
|
||||||
|
// Start discovery of this service.
|
||||||
|
shortUUID, errCode := uuid.shortUUID()
|
||||||
|
if errCode != 0 {
|
||||||
|
return nil, Error(errCode)
|
||||||
|
}
|
||||||
|
discoveringService.state.Set(1)
|
||||||
|
errCode = C.sd_ble_gattc_primary_services_discover(d.connectionHandle, 0, &shortUUID)
|
||||||
|
if errCode != 0 {
|
||||||
|
discoveringService.state.Set(0)
|
||||||
|
return nil, Error(errCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until it is discovered.
|
||||||
|
// TODO: use some sort of condition variable once the scheduler supports
|
||||||
|
// them.
|
||||||
|
for discoveringService.state.Get() == 1 {
|
||||||
|
// still waiting...
|
||||||
|
arm.Asm("wfe")
|
||||||
|
}
|
||||||
|
// Retrieve values, and mark the global as unused.
|
||||||
|
startHandle := discoveringService.startHandle.Get()
|
||||||
|
endHandle := discoveringService.endHandle.Get()
|
||||||
|
discoveringService.state.Set(0)
|
||||||
|
|
||||||
|
if startHandle == 0 {
|
||||||
|
// The event handler will set the start handle to zero if the
|
||||||
|
// service was not found.
|
||||||
|
return nil, errNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the discovered service.
|
||||||
|
services[i] = DeviceService{
|
||||||
|
connectionHandle: d.connectionHandle,
|
||||||
|
startHandle: startHandle,
|
||||||
|
endHandle: endHandle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceCharacteristic is a BLE characteristic on a connected peripheral
|
||||||
|
// device. It is only valid as long as the device remains connected.
|
||||||
|
type DeviceCharacteristic struct {
|
||||||
|
connectionHandle uint16
|
||||||
|
valueHandle uint16
|
||||||
|
cccdHandle uint16
|
||||||
|
permissions CharacteristicPermissions
|
||||||
|
}
|
||||||
|
|
||||||
|
// A global used to pass information from the event handler back to the
|
||||||
|
// DiscoverCharacteristics function below.
|
||||||
|
var discoveringCharacteristic struct {
|
||||||
|
uuid C.ble_uuid_t
|
||||||
|
char_props C.ble_gatt_char_props_t
|
||||||
|
handle_value volatile.Register16
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscoverCharacteristics discovers characteristics in this service. Pass a
|
||||||
|
// list of characteristic UUIDs you are interested in to this function. Either a
|
||||||
|
// list of all requested services is returned, or if some services could not be
|
||||||
|
// discovered an error is returned. If there is no error, the characteristics
|
||||||
|
// slice has the same length as the UUID slice with characteristics in the same
|
||||||
|
// order in the slice as in the requested UUID list.
|
||||||
|
//
|
||||||
|
// Passing a nil slice of UUIDs will currently result in zero characteristics
|
||||||
|
// being returned, but this may be changed in the future to return a complete
|
||||||
|
// list of characteristics.
|
||||||
|
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
|
||||||
|
if len(uuids) == 0 {
|
||||||
|
// Nothing to do. This behavior might change in the future (if a nil
|
||||||
|
// uuids slice is passed).
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if discoveringCharacteristic.handle_value.Get() != 0 {
|
||||||
|
return nil, errAlreadyDiscovering
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a list of UUIDs in SoftDevice short form, for easier comparing.
|
||||||
|
shortUUIDs := make([]C.ble_uuid_t, len(uuids))
|
||||||
|
for i, uuid := range uuids {
|
||||||
|
var errCode uint32
|
||||||
|
shortUUIDs[i], errCode = uuid.shortUUID()
|
||||||
|
if errCode != 0 {
|
||||||
|
return nil, Error(errCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request characteristics one by one, until all are found.
|
||||||
|
numFound := 0
|
||||||
|
characteristics := make([]DeviceCharacteristic, len(uuids))
|
||||||
|
startHandle := s.startHandle
|
||||||
|
for numFound < len(uuids) && startHandle < s.endHandle {
|
||||||
|
// Discover the next characteristic in this service.
|
||||||
|
handles := C.ble_gattc_handle_range_t{
|
||||||
|
start_handle: startHandle,
|
||||||
|
end_handle: s.endHandle,
|
||||||
|
}
|
||||||
|
errCode := C.sd_ble_gattc_characteristics_discover(s.connectionHandle, &handles)
|
||||||
|
if errCode != 0 {
|
||||||
|
return nil, Error(errCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until it is discovered.
|
||||||
|
// TODO: use some sort of condition variable once the scheduler supports
|
||||||
|
// them.
|
||||||
|
for discoveringCharacteristic.handle_value.Get() == 0 {
|
||||||
|
arm.Asm("wfe")
|
||||||
|
}
|
||||||
|
foundCharacteristicHandle := discoveringCharacteristic.handle_value.Get()
|
||||||
|
discoveringCharacteristic.handle_value.Set(0)
|
||||||
|
|
||||||
|
// Start the next request from the handle right after this one.
|
||||||
|
startHandle = foundCharacteristicHandle + 1
|
||||||
|
|
||||||
|
// Look whether we found a requested handle.
|
||||||
|
for i, shortUUID := range shortUUIDs {
|
||||||
|
if discoveringCharacteristic.uuid == shortUUID {
|
||||||
|
// Found a characteristic.
|
||||||
|
permissions := CharacteristicPermissions(0)
|
||||||
|
rawPermissions := discoveringCharacteristic.char_props
|
||||||
|
if rawPermissions.bitfield_broadcast() != 0 {
|
||||||
|
permissions |= CharacteristicBroadcastPermission
|
||||||
|
}
|
||||||
|
if rawPermissions.bitfield_read() != 0 {
|
||||||
|
permissions |= CharacteristicReadPermission
|
||||||
|
}
|
||||||
|
if rawPermissions.bitfield_write_wo_resp() != 0 {
|
||||||
|
permissions |= CharacteristicWriteWithoutResponsePermission
|
||||||
|
}
|
||||||
|
if rawPermissions.bitfield_write() != 0 {
|
||||||
|
permissions |= CharacteristicWritePermission
|
||||||
|
}
|
||||||
|
if rawPermissions.bitfield_notify() != 0 {
|
||||||
|
permissions |= CharacteristicNotifyPermission
|
||||||
|
}
|
||||||
|
if rawPermissions.bitfield_indicate() != 0 {
|
||||||
|
permissions |= CharacteristicIndicatePermission
|
||||||
|
}
|
||||||
|
characteristics[i].permissions = permissions
|
||||||
|
characteristics[i].valueHandle = foundCharacteristicHandle
|
||||||
|
|
||||||
|
if permissions&CharacteristicNotifyPermission != 0 {
|
||||||
|
// This characteristic has the notify permission, so most
|
||||||
|
// likely it also supports notifications.
|
||||||
|
errCode := C.sd_ble_gattc_descriptors_discover(s.connectionHandle, &C.ble_gattc_handle_range_t{
|
||||||
|
start_handle: startHandle,
|
||||||
|
end_handle: s.endHandle,
|
||||||
|
})
|
||||||
|
if errCode != 0 {
|
||||||
|
return nil, Error(errCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the descriptor handle is found.
|
||||||
|
for discoveringCharacteristic.handle_value.Get() == 0 {
|
||||||
|
arm.Asm("wfe")
|
||||||
|
}
|
||||||
|
foundDescriptorHandle := discoveringCharacteristic.handle_value.Get()
|
||||||
|
discoveringCharacteristic.handle_value.Set(0)
|
||||||
|
|
||||||
|
characteristics[i].cccdHandle = foundDescriptorHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
numFound++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if numFound != len(uuids) {
|
||||||
|
return nil, errNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return characteristics, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteWithoutResponse replaces the characteristic value with a new value. The
|
||||||
|
// call will return before all data has been written. A limited number of such
|
||||||
|
// writes can be in flight at any given time. This call is also known as a
|
||||||
|
// "write command" (as opposed to a write request).
|
||||||
|
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errCode := C.sd_ble_gattc_write(c.connectionHandle, &C.ble_gattc_write_params_t{
|
||||||
|
write_op: C.BLE_GATT_OP_WRITE_CMD,
|
||||||
|
handle: c.valueHandle,
|
||||||
|
offset: 0,
|
||||||
|
len: uint16(len(p)),
|
||||||
|
p_value: &p[0],
|
||||||
|
})
|
||||||
|
if errCode != 0 {
|
||||||
|
return 0, Error(errCode)
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type gattcNotificationCallback struct {
|
||||||
|
connectionHandle uint16
|
||||||
|
valueHandle uint16 // may be 0 if the slot is empty
|
||||||
|
callback func([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of notification callbacks for the current connection. Some slots may be
|
||||||
|
// empty, they are indicated with a zero valueHandle. They can be reused for new
|
||||||
|
// notification callbacks.
|
||||||
|
var gattcNotificationCallbacks []gattcNotificationCallback
|
||||||
|
|
||||||
|
// EnableNotifications enables notifications in the Client Characteristic
|
||||||
|
// Configuration Descriptor (CCCD). This means that most peripherals will send a
|
||||||
|
// notification with a new value every time the value of the characteristic
|
||||||
|
// changes.
|
||||||
|
//
|
||||||
|
// Warning: when using the SoftDevice, the callback is called from an interrupt
|
||||||
|
// which means there are various limitations (such as not being able to allocate
|
||||||
|
// heap memory).
|
||||||
|
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
|
||||||
|
if c.permissions&CharacteristicNotifyPermission == 0 {
|
||||||
|
return errNoNotify
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to insert the callback in the list.
|
||||||
|
updatedCallback := false
|
||||||
|
mask := DisableInterrupts()
|
||||||
|
for i, callbackInfo := range gattcNotificationCallbacks {
|
||||||
|
// Check for re-enabling an already enabled notification.
|
||||||
|
if callbackInfo.valueHandle == c.valueHandle {
|
||||||
|
gattcNotificationCallbacks[i].callback = callback
|
||||||
|
updatedCallback = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !updatedCallback {
|
||||||
|
for i, callbackInfo := range gattcNotificationCallbacks {
|
||||||
|
// Check for empty slots.
|
||||||
|
if callbackInfo.valueHandle == 0 {
|
||||||
|
gattcNotificationCallbacks[i] = gattcNotificationCallback{
|
||||||
|
connectionHandle: c.connectionHandle,
|
||||||
|
valueHandle: c.valueHandle,
|
||||||
|
callback: callback,
|
||||||
|
}
|
||||||
|
updatedCallback = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RestoreInterrupts(mask)
|
||||||
|
|
||||||
|
// Add this callback to the list of callbacks, if it couldn't be inserted
|
||||||
|
// into the list.
|
||||||
|
if !updatedCallback {
|
||||||
|
// The append call is done outside of a cricital section to avoid GC in
|
||||||
|
// a critical section.
|
||||||
|
callbackList := append(gattcNotificationCallbacks, gattcNotificationCallback{
|
||||||
|
connectionHandle: c.connectionHandle,
|
||||||
|
valueHandle: c.valueHandle,
|
||||||
|
callback: callback,
|
||||||
|
})
|
||||||
|
mask := DisableInterrupts()
|
||||||
|
gattcNotificationCallbacks = callbackList
|
||||||
|
RestoreInterrupts(mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to the CCCD to enable notifications. Don't wait for a response.
|
||||||
|
value := [2]byte{0x01, 0x00} // 0x0001 enables notifications (and disables indications)
|
||||||
|
errCode := C.sd_ble_gattc_write(c.connectionHandle, &C.ble_gattc_write_params_t{
|
||||||
|
write_op: C.BLE_GATT_OP_WRITE_CMD,
|
||||||
|
handle: c.cccdHandle,
|
||||||
|
offset: 0,
|
||||||
|
len: 2,
|
||||||
|
p_value: &value[0],
|
||||||
|
})
|
||||||
|
return makeError(errCode)
|
||||||
|
}
|
|
@ -66,10 +66,13 @@ func (a *Adapter) AddService(service *Service) error {
|
||||||
char.Handle.permissions = char.Flags
|
char.Handle.permissions = char.Flags
|
||||||
}
|
}
|
||||||
if char.Flags.Write() && char.WriteEvent != nil {
|
if char.Flags.Write() && char.WriteEvent != nil {
|
||||||
a.charWriteHandlers = append(a.charWriteHandlers, charWriteHandler{
|
handlers := append(a.charWriteHandlers, charWriteHandler{
|
||||||
handle: handles.value_handle,
|
handle: handles.value_handle,
|
||||||
callback: char.WriteEvent,
|
callback: char.WriteEvent,
|
||||||
})
|
})
|
||||||
|
mask := DisableInterrupts()
|
||||||
|
a.charWriteHandlers = handlers
|
||||||
|
RestoreInterrupts(mask)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return makeError(errCode)
|
return makeError(errCode)
|
||||||
|
|
Loading…
Reference in a new issue