diff --git a/adapter_nrf528xx-full.go b/adapter_nrf528xx-full.go index bdeaacf..0375ab3 100644 --- a/adapter_nrf528xx-full.go +++ b/adapter_nrf528xx-full.go @@ -42,6 +42,7 @@ func handleEvent() { } connectionAttempt.connectionHandle = gapEvent.conn_handle connectionAttempt.state.Set(2) // connection was successful + mtuExchangeAttempt.state.Set(0) DefaultAdapter.connectHandler(device, true) } case C.BLE_GAP_EVT_DISCONNECTED: @@ -63,7 +64,7 @@ func handleEvent() { // because it would need to be reconfigured as a non-connectable // advertisement. That's left as a future addition, if // necessary. - C.sd_ble_gap_adv_start(defaultAdvertisement.handle, C.BLE_CONN_CFG_TAG_DEFAULT) + C.sd_ble_gap_adv_start(defaultAdvertisement.handle, connCfgTag) } device := Device{ connectionHandle: gapEvent.conn_handle, @@ -157,9 +158,16 @@ func handleEvent() { // way to handle it, ignore it. C.sd_ble_gatts_sys_attr_set(gattsEvent.conn_handle, nil, 0, 0) case C.BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST: - // This event is generated by some devices. While we could support - // larger MTUs, this default MTU is supported everywhere. - C.sd_ble_gatts_exchange_mtu_reply(gattsEvent.conn_handle, C.BLE_GATT_ATT_MTU_DEFAULT) + rsp := gattsEvent.params.unionfield_exchange_mtu_request() + effectiveMtu := min(DefaultAdapter.cfg.Gatt.AttMtu, uint16(rsp.client_rx_mtu)) + if debug { + println("mtu exchange requested. self:", DefaultAdapter.cfg.Gatt.AttMtu, ", peer:", rsp.client_rx_mtu, ", effective:", effectiveMtu) + } + + var errCode = C.sd_ble_gatts_exchange_mtu_reply(gattsEvent.conn_handle, C.uint16_t(effectiveMtu)) + if debug { + println("mtu exchange replied, err:", Error(errCode).Error()) + } case C.BLE_GATTS_EVT_HVN_TX_COMPLETE: // ignore confirmation of a notification successfully sent default: @@ -255,6 +263,36 @@ func handleEvent() { } } } + case C.BLE_GATTC_EVT_EXCHANGE_MTU_RSP: + rsp := gattcEvent.params.unionfield_exchange_mtu_rsp() + if debug { + println("mtu exchanged, effective mtu:", rsp.server_rx_mtu) + } + + mtuExchangeAttempt.effectiveMtu = uint16(rsp.server_rx_mtu) + mtuExchangeAttempt.state.Set(2) // mtu exchange was successful + + if debug { + rsp := gattcEvent.params.unionfield_exchange_mtu_rsp() + println("mtu exchanged, effective mtu:", rsp.server_rx_mtu) + } + case C.BLE_GATTC_EVT_TIMEOUT: + timeoutEvt := gattcEvent.params.unionfield_timeout() + switch timeoutEvt.src { + case C.BLE_GATT_TIMEOUT_SRC_PROTOCOL: + // Failed to connect to a peripheral. + if debug { + println("gattc timeout: src protocol") + } + mtuExchangeAttempt.state.Set(3) // mtu exchange timed out + default: + // For example a scan timeout. + if debug { + println("gattc timeout: other") + } + } + case C.BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE: + // no op default: if debug { println("unknown GATTC event:", id, id-C.BLE_GATTC_EVT_BASE) @@ -266,3 +304,10 @@ func handleEvent() { } } } + +func min(a, b uint16) uint16 { + if a < b { + return a + } + return b +} diff --git a/adapter_nrf528xx-peripheral.go b/adapter_nrf528xx-peripheral.go index 00860df..f70749b 100644 --- a/adapter_nrf528xx-peripheral.go +++ b/adapter_nrf528xx-peripheral.go @@ -47,7 +47,7 @@ func handleEvent() { // because it would need to be reconfigured as a non-connectable // advertisement. That's left as a future addition, if // necessary. - C.sd_ble_gap_adv_start(defaultAdvertisement.handle, C.BLE_CONN_CFG_TAG_DEFAULT) + C.sd_ble_gap_adv_start(defaultAdvertisement.handle, connCfgTag) } device := Device{ connectionHandle: gapEvent.conn_handle, @@ -91,9 +91,16 @@ func handleEvent() { // way to handle it, ignore it. C.sd_ble_gatts_sys_attr_set(gattsEvent.conn_handle, nil, 0, 0) case C.BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST: - // This event is generated by some devices. While we could support - // larger MTUs, this default MTU is supported everywhere. - C.sd_ble_gatts_exchange_mtu_reply(gattsEvent.conn_handle, C.BLE_GATT_ATT_MTU_DEFAULT) + rsp := gattsEvent.params.unionfield_exchange_mtu_request() + effectiveMtu := min(DefaultAdapter.cfg.Gatt.AttMtu, uint16(rsp.client_rx_mtu)) + if debug { + println("mtu exchange requested. self:", DefaultAdapter.cfg.Gatt.AttMtu, ", peer:", rsp.client_rx_mtu, ", effective:", effectiveMtu) + } + + var errCode = C.sd_ble_gatts_exchange_mtu_reply(gattsEvent.conn_handle, C.uint16_t(effectiveMtu)) + if debug { + println("mtu exchange replied, err:", Error(errCode).Error()) + } case C.BLE_GATTS_EVT_HVN_TX_COMPLETE: // ignore confirmation of a notification successfully sent default: @@ -107,3 +114,10 @@ func handleEvent() { } } } + +func min(a, b uint16) uint16 { + if a < b { + return a + } + return b +} diff --git a/adapter_nrf528xx.go b/adapter_nrf528xx.go index 557699d..ebc97ee 100644 --- a/adapter_nrf528xx.go +++ b/adapter_nrf528xx.go @@ -31,6 +31,8 @@ var clockConfigXtal C.nrf_clock_lf_cfg_t = C.nrf_clock_lf_cfg_t{ accuracy: C.NRF_CLOCK_LF_ACCURACY_250_PPM, } +const connCfgTag uint8 = 1 + //go:extern __app_ram_base var appRAMBase [0]uint32 @@ -47,6 +49,37 @@ func (a *Adapter) enable() error { // Enable the BLE stack. appRAMBase := C.uint32_t(uintptr(unsafe.Pointer(&appRAMBase))) + + bleCfg := C.ble_cfg_t{} + + connCfg := bleCfg.unionfield_conn_cfg() + connCfg.conn_cfg_tag = connCfgTag + + if a.cfg.Gap.EventLength == 0 { + a.cfg.Gap.EventLength = 2 + } + + gapCfg := connCfg.params.unionfield_gap_conn_cfg() + gapCfg.conn_count = 1 + gapCfg.event_length = a.cfg.Gap.EventLength + + errCode = C.sd_ble_cfg_set(C.uint32_t(C.BLE_CONN_CFG_GAP), &bleCfg, appRAMBase) + if errCode != 0 { + return Error(errCode) + } + + if a.cfg.Gatt.AttMtu == 0 { + a.cfg.Gatt.AttMtu = 23 + } + + gattCfg := connCfg.params.unionfield_gatt_conn_cfg() + gattCfg.att_mtu = a.cfg.Gatt.AttMtu + + errCode = C.sd_ble_cfg_set(C.uint32_t(C.BLE_CONN_CFG_GATT), &bleCfg, appRAMBase) + if errCode != 0 { + return Error(errCode) + } + errCode = C.sd_ble_enable(&appRAMBase) return makeError(errCode) } diff --git a/adapter_sd.go b/adapter_sd.go index 4037ed9..eb24dcd 100644 --- a/adapter_sd.go +++ b/adapter_sd.go @@ -33,7 +33,7 @@ var currentConnection = volatileHandle{handle: volatile.Register16{C.BLE_CONN_HA // Globally allocated buffer for incoming SoftDevice events. var eventBuf struct { C.ble_evt_t - buf [23]byte + buf [244]byte } func init() { @@ -49,6 +49,8 @@ type Adapter struct { charWriteHandlers []charWriteHandler connectHandler func(device Device, connected bool) + + cfg Config } // DefaultAdapter is the default adapter on the current system. On Nordic chips, @@ -62,6 +64,42 @@ var DefaultAdapter = &Adapter{isDefault: true, var eventBufLen C.uint16_t +// Config represents the settings that will be configured to the connection +// '1' of the SoftDevice. +type Config struct { + Gap GapConfig + Gatt GattConfig +} + +type GapConfig struct { + // EventLength is the time set aside for this connection on every + // connection interval in 1.25 ms units. The minimum value is 2. + EventLength uint16 +} + +type GattConfig struct { + // AttMtu is the maximum size of ATT packet the SoftDevice can send or + // receive. The default and minimum value is 23. The maximum value is 247. + // Using ATT_MTU sizes that are multiples of 23 is ideal. + AttMtu uint16 +} + +// Configure sets the configuration for this adapter. +// The configuration will be applied when the adapter is enabled. +func (a *Adapter) Configure(cfg Config) error { + if cfg.Gap.EventLength != 0 && cfg.Gap.EventLength < 2 { + return errors.New("invalid event length") + } + + if cfg.Gatt.AttMtu != 0 && (cfg.Gatt.AttMtu < 23 || cfg.Gatt.AttMtu > 247) { + return errors.New("invalid ATT MTU") + } + + a.cfg = cfg + + return nil +} + // Enable configures the BLE stack. It must be called before any // Bluetooth-related calls (unless otherwise indicated). func (a *Adapter) Enable() error { diff --git a/gap_nrf528xx-advertisement.go b/gap_nrf528xx-advertisement.go index 1d72604..d6ebf7d 100644 --- a/gap_nrf528xx-advertisement.go +++ b/gap_nrf528xx-advertisement.go @@ -74,7 +74,7 @@ func (a *Advertisement) Configure(options AdvertisementOptions) error { // Start advertisement. May only be called after it has been configured. func (a *Advertisement) Start() error { a.isAdvertising.Set(1) - errCode := C.sd_ble_gap_adv_start(a.handle, C.BLE_CONN_CFG_TAG_DEFAULT) + errCode := C.sd_ble_gap_adv_start(a.handle, connCfgTag) return makeError(errCode) } diff --git a/gap_nrf528xx-central.go b/gap_nrf528xx-central.go index a0c8db7..1e09485 100644 --- a/gap_nrf528xx-central.go +++ b/gap_nrf528xx-central.go @@ -12,6 +12,7 @@ import ( /* #include "ble_gap.h" +#include "ble_gattc.h" */ import "C" @@ -99,6 +100,11 @@ var connectionAttempt struct { connectionHandle C.uint16_t } +var mtuExchangeAttempt struct { + state volatile.Register8 // 0 means unused, 1 means in progress, 2 means exchanged, 3 means timeout + effectiveMtu uint16 +} + // Connect starts a connection attempt to the given peripheral device address. // // Limitations on Nordic SoftDevices inclue that you cannot do more than one @@ -162,7 +168,7 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, err 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) + errCode := C.sd_ble_gap_connect(&addr, &scanParams, &connectionParams, connCfgTag) if errCode != 0 { connectionAttempt.state.Set(0) return Device{}, Error(errCode) @@ -175,6 +181,7 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, err // Successfully connected. connectionAttempt.state.Set(0) connectionHandle := connectionAttempt.connectionHandle + return Device{ connectionHandle: connectionHandle, }, nil @@ -190,6 +197,41 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, err } } +// ExchangeMTU starts an MTU exchange request procedure. The effective MTU +// (the maximum value supported by both parties) is returned. +func (d Device) ExchangeMTU(mtu uint16) (uint16, error) { + if mtuExchangeAttempt.state.Get() == 2 { + return 0, errors.New("mtu already exchanged") + } + + if mtuExchangeAttempt.state.Get() == 1 { + return 0, errors.New("mtu exchange in progress") + } + + mtuExchangeAttempt.state.Set(1) + + errCode := C.sd_ble_gattc_exchange_mtu_request(d.connectionHandle, C.uint16_t(mtu)) + if errCode != 0 { + mtuExchangeAttempt.state.Set(0) + return 0, Error(errCode) + } + + for { + state := mtuExchangeAttempt.state.Get() + if state == 2 { + return mtuExchangeAttempt.effectiveMtu, nil + } else if state == 3 { + // Timeout while exchanging mtu + mtuExchangeAttempt.state.Set(0) + return 0, errors.New("mtu exchange timeout") + } else { + // TODO: use some sort of condition variable once the scheduler + // supports them. + arm.Asm("wfe") + } + } +} + // Disconnect from the BLE device. func (d Device) Disconnect() error { errCode := C.sd_ble_gap_disconnect(d.connectionHandle, C.BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION) diff --git a/gatts_sd.go b/gatts_sd.go index 602aa24..db57c9a 100644 --- a/gatts_sd.go +++ b/gatts_sd.go @@ -63,7 +63,7 @@ func (a *Adapter) AddService(service *Service) error { }, init_len: C.uint16_t(len(char.Value)), init_offs: 0, - max_len: 20, // This is a conservative maximum length. + max_len: C.BLE_GATTS_FIX_ATTR_LEN_MAX, } if len(char.Value) != 0 { value.p_value = (*C.uint8_t)(unsafe.Pointer(&char.Value[0]))