From e1b51207a48b659ae3c32b971379958534493ff8 Mon Sep 17 00:00:00 2001 From: David Ross Date: Thu, 13 Aug 2020 00:16:20 -0700 Subject: [PATCH 01/20] Start of writeable connection attribute user interface --- rubble/src/att/mod.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/rubble/src/att/mod.rs b/rubble/src/att/mod.rs index 51cea7d1..12d9cbb9 100644 --- a/rubble/src/att/mod.rs +++ b/rubble/src/att/mod.rs @@ -94,6 +94,12 @@ impl Attribute { } } +pub enum AttributeAccessPermissions { + Readable, + Writeable, + ReadableAndWritable, +} + /// Trait for attribute sets that can be hosted by an `AttributeServer`. pub trait AttributeProvider { /// Calls a closure `f` with every attribute whose handle is inside `range`, ascending. @@ -130,6 +136,30 @@ pub trait AttributeProvider { /// /// TODO: document what the BLE spec has to say about grouping for characteristics. fn group_end(&self, handle: Handle) -> Option<&Attribute>; + + /// Retrieves the permissions for the given attribute. + /// + /// These are used purely for access control within rubble, and won't be + /// communicated with clients. They should be coordinated beforehand as part + /// of a larger protocol. + /// + /// Defaults to read-only. If this is overwritten, `write_attribute` should + /// be overwritten. + fn attribute_access_permissions(&self, uuid: AttUuid) -> AttributeAccessPermissions { + AttributeAccessPermissions::Readable + } + + /// Attempts to write data to the given attribute. + /// + /// This will only be called on UUIDs for which + /// `attribute_access_permissions` returns + /// [`AttributeAccessPermissions::Writeable`] or [`AttributeAccessPermission::ReadableAndWriteable`]. + /// + /// By default, panics on all writes. This should be overwritten if + /// `attribute_access_permissions` is. + fn write_attribute(&mut self, uuid: AttUuid, data: &[u8]) -> Result<(), Error> { + unimplemented!("by default, no attributes should have write access permissions, and this should never be called"); + } } /// An empty attribute set. From 9552868f081312ca352a1fdbb5fc540e57b1f372 Mon Sep 17 00:00:00 2001 From: David Ross Date: Fri, 21 Aug 2020 16:57:31 -0700 Subject: [PATCH 02/20] WIP interface as of 08/21. --- rubble/src/att/mod.rs | 21 +++++++++++++++++++-- rubble/src/att/server.rs | 36 +++++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/rubble/src/att/mod.rs b/rubble/src/att/mod.rs index 12d9cbb9..2c389936 100644 --- a/rubble/src/att/mod.rs +++ b/rubble/src/att/mod.rs @@ -100,6 +100,23 @@ pub enum AttributeAccessPermissions { ReadableAndWritable, } +impl AttributeAccessPermissions { + fn can_read(&self) -> bool { + match self { + AttributeAccessPermissions::Readable + | AttributeAccessPermissions::ReadableAndWritable => true, + AttributeAccessPermissions::Writeable => false, + } + } + fn can_write(&self) -> bool { + match self { + AttributeAccessPermissions::Writeable + | AttributeAccessPermissions::ReadableAndWritable => true, + AttributeAccessPermissions::Readable => false, + } + } +} + /// Trait for attribute sets that can be hosted by an `AttributeServer`. pub trait AttributeProvider { /// Calls a closure `f` with every attribute whose handle is inside `range`, ascending. @@ -145,7 +162,7 @@ pub trait AttributeProvider { /// /// Defaults to read-only. If this is overwritten, `write_attribute` should /// be overwritten. - fn attribute_access_permissions(&self, uuid: AttUuid) -> AttributeAccessPermissions { + fn attribute_access_permissions(&self, handle: Handle) -> AttributeAccessPermissions { AttributeAccessPermissions::Readable } @@ -157,7 +174,7 @@ pub trait AttributeProvider { /// /// By default, panics on all writes. This should be overwritten if /// `attribute_access_permissions` is. - fn write_attribute(&mut self, uuid: AttUuid, data: &[u8]) -> Result<(), Error> { + fn write_attribute(&mut self, handle: Handle, data: &[u8]) -> Result<(), Error> { unimplemented!("by default, no attributes should have write access permissions, and this should never be called"); } } diff --git a/rubble/src/att/server.rs b/rubble/src/att/server.rs index 6117a59d..4c05050e 100644 --- a/rubble/src/att/server.rs +++ b/rubble/src/att/server.rs @@ -213,19 +213,36 @@ impl AttributeServer { Ok(()) } - AttPdu::WriteReq { .. } => { + AttPdu::WriteReq { value, handle } => { // FIXME: ATT Writes are not yet supported, but we pretend they work so that some // applications that only need CCCD writes work (eg. BLE MIDI). - warn!("NYI: ATT Write Req"); + if self.attrs.attribute_access_permissions(handle).can_write() { + self.attrs.write_attribute(handle, value.as_ref()); - responder - .send_with(|writer| -> Result<(), Error> { - writer.write_u8(Opcode::WriteRsp.into())?; - Ok(()) - }) - .unwrap(); + responder + .send_with(|writer| -> Result<(), Error> { + writer.write_u8(Opcode::WriteRsp.into())?; + Ok(()) + }) + .unwrap(); + } else { + responder + .send_with(|writer| -> Result<(), Error> { + AttPdu::ErrorRsp { + opcode: Opcode::WriteReq, + handle, + error_code: ErrorCode::WriteNotPermitted, + } + .to_bytes(writer)?; + Ok(()) + }) + .unwrap(); + } Ok(()) } + AttPdu::WriteCommand { handle, value } => {} + AttPdu::PrepareWriteReq { handle, offset, value } => {} + AttPdu::ExecuteWriteReq { flags } => {} // Responses are always invalid here AttPdu::ErrorRsp { .. } @@ -251,10 +268,7 @@ impl AttributeServer { | AttPdu::FindByTypeValueReq { .. } | AttPdu::ReadBlobReq { .. } | AttPdu::ReadMultipleReq { .. } - | AttPdu::WriteCommand { .. } | AttPdu::SignedWriteCommand { .. } - | AttPdu::PrepareWriteReq { .. } - | AttPdu::ExecuteWriteReq { .. } | AttPdu::HandleValueConfirmation { .. } => { if msg.opcode().is_command() { // According to the spec, unknown Command PDUs should be ignored From 571a2deee10ed6b1db4d7c6ad7dae3f72ca0ca6c Mon Sep 17 00:00:00 2001 From: noloerino Date: Wed, 2 Dec 2020 16:44:38 -0800 Subject: [PATCH 03/20] Add permission checks on type/group reads --- rubble/src/att/mod.rs | 8 +++---- rubble/src/att/server.rs | 45 ++++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/rubble/src/att/mod.rs b/rubble/src/att/mod.rs index 2c389936..fc5fc868 100644 --- a/rubble/src/att/mod.rs +++ b/rubble/src/att/mod.rs @@ -101,14 +101,14 @@ pub enum AttributeAccessPermissions { } impl AttributeAccessPermissions { - fn can_read(&self) -> bool { + fn is_readable(&self) -> bool { match self { AttributeAccessPermissions::Readable | AttributeAccessPermissions::ReadableAndWritable => true, AttributeAccessPermissions::Writeable => false, } } - fn can_write(&self) -> bool { + fn is_writeable(&self) -> bool { match self { AttributeAccessPermissions::Writeable | AttributeAccessPermissions::ReadableAndWritable => true, @@ -162,7 +162,7 @@ pub trait AttributeProvider { /// /// Defaults to read-only. If this is overwritten, `write_attribute` should /// be overwritten. - fn attribute_access_permissions(&self, handle: Handle) -> AttributeAccessPermissions { + fn attr_access_permissions(&self, _handle: Handle) -> AttributeAccessPermissions { AttributeAccessPermissions::Readable } @@ -174,7 +174,7 @@ pub trait AttributeProvider { /// /// By default, panics on all writes. This should be overwritten if /// `attribute_access_permissions` is. - fn write_attribute(&mut self, handle: Handle, data: &[u8]) -> Result<(), Error> { + fn write_attr(&mut self, _handle: Handle, _data: &[u8]) -> Result<(), Error> { unimplemented!("by default, no attributes should have write access permissions, and this should never be called"); } } diff --git a/rubble/src/att/server.rs b/rubble/src/att/server.rs index 4c05050e..c1e3aad0 100644 --- a/rubble/src/att/server.rs +++ b/rubble/src/att/server.rs @@ -96,8 +96,12 @@ impl AttributeServer { let mut size = None; let att_mtu = self.att_mtu(); self.attrs - .for_attrs_in_range(range, |_provider, attr| { - if attr.att_type == *attribute_type { + .for_attrs_in_range(range, |provider, attr| { + // "Only attributes that can be read shall be returned in a + // Read By Type Response." + if attr.att_type == *attribute_type + && provider.attr_access_permissions(attr.handle).is_readable() + { let data = ByTypeAttData::new(att_mtu, attr.handle, attr.value.as_slice()); if size == Some(data.encoded_size()) || size.is_none() { @@ -151,7 +155,9 @@ impl AttributeServer { let att_mtu = self.att_mtu(); self.attrs .for_attrs_in_range(range, |provider, attr| { - if attr.att_type == *group_type { + if attr.att_type == *group_type + && provider.attr_access_permissions(attr.handle).is_readable() + { let data = ByGroupAttData::new( att_mtu, attr.handle, @@ -197,6 +203,8 @@ impl AttributeServer { self.attrs.for_attrs_in_range( HandleRange::new(*handle, *handle), |_provider, attr| { + // FIXME short circuit if attribute is not readable + // Err(AttError::new(ErrorCode::ReadNotPermitted, *handle)) let value = if writer.space_left() < attr.value.as_slice().len() { &attr.value.as_slice()[..writer.space_left()] } else { @@ -214,35 +222,26 @@ impl AttributeServer { } AttPdu::WriteReq { value, handle } => { - // FIXME: ATT Writes are not yet supported, but we pretend they work so that some - // applications that only need CCCD writes work (eg. BLE MIDI). - if self.attrs.attribute_access_permissions(handle).can_write() { - self.attrs.write_attribute(handle, value.as_ref()); - + if self.attrs.attr_access_permissions(*handle).is_writeable() { + self.attrs.write_attr(*handle, value.as_ref()).unwrap(); responder .send_with(|writer| -> Result<(), Error> { writer.write_u8(Opcode::WriteRsp.into())?; Ok(()) }) .unwrap(); + Ok(()) } else { - responder - .send_with(|writer| -> Result<(), Error> { - AttPdu::ErrorRsp { - opcode: Opcode::WriteReq, - handle, - error_code: ErrorCode::WriteNotPermitted, - } - .to_bytes(writer)?; - Ok(()) - }) - .unwrap(); + Err(AttError::new(ErrorCode::WriteNotPermitted, *handle)) + } + } + AttPdu::WriteCommand { handle, value } => { + // WriteCommand shouldn't respond to the client even on failure + if self.attrs.attr_access_permissions(*handle).is_writeable() { + self.attrs.write_attr(*handle, value.as_ref()).unwrap(); } Ok(()) } - AttPdu::WriteCommand { handle, value } => {} - AttPdu::PrepareWriteReq { handle, offset, value } => {} - AttPdu::ExecuteWriteReq { flags } => {} // Responses are always invalid here AttPdu::ErrorRsp { .. } @@ -268,6 +267,8 @@ impl AttributeServer { | AttPdu::FindByTypeValueReq { .. } | AttPdu::ReadBlobReq { .. } | AttPdu::ReadMultipleReq { .. } + | AttPdu::PrepareWriteReq { .. } + | AttPdu::ExecuteWriteReq { .. } | AttPdu::SignedWriteCommand { .. } | AttPdu::HandleValueConfirmation { .. } => { if msg.opcode().is_command() { From 4f41d2345e224fb879ca4f3cbeefa9ae80f54bf2 Mon Sep 17 00:00:00 2001 From: noloerino Date: Wed, 2 Dec 2020 19:54:17 -0800 Subject: [PATCH 04/20] Start writeable attribute demo (stuck on mutability of state) --- .../nrf52-writeable-attributes/.cargo/config | 8 + demos/nrf52-writeable-attributes/Cargo.toml | 37 ++ demos/nrf52-writeable-attributes/src/main.rs | 331 ++++++++++++++++++ rubble/src/att/mod.rs | 17 +- 4 files changed, 388 insertions(+), 5 deletions(-) create mode 100644 demos/nrf52-writeable-attributes/.cargo/config create mode 100644 demos/nrf52-writeable-attributes/Cargo.toml create mode 100644 demos/nrf52-writeable-attributes/src/main.rs diff --git a/demos/nrf52-writeable-attributes/.cargo/config b/demos/nrf52-writeable-attributes/.cargo/config new file mode 100644 index 00000000..7a738d65 --- /dev/null +++ b/demos/nrf52-writeable-attributes/.cargo/config @@ -0,0 +1,8 @@ +[build] +target = 'thumbv7em-none-eabi' + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = 'arm-none-eabi-gdb' +rustflags = [ + "-C", "link-arg=-Tlink.x", +] diff --git a/demos/nrf52-writeable-attributes/Cargo.toml b/demos/nrf52-writeable-attributes/Cargo.toml new file mode 100644 index 00000000..2b74f9df --- /dev/null +++ b/demos/nrf52-writeable-attributes/Cargo.toml @@ -0,0 +1,37 @@ +[package] +authors = ["Jonathan Shi "] +description = "Rubble BLE demo with writeable attributes nRF52 MCU family" +categories = ["embedded", "no-std"] +keywords = ["arm", "nrf", "bluetooth", "low", "energy"] +repository = "https://github.com/jonas-schievink/rubble/" +license = "0BSD" +name = "nrf52-writeable-attributes" +version = "0.0.0" +edition = "2018" +publish = false + +[dependencies] +rubble = { path = "../../rubble", default-features = false } +# TODO make feature generic +rubble-nrf5x = { path = "../../rubble-nrf5x", features = ["52832"] } +demo-utils = { path = "../demo-utils" } +cortex-m = "0.6.1" +cortex-m-rtic = "0.5.3" +cortex-m-rt = "0.6.11" +rtt-target = { version = "0.2.0", features = ["cortex-m"] } + +# TODO uncomment the other versions as well +# nrf52810-hal = { version = "0.12", features = ["rt"], optional = true } +nrf52832-hal = { version = "0.12", features = ["rt"] } +# nrf52840-hal = { version = "0.12", features = ["rt"], optional = true } + +# Disable documentation to avoid spurious rustdoc warnings +[[bin]] +name = "nrf52-writeable-attributes" +doc = false +test = false + +# [features] +# 52810 = ["rubble-nrf5x/52810", "nrf52810-hal"] +# 52832 = ["rubble-nrf5x/52832", "nrf52832-hal"] +# 52840 = ["rubble-nrf5x/52840", "nrf52840-hal"] diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs new file mode 100644 index 00000000..c825f7ff --- /dev/null +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -0,0 +1,331 @@ +#![no_std] +#![no_main] + +// #[cfg(feature = "52810")] +// use nrf52810_hal as hal; +// #[cfg(feature = "52832")] +use nrf52832_hal as hal; +// #[cfg(feature = "52840")] +// use nrf52840_hal as hal; + +use core::cmp; +use hal::gpio::{Level, Output, Pin, PushPull}; +use hal::prelude::OutputPin; +use rtt_target::{rprintln, rtt_init_print}; +use rubble::{ + att::{AttUuid, Attribute, AttributeAccessPermissions, AttributeProvider, Handle, HandleRange}, + config::Config, + l2cap::{BleChannelMap, L2CAPState}, + link::{ + ad_structure::AdStructure, + queue::{PacketQueue, SimpleQueue}, + LinkLayer, Responder, MIN_PDU_BUF, + }, + security::NoSecurity, + time::{Duration, Timer}, + uuid::{Uuid128, Uuid16}, + Error, +}; +use rubble_nrf5x::{ + radio::{BleRadio, PacketBuffer}, + timer::BleTimer, + utils::get_device_address, +}; + +pub struct LedBlinkAttrs { + // State and resources to be modified/queried when packets are received + led_pin: Pin>, + // Attributes exposed to clients + attributes: [Attribute<'static>; 3], +} + +const PRIMARY_SERVICE_UUID16: Uuid16 = Uuid16(0x2800); +const CHARACTERISTIC_UUID16: Uuid16 = Uuid16(0x2803); + +// TODO what UUID should this be? I took this from a course assignment :P +// 32e61089-2b22-4db5-a914-43ce41986c70 +const LED_UUID128: [u8; 16] = [ + 0x70, 0x6C, 0x98, 0x41, 0xCE, 0x43, 0x14, 0xA9, 0xB5, 0x4D, 0x22, 0x2B, 0x89, 0x10, 0xE6, 0x32, +]; +// Replace bytes 12/13 (0x1089) of the 128-bit UUID with 0x108A +const LED_STATE_CHAR_UUID128: [u8; 16] = [ + 0x70, 0x6C, 0x98, 0x41, 0xCE, 0x43, 0x14, 0xA9, 0xB5, 0x4D, 0x22, 0x2B, 0x8A, 0x10, 0xE6, 0x32, +]; + +const LED_CHAR_DECL_VALUE: [u8; 19] = [ + 0x02 | 0x08, // 0x02 = read, 0x08 = write with response + // 2 byte handle pointing to characteristic value + 0x03, + 0x00, + // 128-bit UUID of characteristic value (copied from above constant) + 0x70, + 0x6C, + 0x98, + 0x41, + 0xCE, + 0x43, + 0x14, + 0xA9, + 0xB5, + 0x4D, + 0x22, + 0x2B, + 0x8A, + 0x10, + 0xE6, + 0x32, +]; + +impl LedBlinkAttrs { + fn new(led_pin: Pin>, led_state: &'static mut [u8; 1]) -> Self { + Self { + led_pin, + attributes: [ + Attribute::new( + PRIMARY_SERVICE_UUID16.into(), + Handle::from_raw(0x0001), + &LED_UUID128, + ), + Attribute::new( + CHARACTERISTIC_UUID16.into(), + Handle::from_raw(0x0002), + &LED_CHAR_DECL_VALUE, + ), + // Characteristic value + Attribute::new( + Uuid128::from_bytes(LED_STATE_CHAR_UUID128).into(), + Handle::from_raw(0x0003), + led_state, + ), + ], + } + } +} + +impl AttributeProvider for LedBlinkAttrs { + /// Retrieves the permissions for attribute with the given handle. + fn attr_access_permissions(&self, handle: Handle) -> AttributeAccessPermissions { + match handle.as_u16() { + 0x0003 => AttributeAccessPermissions::ReadableAndWritable, + _ => AttributeAccessPermissions::Readable, + } + } + + /// Attempts to write data to the attribute with the given handle. + /// If any of your attributes are writeable, this function must be implemented. + fn write_attr(&mut self, handle: Handle, data: &[u8]) -> Result<(), Error> { + match handle.as_u16() { + 0x0003 => { + self.attributes[2].value.0[0..1].copy_from_slice(&data[0..1]); + // If we receive a 1, activate the LED; otherwise deactivate it + // Assumes LED is active low + if data[0] == 1 { + self.led_pin.set_low().unwrap(); + } else { + self.led_pin.set_high().unwrap(); + } + Ok(()) + } + _ => panic!("Attempted to write an unwriteable attribute"), + } + } + + fn is_grouping_attr(&self, uuid: AttUuid) -> bool { + uuid == PRIMARY_SERVICE_UUID16 || uuid == CHARACTERISTIC_UUID16 + } + + fn group_end(&self, handle: Handle) -> core::option::Option<&Attribute<'_>> { + match handle.as_u16() { + // Handles for the primary service and characteristic + 0x0001 | 0x0002 => Some(&self.attributes[2]), + _ => None, + } + } + + // Boilerplate to apply a function to all attributes with handles within the specified range + // This was copied from the implementation of gatt:BatteryServiceAttrs + fn for_attrs_in_range( + &mut self, + range: HandleRange, + mut f: impl FnMut(&Self, Attribute<'_>) -> Result<(), Error>, + ) -> Result<(), Error> { + let count = self.attributes.len(); + let start = usize::from(range.start().as_u16() - 1); // handles start at 1, not 0 + let end = usize::from(range.end().as_u16() - 1); + + let attrs = if start >= count { + &[] + } else { + let end = cmp::min(count - 1, end); + &self.attributes[start..=end] + }; + + for attr in attrs { + f( + self, + Attribute { + att_type: attr.att_type, + handle: attr.handle, + value: attr.value, + }, + )?; + } + Ok(()) + } +} + +pub enum AppConfig {} + +impl Config for AppConfig { + type Timer = BleTimer; + type Transmitter = BleRadio; + type ChannelMapper = BleChannelMap; + type PacketQueue = &'static mut SimpleQueue; +} + +#[rtic::app(device = crate::hal::pac, peripherals = true)] +const APP: () = { + struct Resources { + // Program state, backed by RTIC + #[init([0])] + led_state: [u8; 1], + // BLE boilerplate + #[init([0; MIN_PDU_BUF])] + ble_tx_buf: PacketBuffer, + #[init([0; MIN_PDU_BUF])] + ble_rx_buf: PacketBuffer, + #[init(SimpleQueue::new())] + tx_queue: SimpleQueue, + #[init(SimpleQueue::new())] + rx_queue: SimpleQueue, + ble_ll: LinkLayer, + ble_r: Responder, + radio: BleRadio, + } + + #[init(resources = [led_state, ble_tx_buf, ble_rx_buf, tx_queue, rx_queue])] + fn init(ctx: init::Context) -> init::LateResources { + rtt_init_print!(); + // On reset, the internal high frequency clock is already used, but we + // also need to switch to the external HF oscillator. This is needed + // for Bluetooth to work. + let _clocks = hal::clocks::Clocks::new(ctx.device.CLOCK).enable_ext_hfosc(); + + let ble_timer = BleTimer::init(ctx.device.TIMER0); + + let p0 = hal::gpio::p0::Parts::new(ctx.device.P0); + + // Determine device address + let device_address = get_device_address(); + + let mut radio = BleRadio::new( + ctx.device.RADIO, + &ctx.device.FICR, + ctx.resources.ble_tx_buf, + ctx.resources.ble_rx_buf, + ); + + // Create TX/RX queues + let (tx, tx_cons) = ctx.resources.tx_queue.split(); + let (rx_prod, rx) = ctx.resources.rx_queue.split(); + + // Create the actual BLE stack objects + let mut ble_ll = LinkLayer::::new(device_address, ble_timer); + + let ble_r = Responder::new( + tx, + rx, + L2CAPState::new(BleChannelMap::with_attributes(LedBlinkAttrs::new( + p0.p0_23.into_push_pull_output(Level::High).degrade(), + ctx.resources.led_state, + ))), + ); + + // Send advertisement and set up regular interrupt + let next_update = ble_ll + .start_advertise( + Duration::from_millis(200), + &[AdStructure::CompleteLocalName("CONCVRRENS CERTA CELERIS")], + &mut radio, + tx_cons, + rx_prod, + ) + .unwrap(); + + ble_ll.timer().configure_interrupt(next_update); + + init::LateResources { + radio, + ble_ll, + ble_r, + } + } + + #[task(binds = RADIO, resources = [radio, ble_ll], spawn = [ble_worker], priority = 3)] + fn radio(ctx: radio::Context) { + let ble_ll: &mut LinkLayer = ctx.resources.ble_ll; + if let Some(cmd) = ctx + .resources + .radio + .recv_interrupt(ble_ll.timer().now(), ble_ll) + { + ctx.resources.radio.configure_receiver(cmd.radio); + ble_ll.timer().configure_interrupt(cmd.next_update); + + if cmd.queued_work { + // If there's any lower-priority work to be done, ensure that happens. + // If we fail to spawn the task, it's already scheduled. + ctx.spawn.ble_worker().ok(); + } + } + } + + #[task(binds = TIMER0, resources = [radio, ble_ll], spawn = [ble_worker], priority = 3)] + fn timer0(ctx: timer0::Context) { + let timer = ctx.resources.ble_ll.timer(); + if !timer.is_interrupt_pending() { + return; + } + timer.clear_interrupt(); + + let cmd = ctx.resources.ble_ll.update_timer(ctx.resources.radio); + ctx.resources.radio.configure_receiver(cmd.radio); + + ctx.resources + .ble_ll + .timer() + .configure_interrupt(cmd.next_update); + + if cmd.queued_work { + // If there's any lower-priority work to be done, ensure that happens. + // If we fail to spawn the task, it's already scheduled. + ctx.spawn.ble_worker().ok(); + } + } + + #[idle] + fn idle(ctx: idle::Context) -> ! { + unimplemented!() + } + + #[task(resources = [ble_r], priority = 2)] + fn ble_worker(ctx: ble_worker::Context) { + // Fully drain the packet queue + while ctx.resources.ble_r.has_work() { + ctx.resources.ble_r.process_one().unwrap(); + } + } + + extern "C" { + fn WDT(); + } +}; + +#[panic_handler] +fn panic(e: &core::panic::PanicInfo) -> ! { + rprintln!("Unhandled panic; stopping"); + rprintln!("{}", e); + loop { + cortex_m::asm::bkpt(); + } +} diff --git a/rubble/src/att/mod.rs b/rubble/src/att/mod.rs index fc5fc868..4d1fdd6e 100644 --- a/rubble/src/att/mod.rs +++ b/rubble/src/att/mod.rs @@ -117,6 +117,12 @@ impl AttributeAccessPermissions { } } +impl Default for AttributeAccessPermissions { + fn default() -> Self { + AttributeAccessPermissions::Readable + } +} + /// Trait for attribute sets that can be hosted by an `AttributeServer`. pub trait AttributeProvider { /// Calls a closure `f` with every attribute whose handle is inside `range`, ascending. @@ -160,19 +166,20 @@ pub trait AttributeProvider { /// communicated with clients. They should be coordinated beforehand as part /// of a larger protocol. /// - /// Defaults to read-only. If this is overwritten, `write_attribute` should - /// be overwritten. + /// Defaults to read-only. If this is overwritten and some attributes are made writeable, + /// `write_attribute` must be implemented as well. fn attr_access_permissions(&self, _handle: Handle) -> AttributeAccessPermissions { AttributeAccessPermissions::Readable } /// Attempts to write data to the given attribute. /// - /// This will only be called on UUIDs for which + /// This will only be called on handles for which /// `attribute_access_permissions` returns - /// [`AttributeAccessPermissions::Writeable`] or [`AttributeAccessPermission::ReadableAndWriteable`]. + /// [`AttributeAccessPermissions::Writeable`] + /// or [`AttributeAccessPermission::ReadableAndWriteable`]. /// - /// By default, panics on all writes. This should be overwritten if + /// By default, panics on all writes. This must be overwritten if /// `attribute_access_permissions` is. fn write_attr(&mut self, _handle: Handle, _data: &[u8]) -> Result<(), Error> { unimplemented!("by default, no attributes should have write access permissions, and this should never be called"); From f84d6b6aa953116d57fec28d9c8250996cea7d63 Mon Sep 17 00:00:00 2001 From: noloerino Date: Wed, 2 Dec 2020 20:52:55 -0800 Subject: [PATCH 05/20] Print on receiving write --- demos/nrf52-writeable-attributes/src/main.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs index c825f7ff..b8a4f837 100644 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -9,6 +9,7 @@ use nrf52832_hal as hal; // use nrf52840_hal as hal; use core::cmp; +use cortex_m::interrupt::Mutex; use hal::gpio::{Level, Output, Pin, PushPull}; use hal::prelude::OutputPin; use rtt_target::{rprintln, rtt_init_print}; @@ -77,7 +78,7 @@ const LED_CHAR_DECL_VALUE: [u8; 19] = [ ]; impl LedBlinkAttrs { - fn new(led_pin: Pin>, led_state: &'static mut [u8; 1]) -> Self { + fn new(led_pin: Pin>, led_state: &Mutex<[u8; 1]>) -> Self { Self { led_pin, attributes: [ @@ -120,8 +121,10 @@ impl AttributeProvider for LedBlinkAttrs { // If we receive a 1, activate the LED; otherwise deactivate it // Assumes LED is active low if data[0] == 1 { + rprintln!("Setting LED high"); self.led_pin.set_low().unwrap(); } else { + rprintln!("Setting LED low"); self.led_pin.set_high().unwrap(); } Ok(()) @@ -187,8 +190,8 @@ impl Config for AppConfig { const APP: () = { struct Resources { // Program state, backed by RTIC - #[init([0])] - led_state: [u8; 1], + #[init(Mutex::new([0]))] + led_state: Mutex<[u8; 1]>, // BLE boilerplate #[init([0; MIN_PDU_BUF])] ble_tx_buf: PacketBuffer, From 8c03e69851a88accb93b27d0c11153799ca87e0a Mon Sep 17 00:00:00 2001 From: noloerino Date: Wed, 2 Dec 2020 21:08:47 -0800 Subject: [PATCH 06/20] Figure out how to store state --- demos/nrf52-writeable-attributes/src/main.rs | 29 ++++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs index b8a4f837..4306108b 100644 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -9,7 +9,6 @@ use nrf52832_hal as hal; // use nrf52840_hal as hal; use core::cmp; -use cortex_m::interrupt::Mutex; use hal::gpio::{Level, Output, Pin, PushPull}; use hal::prelude::OutputPin; use rtt_target::{rprintln, rtt_init_print}; @@ -33,11 +32,12 @@ use rubble_nrf5x::{ utils::get_device_address, }; -pub struct LedBlinkAttrs { +pub struct LedBlinkAttrs<'a> { // State and resources to be modified/queried when packets are received led_pin: Pin>, + led_state: &'a [u8; 1], // Attributes exposed to clients - attributes: [Attribute<'static>; 3], + attributes: [Attribute<'a>; 3], } const PRIMARY_SERVICE_UUID16: Uuid16 = Uuid16(0x2800); @@ -77,10 +77,11 @@ const LED_CHAR_DECL_VALUE: [u8; 19] = [ 0x32, ]; -impl LedBlinkAttrs { - fn new(led_pin: Pin>, led_state: &Mutex<[u8; 1]>) -> Self { +impl<'a> LedBlinkAttrs<'a> { + fn new(led_pin: Pin>, led_state: &'a [u8; 1]) -> Self { Self { led_pin, + led_state, attributes: [ Attribute::new( PRIMARY_SERVICE_UUID16.into(), @@ -96,14 +97,14 @@ impl LedBlinkAttrs { Attribute::new( Uuid128::from_bytes(LED_STATE_CHAR_UUID128).into(), Handle::from_raw(0x0003), - led_state, + &[0], ), ], } } } -impl AttributeProvider for LedBlinkAttrs { +impl<'a> AttributeProvider for LedBlinkAttrs<'a> { /// Retrieves the permissions for attribute with the given handle. fn attr_access_permissions(&self, handle: Handle) -> AttributeAccessPermissions { match handle.as_u16() { @@ -117,7 +118,6 @@ impl AttributeProvider for LedBlinkAttrs { fn write_attr(&mut self, handle: Handle, data: &[u8]) -> Result<(), Error> { match handle.as_u16() { 0x0003 => { - self.attributes[2].value.0[0..1].copy_from_slice(&data[0..1]); // If we receive a 1, activate the LED; otherwise deactivate it // Assumes LED is active low if data[0] == 1 { @@ -127,6 +127,11 @@ impl AttributeProvider for LedBlinkAttrs { rprintln!("Setting LED low"); self.led_pin.set_high().unwrap(); } + self.attributes[2] = Attribute::new( + Uuid128::from_bytes(LED_STATE_CHAR_UUID128).into(), + Handle::from_raw(0x0003), + self.led_state, + ); Ok(()) } _ => panic!("Attempted to write an unwriteable attribute"), @@ -182,16 +187,16 @@ pub enum AppConfig {} impl Config for AppConfig { type Timer = BleTimer; type Transmitter = BleRadio; - type ChannelMapper = BleChannelMap; + type ChannelMapper = BleChannelMap, NoSecurity>; type PacketQueue = &'static mut SimpleQueue; } #[rtic::app(device = crate::hal::pac, peripherals = true)] const APP: () = { struct Resources { - // Program state, backed by RTIC - #[init(Mutex::new([0]))] - led_state: Mutex<[u8; 1]>, + // State managed by RTIC + #[init([0; 1])] + led_state: [u8; 1], // BLE boilerplate #[init([0; MIN_PDU_BUF])] ble_tx_buf: PacketBuffer, From f1730193fac76ab90adc02dce1d9d90fcb9330fd Mon Sep 17 00:00:00 2001 From: noloerino Date: Wed, 2 Dec 2020 21:37:04 -0800 Subject: [PATCH 07/20] More state troubles (last time forgot to mutate state) --- demos/nrf52-writeable-attributes/src/main.rs | 26 ++++++++++++-------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs index 4306108b..61dbd5f1 100644 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -9,6 +9,7 @@ use nrf52832_hal as hal; // use nrf52840_hal as hal; use core::cmp; +use core::sync::atomic::{compiler_fence, Ordering}; use hal::gpio::{Level, Output, Pin, PushPull}; use hal::prelude::OutputPin; use rtt_target::{rprintln, rtt_init_print}; @@ -35,7 +36,7 @@ use rubble_nrf5x::{ pub struct LedBlinkAttrs<'a> { // State and resources to be modified/queried when packets are received led_pin: Pin>, - led_state: &'a [u8; 1], + led_state: &'a mut [u8; 1], // Attributes exposed to clients attributes: [Attribute<'a>; 3], } @@ -78,7 +79,7 @@ const LED_CHAR_DECL_VALUE: [u8; 19] = [ ]; impl<'a> LedBlinkAttrs<'a> { - fn new(led_pin: Pin>, led_state: &'a [u8; 1]) -> Self { + fn new(led_pin: Pin>, led_state: &'a mut [u8; 1]) -> Self { Self { led_pin, led_state, @@ -118,8 +119,13 @@ impl<'a> AttributeProvider for LedBlinkAttrs<'a> { fn write_attr(&mut self, handle: Handle, data: &[u8]) -> Result<(), Error> { match handle.as_u16() { 0x0003 => { + if data.is_empty() { + return Err(Error::InvalidLength); + } + rprintln!("Received data: {:#?}", data); // If we receive a 1, activate the LED; otherwise deactivate it // Assumes LED is active low + self.led_state.copy_from_slice(&data[0..1]); if data[0] == 1 { rprintln!("Setting LED high"); self.led_pin.set_low().unwrap(); @@ -127,11 +133,7 @@ impl<'a> AttributeProvider for LedBlinkAttrs<'a> { rprintln!("Setting LED low"); self.led_pin.set_high().unwrap(); } - self.attributes[2] = Attribute::new( - Uuid128::from_bytes(LED_STATE_CHAR_UUID128).into(), - Handle::from_raw(0x0003), - self.led_state, - ); + self.attributes[2].value.0.copy_from_slice(self.led_state); Ok(()) } _ => panic!("Attempted to write an unwriteable attribute"), @@ -214,6 +216,7 @@ const APP: () = { #[init(resources = [led_state, ble_tx_buf, ble_rx_buf, tx_queue, rx_queue])] fn init(ctx: init::Context) -> init::LateResources { rtt_init_print!(); + rprintln!("RTT initialized"); // On reset, the internal high frequency clock is already used, but we // also need to switch to the external HF oscillator. This is needed // for Bluetooth to work. @@ -252,7 +255,7 @@ const APP: () = { // Send advertisement and set up regular interrupt let next_update = ble_ll .start_advertise( - Duration::from_millis(200), + Duration::from_millis(1000), &[AdStructure::CompleteLocalName("CONCVRRENS CERTA CELERIS")], &mut radio, tx_cons, @@ -261,6 +264,7 @@ const APP: () = { .unwrap(); ble_ll.timer().configure_interrupt(next_update); + rprintln!("begin advertising"); init::LateResources { radio, @@ -312,8 +316,10 @@ const APP: () = { } #[idle] - fn idle(ctx: idle::Context) -> ! { - unimplemented!() + fn idle(_ctx: idle::Context) -> ! { + loop { + compiler_fence(Ordering::SeqCst); + } } #[task(resources = [ble_r], priority = 2)] From b5b240b101acb3c7fc11cffde57ce8ca20018847 Mon Sep 17 00:00:00 2001 From: noloerino Date: Wed, 2 Dec 2020 21:58:45 -0800 Subject: [PATCH 08/20] Use set_value to no avail --- demos/nrf52-writeable-attributes/src/main.rs | 8 +------- rubble/src/gatt/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs index 61dbd5f1..510207c8 100644 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -36,7 +36,6 @@ use rubble_nrf5x::{ pub struct LedBlinkAttrs<'a> { // State and resources to be modified/queried when packets are received led_pin: Pin>, - led_state: &'a mut [u8; 1], // Attributes exposed to clients attributes: [Attribute<'a>; 3], } @@ -82,7 +81,6 @@ impl<'a> LedBlinkAttrs<'a> { fn new(led_pin: Pin>, led_state: &'a mut [u8; 1]) -> Self { Self { led_pin, - led_state, attributes: [ Attribute::new( PRIMARY_SERVICE_UUID16.into(), @@ -125,7 +123,6 @@ impl<'a> AttributeProvider for LedBlinkAttrs<'a> { rprintln!("Received data: {:#?}", data); // If we receive a 1, activate the LED; otherwise deactivate it // Assumes LED is active low - self.led_state.copy_from_slice(&data[0..1]); if data[0] == 1 { rprintln!("Setting LED high"); self.led_pin.set_low().unwrap(); @@ -133,7 +130,7 @@ impl<'a> AttributeProvider for LedBlinkAttrs<'a> { rprintln!("Setting LED low"); self.led_pin.set_high().unwrap(); } - self.attributes[2].value.0.copy_from_slice(self.led_state); + self.attributes[2].set_value(data); Ok(()) } _ => panic!("Attempted to write an unwriteable attribute"), @@ -196,9 +193,6 @@ impl Config for AppConfig { #[rtic::app(device = crate::hal::pac, peripherals = true)] const APP: () = { struct Resources { - // State managed by RTIC - #[init([0; 1])] - led_state: [u8; 1], // BLE boilerplate #[init([0; MIN_PDU_BUF])] ble_tx_buf: PacketBuffer, diff --git a/rubble/src/gatt/mod.rs b/rubble/src/gatt/mod.rs index acd6e12f..d7ba6bca 100644 --- a/rubble/src/gatt/mod.rs +++ b/rubble/src/gatt/mod.rs @@ -44,7 +44,7 @@ impl BatteryServiceAttrs { } } -impl AttributeProvider for BatteryServiceAttrs { +impl AttributeProvider<'_> for BatteryServiceAttrs { fn for_attrs_in_range( &mut self, range: HandleRange, @@ -154,7 +154,7 @@ impl MidiServiceAttrs { } } -impl AttributeProvider for MidiServiceAttrs { +impl AttributeProvider<'_> for MidiServiceAttrs { fn for_attrs_in_range( &mut self, range: HandleRange, From fd1b6b9b93263b07125a7a8491a14bfb1231c4f5 Mon Sep 17 00:00:00 2001 From: noloerino Date: Wed, 2 Dec 2020 22:02:59 -0800 Subject: [PATCH 09/20] Identify lifetime issue --- rubble/src/gatt/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rubble/src/gatt/mod.rs b/rubble/src/gatt/mod.rs index d7ba6bca..acd6e12f 100644 --- a/rubble/src/gatt/mod.rs +++ b/rubble/src/gatt/mod.rs @@ -44,7 +44,7 @@ impl BatteryServiceAttrs { } } -impl AttributeProvider<'_> for BatteryServiceAttrs { +impl AttributeProvider for BatteryServiceAttrs { fn for_attrs_in_range( &mut self, range: HandleRange, @@ -154,7 +154,7 @@ impl MidiServiceAttrs { } } -impl AttributeProvider<'_> for MidiServiceAttrs { +impl AttributeProvider for MidiServiceAttrs { fn for_attrs_in_range( &mut self, range: HandleRange, From d8be471ac1a240072fc107739268fe4deb447e0d Mon Sep 17 00:00:00 2001 From: noloerino Date: Wed, 23 Dec 2020 15:11:17 -0800 Subject: [PATCH 10/20] Integrate ownable attr value --- demos/nrf52-writeable-attributes/src/main.rs | 39 +++++++++----------- rubble/src/att/mod.rs | 31 ++++------------ rubble/src/att/server.rs | 10 ++--- rubble/src/gatt/mod.rs | 12 +++--- 4 files changed, 36 insertions(+), 56 deletions(-) diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs index 510207c8..6f0b15a8 100644 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -33,11 +33,14 @@ use rubble_nrf5x::{ utils::get_device_address, }; -pub struct LedBlinkAttrs<'a> { +pub struct LedBlinkAttrs { // State and resources to be modified/queried when packets are received led_pin: Pin>, // Attributes exposed to clients - attributes: [Attribute<'a>; 3], + attributes: [Attribute<&'static [u8]>; 2], + // These attributes are also exposed to the client, but because they are writeable + // it is easiest for this struct to take ownership of the data + owned_attributes: [Attribute<[u8; 1]>; 1], } const PRIMARY_SERVICE_UUID16: Uuid16 = Uuid16(0x2800); @@ -77,8 +80,8 @@ const LED_CHAR_DECL_VALUE: [u8; 19] = [ 0x32, ]; -impl<'a> LedBlinkAttrs<'a> { - fn new(led_pin: Pin>, led_state: &'a mut [u8; 1]) -> Self { +impl LedBlinkAttrs { + fn new(led_pin: Pin>) -> Self { Self { led_pin, attributes: [ @@ -92,18 +95,20 @@ impl<'a> LedBlinkAttrs<'a> { Handle::from_raw(0x0002), &LED_CHAR_DECL_VALUE, ), + ], + owned_attributes: [ // Characteristic value Attribute::new( Uuid128::from_bytes(LED_STATE_CHAR_UUID128).into(), Handle::from_raw(0x0003), - &[0], + [0u8], ), ], } } } -impl<'a> AttributeProvider for LedBlinkAttrs<'a> { +impl AttributeProvider for LedBlinkAttrs { /// Retrieves the permissions for attribute with the given handle. fn attr_access_permissions(&self, handle: Handle) -> AttributeAccessPermissions { match handle.as_u16() { @@ -130,7 +135,7 @@ impl<'a> AttributeProvider for LedBlinkAttrs<'a> { rprintln!("Setting LED low"); self.led_pin.set_high().unwrap(); } - self.attributes[2].set_value(data); + self.owned_attributes[0].value.copy_from_slice(data); Ok(()) } _ => panic!("Attempted to write an unwriteable attribute"), @@ -141,10 +146,10 @@ impl<'a> AttributeProvider for LedBlinkAttrs<'a> { uuid == PRIMARY_SERVICE_UUID16 || uuid == CHARACTERISTIC_UUID16 } - fn group_end(&self, handle: Handle) -> core::option::Option<&Attribute<'_>> { + fn group_end(&self, handle: Handle) -> Option<&Attribute>> { match handle.as_u16() { // Handles for the primary service and characteristic - 0x0001 | 0x0002 => Some(&self.attributes[2]), + 0x0001 | 0x0002 => Some(&self.owned_attributes[0]), _ => None, } } @@ -154,7 +159,7 @@ impl<'a> AttributeProvider for LedBlinkAttrs<'a> { fn for_attrs_in_range( &mut self, range: HandleRange, - mut f: impl FnMut(&Self, Attribute<'_>) -> Result<(), Error>, + mut f: impl FnMut(&Self, &Attribute>) -> Result<(), Error>, ) -> Result<(), Error> { let count = self.attributes.len(); let start = usize::from(range.start().as_u16() - 1); // handles start at 1, not 0 @@ -168,14 +173,7 @@ impl<'a> AttributeProvider for LedBlinkAttrs<'a> { }; for attr in attrs { - f( - self, - Attribute { - att_type: attr.att_type, - handle: attr.handle, - value: attr.value, - }, - )?; + f(self, attr)?; } Ok(()) } @@ -186,7 +184,7 @@ pub enum AppConfig {} impl Config for AppConfig { type Timer = BleTimer; type Transmitter = BleRadio; - type ChannelMapper = BleChannelMap, NoSecurity>; + type ChannelMapper = BleChannelMap; type PacketQueue = &'static mut SimpleQueue; } @@ -207,7 +205,7 @@ const APP: () = { radio: BleRadio, } - #[init(resources = [led_state, ble_tx_buf, ble_rx_buf, tx_queue, rx_queue])] + #[init(resources = [ble_tx_buf, ble_rx_buf, tx_queue, rx_queue])] fn init(ctx: init::Context) -> init::LateResources { rtt_init_print!(); rprintln!("RTT initialized"); @@ -242,7 +240,6 @@ const APP: () = { rx, L2CAPState::new(BleChannelMap::with_attributes(LedBlinkAttrs::new( p0.p0_23.into_push_pull_output(Level::High).degrade(), - ctx.resources.led_state, ))), ); diff --git a/rubble/src/att/mod.rs b/rubble/src/att/mod.rs index 4d1fdd6e..5f6c9b86 100644 --- a/rubble/src/att/mod.rs +++ b/rubble/src/att/mod.rs @@ -41,23 +41,6 @@ pub use self::handle::{Handle, HandleRange}; pub use self::server::{AttributeServer, AttributeServerTx}; pub use self::uuid::AttUuid; -/// An attribute value that can be represented as a byte slice. -pub trait AttrValue { - fn as_slice(&self) -> &[u8]; -} - -impl AttrValue for &[u8] { - fn as_slice(&self) -> &[u8] { - self - } -} - -impl AttrValue for () { - fn as_slice(&self) -> &[u8] { - &[] - } -} - /// An ATT server attribute pub struct Attribute where @@ -72,20 +55,20 @@ where pub value: T, } -impl Attribute { +impl> Attribute { /// Creates a new attribute. pub fn new(att_type: AttUuid, handle: Handle, value: T) -> Self { assert_ne!(handle, Handle::NULL); Attribute { att_type, handle, - value: value, + value, } } /// Retrieves the attribute's value as a slice. pub fn value(&self) -> &[u8] { - self.value.as_slice() + self.value.as_ref() } /// Overrides the previously set attribute's value. @@ -136,7 +119,7 @@ pub trait AttributeProvider { fn for_attrs_in_range( &mut self, range: HandleRange, - f: impl FnMut(&Self, &Attribute) -> Result<(), Error>, + f: impl FnMut(&Self, &Attribute>) -> Result<(), Error>, ) -> Result<(), Error>; /// Returns whether `uuid` is a valid grouping attribute type that can be used in *Read By @@ -158,7 +141,7 @@ pub trait AttributeProvider { /// last attribute contained within that service. /// /// TODO: document what the BLE spec has to say about grouping for characteristics. - fn group_end(&self, handle: Handle) -> Option<&Attribute>; + fn group_end(&self, handle: Handle) -> Option<&Attribute>>; /// Retrieves the permissions for the given attribute. /// @@ -195,7 +178,7 @@ impl AttributeProvider for NoAttributes { fn for_attrs_in_range( &mut self, _range: HandleRange, - _f: impl FnMut(&Self, &Attribute) -> Result<(), Error>, + _f: impl FnMut(&Self, &Attribute>) -> Result<(), Error>, ) -> Result<(), Error> { Ok(()) } @@ -204,7 +187,7 @@ impl AttributeProvider for NoAttributes { false } - fn group_end(&self, _handle: Handle) -> Option<&Attribute> { + fn group_end(&self, _handle: Handle) -> Option<&Attribute>> { None } } diff --git a/rubble/src/att/server.rs b/rubble/src/att/server.rs index c1e3aad0..c2f5080c 100644 --- a/rubble/src/att/server.rs +++ b/rubble/src/att/server.rs @@ -103,7 +103,7 @@ impl AttributeServer { && provider.attr_access_permissions(attr.handle).is_readable() { let data = - ByTypeAttData::new(att_mtu, attr.handle, attr.value.as_slice()); + ByTypeAttData::new(att_mtu, attr.handle, attr.value.as_ref()); if size == Some(data.encoded_size()) || size.is_none() { // Can try to encode `data`. If we run out of space, end the list. data.to_bytes(writer)?; @@ -162,7 +162,7 @@ impl AttributeServer { att_mtu, attr.handle, provider.group_end(attr.handle).unwrap().handle, - attr.value.as_slice(), + attr.value.as_ref(), ); if size == Some(data.encoded_size()) || size.is_none() { // Can try to encode `data`. If we run out of space, end the list. @@ -205,10 +205,10 @@ impl AttributeServer { |_provider, attr| { // FIXME short circuit if attribute is not readable // Err(AttError::new(ErrorCode::ReadNotPermitted, *handle)) - let value = if writer.space_left() < attr.value.as_slice().len() { - &attr.value.as_slice()[..writer.space_left()] + let value = if writer.space_left() < attr.value.as_ref().len() { + &attr.value.as_ref()[..writer.space_left()] } else { - attr.value.as_slice() + attr.value.as_ref() }; writer.write_slice(value) }, diff --git a/rubble/src/gatt/mod.rs b/rubble/src/gatt/mod.rs index acd6e12f..94b0738b 100644 --- a/rubble/src/gatt/mod.rs +++ b/rubble/src/gatt/mod.rs @@ -5,7 +5,7 @@ pub mod characteristic; -use crate::att::{AttUuid, AttrValue, Attribute, AttributeProvider, Handle, HandleRange}; +use crate::att::{AttUuid, Attribute, AttributeProvider, Handle, HandleRange}; use crate::uuid::{Uuid128, Uuid16}; use crate::Error; use core::cmp; @@ -22,7 +22,7 @@ impl BatteryServiceAttrs { Attribute::new( Uuid16(0x2800).into(), // "Primary Service" Handle::from_raw(0x0001), - &[0x0F, 0x18], // "Battery Service" = 0x180F + &[0x0F, 0x18].as_ref(), // "Battery Service" = 0x180F ), Attribute::new( Uuid16(0x2803).into(), // "Characteristic" @@ -48,7 +48,7 @@ impl AttributeProvider for BatteryServiceAttrs { fn for_attrs_in_range( &mut self, range: HandleRange, - mut f: impl FnMut(&Self, &Attribute) -> Result<(), Error>, + mut f: impl FnMut(&Self, &Attribute>) -> Result<(), Error>, ) -> Result<(), Error> { let count = self.attributes.len(); let start = usize::from(range.start().as_u16() - 1); // handles start at 1, not 0 @@ -71,7 +71,7 @@ impl AttributeProvider for BatteryServiceAttrs { uuid == Uuid16(0x2800) // FIXME not characteristics? } - fn group_end(&self, handle: Handle) -> Option<&Attribute> { + fn group_end(&self, handle: Handle) -> Option<&Attribute>> { match handle.as_u16() { 0x0001 => Some(&self.attributes[2]), 0x0002 => Some(&self.attributes[2]), @@ -158,7 +158,7 @@ impl AttributeProvider for MidiServiceAttrs { fn for_attrs_in_range( &mut self, range: HandleRange, - mut f: impl FnMut(&Self, &Attribute) -> Result<(), Error>, + mut f: impl FnMut(&Self, &Attribute>) -> Result<(), Error>, ) -> Result<(), Error> { let count = self.attributes.len(); let start = usize::from(range.start().as_u16() - 1); // handles start at 1, not 0 @@ -181,7 +181,7 @@ impl AttributeProvider for MidiServiceAttrs { uuid == Uuid16(0x2800) // FIXME not characteristics? } - fn group_end(&self, handle: Handle) -> Option<&Attribute> { + fn group_end(&self, handle: Handle) -> Option<&Attribute>> { match handle.as_u16() { 0x0001 => Some(&self.attributes[3]), 0x0002 => Some(&self.attributes[3]), From 3b23c5390c7c557b416500fd7fbec5b26b60d746 Mon Sep 17 00:00:00 2001 From: noloerino Date: Sun, 27 Dec 2020 20:06:02 -0800 Subject: [PATCH 11/20] Lazily generate attributes for LED demo --- demos/nrf52-writeable-attributes/src/main.rs | 84 ++++++++++++++------ rubble/src/att/server.rs | 13 ++- 2 files changed, 71 insertions(+), 26 deletions(-) diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs index 6f0b15a8..231414f4 100644 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -1,3 +1,5 @@ +//! Starts a GATT server that exposes a single attribute which, when written to, +//! toggles an LED on/off (on pin 17 by default). #![no_std] #![no_main] @@ -34,17 +36,19 @@ use rubble_nrf5x::{ }; pub struct LedBlinkAttrs { - // State and resources to be modified/queried when packets are received + // Attributes exposed to clients that don't change. + // This includes the "primary service" and "characteristic" attributes. + static_attributes: [Attribute<&'static [u8]>; 3], + // State and resources to be modified/queried when packets are received. + // The AttributeValueProvider allows attributes to be generated lazily; those attributes should + // use these fields. led_pin: Pin>, - // Attributes exposed to clients - attributes: [Attribute<&'static [u8]>; 2], - // These attributes are also exposed to the client, but because they are writeable - // it is easiest for this struct to take ownership of the data - owned_attributes: [Attribute<[u8; 1]>; 1], + led_buf: [u8; 1], } const PRIMARY_SERVICE_UUID16: Uuid16 = Uuid16(0x2800); const CHARACTERISTIC_UUID16: Uuid16 = Uuid16(0x2803); +const GENERIC_ATTRIBUTE_UUID16: Uuid16 = Uuid16(0x1801); // TODO what UUID should this be? I took this from a course assignment :P // 32e61089-2b22-4db5-a914-43ce41986c70 @@ -81,10 +85,11 @@ const LED_CHAR_DECL_VALUE: [u8; 19] = [ ]; impl LedBlinkAttrs { - fn new(led_pin: Pin>) -> Self { + fn new(mut led_pin: Pin>) -> Self { + // Turn off by default (active low) + led_pin.set_high().unwrap(); Self { - led_pin, - attributes: [ + static_attributes: [ Attribute::new( PRIMARY_SERVICE_UUID16.into(), Handle::from_raw(0x0001), @@ -95,19 +100,32 @@ impl LedBlinkAttrs { Handle::from_raw(0x0002), &LED_CHAR_DECL_VALUE, ), - ], - owned_attributes: [ - // Characteristic value + // Dummy ending attribute + // This needs to come after our lazily generated data attribute because group_end() + // needs to return a reference Attribute::new( - Uuid128::from_bytes(LED_STATE_CHAR_UUID128).into(), - Handle::from_raw(0x0003), - [0u8], + GENERIC_ATTRIBUTE_UUID16.into(), + Handle::from_raw(0x0004), + &[], ), ], + led_pin, + led_buf: [0u8], } } } +impl LedBlinkAttrs { + // Lazily produces an attribute to be read/written, representing the LED state. + fn led_data_attr(&self) -> Attribute<[u8; 1]> { + Attribute::new( + Uuid128::from_bytes(LED_STATE_CHAR_UUID128).into(), + Handle::from_raw(0x0003), + self.led_buf, + ) + } +} + impl AttributeProvider for LedBlinkAttrs { /// Retrieves the permissions for attribute with the given handle. fn attr_access_permissions(&self, handle: Handle) -> AttributeAccessPermissions { @@ -122,10 +140,10 @@ impl AttributeProvider for LedBlinkAttrs { fn write_attr(&mut self, handle: Handle, data: &[u8]) -> Result<(), Error> { match handle.as_u16() { 0x0003 => { + rprintln!("Received data: {:#?}", data); if data.is_empty() { return Err(Error::InvalidLength); } - rprintln!("Received data: {:#?}", data); // If we receive a 1, activate the LED; otherwise deactivate it // Assumes LED is active low if data[0] == 1 { @@ -135,7 +153,8 @@ impl AttributeProvider for LedBlinkAttrs { rprintln!("Setting LED low"); self.led_pin.set_high().unwrap(); } - self.owned_attributes[0].value.copy_from_slice(data); + // Copy written value into buffer to display back for reading + self.led_buf.copy_from_slice(data); Ok(()) } _ => panic!("Attempted to write an unwriteable attribute"), @@ -149,19 +168,23 @@ impl AttributeProvider for LedBlinkAttrs { fn group_end(&self, handle: Handle) -> Option<&Attribute>> { match handle.as_u16() { // Handles for the primary service and characteristic - 0x0001 | 0x0002 => Some(&self.owned_attributes[0]), + // The group end is a dummy attribute; strictly speaking it's not required + // but we can't use the lazily generated attribute because this funtion requires + // returning a reference + 0x0001 | 0x0002 => Some(&self.static_attributes[2]), _ => None, } } - // Boilerplate to apply a function to all attributes with handles within the specified range - // This was copied from the implementation of gatt:BatteryServiceAttrs + /// Boilerplate to apply a function to all attributes with handles within the specified range + /// This was copied from the implementation of gatt:BatteryServiceAttrs + /// with some slight modifications to allow for lazily generated attributes fn for_attrs_in_range( &mut self, range: HandleRange, mut f: impl FnMut(&Self, &Attribute>) -> Result<(), Error>, ) -> Result<(), Error> { - let count = self.attributes.len(); + let count = self.static_attributes.len(); let start = usize::from(range.start().as_u16() - 1); // handles start at 1, not 0 let end = usize::from(range.end().as_u16() - 1); @@ -169,12 +192,20 @@ impl AttributeProvider for LedBlinkAttrs { &[] } else { let end = cmp::min(count - 1, end); - &self.attributes[start..=end] + &self.static_attributes[start..=end] }; for attr in attrs { f(self, attr)?; } + // Check lazy attributes + // Note that with this implementation, if a static attribute has handle greater than a + // lazy attribute, the order in which f() is applied is not preserved. + // This may matter for the purposes of short-circuiting an operation if it cannot be applied + // to a particular attribute + if (start..=end).contains(&0x0003) { + f(self, &self.led_data_attr())?; + }; Ok(()) } } @@ -235,19 +266,22 @@ const APP: () = { // Create the actual BLE stack objects let mut ble_ll = LinkLayer::::new(device_address, ble_timer); + // Assumes pin 17 corresponds to an LED. + // On the NRF52DK board, this is LED 1. let ble_r = Responder::new( tx, rx, L2CAPState::new(BleChannelMap::with_attributes(LedBlinkAttrs::new( - p0.p0_23.into_push_pull_output(Level::High).degrade(), + p0.p0_17.into_push_pull_output(Level::High).degrade(), ))), ); // Send advertisement and set up regular interrupt + let name = "Rubble Write Demo"; let next_update = ble_ll .start_advertise( Duration::from_millis(1000), - &[AdStructure::CompleteLocalName("CONCVRRENS CERTA CELERIS")], + &[AdStructure::CompleteLocalName(name)], &mut radio, tx_cons, rx_prod, @@ -255,7 +289,7 @@ const APP: () = { .unwrap(); ble_ll.timer().configure_interrupt(next_update); - rprintln!("begin advertising"); + rprintln!("Advertising with name '{}'", name); init::LateResources { radio, diff --git a/rubble/src/att/server.rs b/rubble/src/att/server.rs index c2f5080c..4cf56c64 100644 --- a/rubble/src/att/server.rs +++ b/rubble/src/att/server.rs @@ -223,7 +223,18 @@ impl AttributeServer { AttPdu::WriteReq { value, handle } => { if self.attrs.attr_access_permissions(*handle).is_writeable() { - self.attrs.write_attr(*handle, value.as_ref()).unwrap(); + self.attrs + .write_attr(*handle, value.as_ref()) + .map_err(|err| { + // Convert rubble::Error to AttError + AttError::new( + match err { + Error::InvalidLength => ErrorCode::InvalidAttributeValueLength, + _ => ErrorCode::UnlikelyError, + }, + *handle, + ) + })?; responder .send_with(|writer| -> Result<(), Error> { writer.write_u8(Opcode::WriteRsp.into())?; From 033b1563f2795e1e99c3a98293b0f5f968289764 Mon Sep 17 00:00:00 2001 From: noloerino Date: Sun, 27 Dec 2020 20:11:09 -0800 Subject: [PATCH 12/20] Uncomment features for boards besides 52832 --- demos/nrf52-writeable-attributes/Cargo.toml | 20 +++++++++----------- demos/nrf52-writeable-attributes/src/main.rs | 10 +++++----- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/demos/nrf52-writeable-attributes/Cargo.toml b/demos/nrf52-writeable-attributes/Cargo.toml index 2b74f9df..4e0d5f2e 100644 --- a/demos/nrf52-writeable-attributes/Cargo.toml +++ b/demos/nrf52-writeable-attributes/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Jonathan Shi "] -description = "Rubble BLE demo with writeable attributes nRF52 MCU family" +description = "Rubble BLE demo for the nRF52 MCU family with writeable attributes" categories = ["embedded", "no-std"] keywords = ["arm", "nrf", "bluetooth", "low", "energy"] repository = "https://github.com/jonas-schievink/rubble/" @@ -12,18 +12,16 @@ publish = false [dependencies] rubble = { path = "../../rubble", default-features = false } -# TODO make feature generic -rubble-nrf5x = { path = "../../rubble-nrf5x", features = ["52832"] } +rubble-nrf5x = { path = "../../rubble-nrf5x" } demo-utils = { path = "../demo-utils" } cortex-m = "0.6.1" cortex-m-rtic = "0.5.3" cortex-m-rt = "0.6.11" rtt-target = { version = "0.2.0", features = ["cortex-m"] } -# TODO uncomment the other versions as well -# nrf52810-hal = { version = "0.12", features = ["rt"], optional = true } -nrf52832-hal = { version = "0.12", features = ["rt"] } -# nrf52840-hal = { version = "0.12", features = ["rt"], optional = true } +nrf52810-hal = { version = "0.12", features = ["rt"], optional = true } +nrf52832-hal = { version = "0.12", features = ["rt"], optional = true } +nrf52840-hal = { version = "0.12", features = ["rt"], optional = true } # Disable documentation to avoid spurious rustdoc warnings [[bin]] @@ -31,7 +29,7 @@ name = "nrf52-writeable-attributes" doc = false test = false -# [features] -# 52810 = ["rubble-nrf5x/52810", "nrf52810-hal"] -# 52832 = ["rubble-nrf5x/52832", "nrf52832-hal"] -# 52840 = ["rubble-nrf5x/52840", "nrf52840-hal"] +[features] +52810 = ["rubble-nrf5x/52810", "nrf52810-hal"] +52832 = ["rubble-nrf5x/52832", "nrf52832-hal"] +52840 = ["rubble-nrf5x/52840", "nrf52840-hal"] diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs index 231414f4..485f41b6 100644 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -3,12 +3,12 @@ #![no_std] #![no_main] -// #[cfg(feature = "52810")] -// use nrf52810_hal as hal; -// #[cfg(feature = "52832")] +#[cfg(feature = "52810")] +use nrf52810_hal as hal; +#[cfg(feature = "52832")] use nrf52832_hal as hal; -// #[cfg(feature = "52840")] -// use nrf52840_hal as hal; +#[cfg(feature = "52840")] +use nrf52840_hal as hal; use core::cmp; use core::sync::atomic::{compiler_fence, Ordering}; From 2f0532aa49613525d17d2054e67387dd592c2d9b Mon Sep 17 00:00:00 2001 From: noloerino Date: Sun, 27 Dec 2020 20:31:06 -0800 Subject: [PATCH 13/20] Clarify comment on read perm issue --- rubble/src/att/server.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rubble/src/att/server.rs b/rubble/src/att/server.rs index 4cf56c64..5e4195a6 100644 --- a/rubble/src/att/server.rs +++ b/rubble/src/att/server.rs @@ -203,8 +203,13 @@ impl AttributeServer { self.attrs.for_attrs_in_range( HandleRange::new(*handle, *handle), |_provider, attr| { - // FIXME short circuit if attribute is not readable - // Err(AttError::new(ErrorCode::ReadNotPermitted, *handle)) + // FIXME return if attribute is not readable + // This code currently doesn't work because the callback should + // return rubble::Error rather than an AtError + // if !self.attrs.attr_access_permissions(*handle).is_readable() { + // return + // Err(AttError::new(ErrorCode::ReadNotPermitted, *handle)) + // } let value = if writer.space_left() < attr.value.as_ref().len() { &attr.value.as_ref()[..writer.space_left()] } else { From 22b7317e3e06c043cb2dabfd71d7af57d610ad0d Mon Sep 17 00:00:00 2001 From: noloerino Date: Sun, 27 Dec 2020 20:35:41 -0800 Subject: [PATCH 14/20] Remove extra as_ref --- rubble/src/gatt/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rubble/src/gatt/mod.rs b/rubble/src/gatt/mod.rs index 94b0738b..dc491fdc 100644 --- a/rubble/src/gatt/mod.rs +++ b/rubble/src/gatt/mod.rs @@ -22,7 +22,7 @@ impl BatteryServiceAttrs { Attribute::new( Uuid16(0x2800).into(), // "Primary Service" Handle::from_raw(0x0001), - &[0x0F, 0x18].as_ref(), // "Battery Service" = 0x180F + &[0x0F, 0x18], // "Battery Service" = 0x180F ), Attribute::new( Uuid16(0x2803).into(), // "Characteristic" From 7c56078c3e9e908ab4e4ef72477adbdbaa61d03a Mon Sep 17 00:00:00 2001 From: noloerino Date: Sun, 27 Dec 2020 21:11:12 -0800 Subject: [PATCH 15/20] Fix missing nrf52833 in write demo --- demos/nrf52-writeable-attributes/Cargo.toml | 2 ++ demos/nrf52-writeable-attributes/src/main.rs | 4 +++- rubble/src/att/mod.rs | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/demos/nrf52-writeable-attributes/Cargo.toml b/demos/nrf52-writeable-attributes/Cargo.toml index 4e0d5f2e..5230bc70 100644 --- a/demos/nrf52-writeable-attributes/Cargo.toml +++ b/demos/nrf52-writeable-attributes/Cargo.toml @@ -21,6 +21,7 @@ rtt-target = { version = "0.2.0", features = ["cortex-m"] } nrf52810-hal = { version = "0.12", features = ["rt"], optional = true } nrf52832-hal = { version = "0.12", features = ["rt"], optional = true } +nrf52833-hal = { version = "0.12", features = ["rt"], optional = true } nrf52840-hal = { version = "0.12", features = ["rt"], optional = true } # Disable documentation to avoid spurious rustdoc warnings @@ -32,4 +33,5 @@ test = false [features] 52810 = ["rubble-nrf5x/52810", "nrf52810-hal"] 52832 = ["rubble-nrf5x/52832", "nrf52832-hal"] +52833 = ["rubble-nrf5x/52833", "nrf52833-hal"] 52840 = ["rubble-nrf5x/52840", "nrf52840-hal"] diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs index 485f41b6..fd313968 100644 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -7,6 +7,8 @@ use nrf52810_hal as hal; #[cfg(feature = "52832")] use nrf52832_hal as hal; +#[cfg(feature = "52833")] +use nrf52833_hal as hal; #[cfg(feature = "52840")] use nrf52840_hal as hal; @@ -130,7 +132,7 @@ impl AttributeProvider for LedBlinkAttrs { /// Retrieves the permissions for attribute with the given handle. fn attr_access_permissions(&self, handle: Handle) -> AttributeAccessPermissions { match handle.as_u16() { - 0x0003 => AttributeAccessPermissions::ReadableAndWritable, + 0x0003 => AttributeAccessPermissions::ReadableAndWriteable, _ => AttributeAccessPermissions::Readable, } } diff --git a/rubble/src/att/mod.rs b/rubble/src/att/mod.rs index 5f6c9b86..43acbf50 100644 --- a/rubble/src/att/mod.rs +++ b/rubble/src/att/mod.rs @@ -80,21 +80,21 @@ impl> Attribute { pub enum AttributeAccessPermissions { Readable, Writeable, - ReadableAndWritable, + ReadableAndWriteable, } impl AttributeAccessPermissions { fn is_readable(&self) -> bool { match self { AttributeAccessPermissions::Readable - | AttributeAccessPermissions::ReadableAndWritable => true, + | AttributeAccessPermissions::ReadableAndWriteable => true, AttributeAccessPermissions::Writeable => false, } } fn is_writeable(&self) -> bool { match self { AttributeAccessPermissions::Writeable - | AttributeAccessPermissions::ReadableAndWritable => true, + | AttributeAccessPermissions::ReadableAndWriteable => true, AttributeAccessPermissions::Readable => false, } } @@ -160,7 +160,7 @@ pub trait AttributeProvider { /// This will only be called on handles for which /// `attribute_access_permissions` returns /// [`AttributeAccessPermissions::Writeable`] - /// or [`AttributeAccessPermission::ReadableAndWriteable`]. + /// or [`AttributeAccessPermissions::ReadableAndWriteable`]. /// /// By default, panics on all writes. This must be overwritten if /// `attribute_access_permissions` is. From 1fb57521a5fa03e19f3071b629cb61e22f5f46ac Mon Sep 17 00:00:00 2001 From: noloerino Date: Mon, 28 Dec 2020 12:49:06 -0800 Subject: [PATCH 16/20] Randomly generate UUID for led demo --- demos/nrf52-writeable-attributes/src/main.rs | 27 +++++--------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs index fd313968..963a94cf 100644 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -52,14 +52,14 @@ const PRIMARY_SERVICE_UUID16: Uuid16 = Uuid16(0x2800); const CHARACTERISTIC_UUID16: Uuid16 = Uuid16(0x2803); const GENERIC_ATTRIBUTE_UUID16: Uuid16 = Uuid16(0x1801); -// TODO what UUID should this be? I took this from a course assignment :P -// 32e61089-2b22-4db5-a914-43ce41986c70 +// Randomly generated +// a86a62f0-5d26-4538-b364-5654961515c9 const LED_UUID128: [u8; 16] = [ - 0x70, 0x6C, 0x98, 0x41, 0xCE, 0x43, 0x14, 0xA9, 0xB5, 0x4D, 0x22, 0x2B, 0x89, 0x10, 0xE6, 0x32, + 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF0, 0x62, 0x6A, 0xA8 ]; -// Replace bytes 12/13 (0x1089) of the 128-bit UUID with 0x108A +// Replace bytes 12/13 (0x62F0) of the 128-bit UUID with 62F1 const LED_STATE_CHAR_UUID128: [u8; 16] = [ - 0x70, 0x6C, 0x98, 0x41, 0xCE, 0x43, 0x14, 0xA9, 0xB5, 0x4D, 0x22, 0x2B, 0x8A, 0x10, 0xE6, 0x32, + 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, 0x6A, 0xA8 ]; const LED_CHAR_DECL_VALUE: [u8; 19] = [ @@ -68,22 +68,7 @@ const LED_CHAR_DECL_VALUE: [u8; 19] = [ 0x03, 0x00, // 128-bit UUID of characteristic value (copied from above constant) - 0x70, - 0x6C, - 0x98, - 0x41, - 0xCE, - 0x43, - 0x14, - 0xA9, - 0xB5, - 0x4D, - 0x22, - 0x2B, - 0x8A, - 0x10, - 0xE6, - 0x32, + 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, 0x6A, 0xA8 ]; impl LedBlinkAttrs { From 189e0ee863c0930debf5f51a1df6d717f66d79fc Mon Sep 17 00:00:00 2001 From: noloerino Date: Mon, 28 Dec 2020 12:57:47 -0800 Subject: [PATCH 17/20] Forgot to run rustfmt --- demos/nrf52-writeable-attributes/src/main.rs | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs index 963a94cf..65efeadf 100644 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ b/demos/nrf52-writeable-attributes/src/main.rs @@ -55,11 +55,11 @@ const GENERIC_ATTRIBUTE_UUID16: Uuid16 = Uuid16(0x1801); // Randomly generated // a86a62f0-5d26-4538-b364-5654961515c9 const LED_UUID128: [u8; 16] = [ - 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF0, 0x62, 0x6A, 0xA8 + 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF0, 0x62, 0x6A, 0xA8, ]; // Replace bytes 12/13 (0x62F0) of the 128-bit UUID with 62F1 const LED_STATE_CHAR_UUID128: [u8; 16] = [ - 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, 0x6A, 0xA8 + 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, 0x6A, 0xA8, ]; const LED_CHAR_DECL_VALUE: [u8; 19] = [ @@ -68,7 +68,22 @@ const LED_CHAR_DECL_VALUE: [u8; 19] = [ 0x03, 0x00, // 128-bit UUID of characteristic value (copied from above constant) - 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, 0x6A, 0xA8 + 0xC9, + 0x15, + 0x15, + 0x96, + 0x54, + 0x56, + 0x64, + 0xB3, + 0x38, + 0x45, + 0x26, + 0x5D, + 0xF1, + 0x62, + 0x6A, + 0xA8, ]; impl LedBlinkAttrs { From aa11fb92850f352c40c489171c5d4aff008b4a4e Mon Sep 17 00:00:00 2001 From: noloerino Date: Tue, 5 Jan 2021 13:18:54 -0800 Subject: [PATCH 18/20] Merge writeable demo with original demo --- demos/nrf52-demo/Cargo.toml | 1 + demos/nrf52-demo/README.md | 2 + demos/nrf52-demo/src/attrs.rs | 213 ++++++++++ demos/nrf52-demo/src/main.rs | 45 ++- .../nrf52-writeable-attributes/.cargo/config | 8 - demos/nrf52-writeable-attributes/Cargo.toml | 37 -- demos/nrf52-writeable-attributes/src/main.rs | 372 ------------------ 7 files changed, 248 insertions(+), 430 deletions(-) create mode 100644 demos/nrf52-demo/src/attrs.rs delete mode 100644 demos/nrf52-writeable-attributes/.cargo/config delete mode 100644 demos/nrf52-writeable-attributes/Cargo.toml delete mode 100644 demos/nrf52-writeable-attributes/src/main.rs diff --git a/demos/nrf52-demo/Cargo.toml b/demos/nrf52-demo/Cargo.toml index 91b86db8..ceb0db3f 100644 --- a/demos/nrf52-demo/Cargo.toml +++ b/demos/nrf52-demo/Cargo.toml @@ -19,6 +19,7 @@ cortex-m-rtic = "0.5.3" cortex-m-rt = "0.6.11" panic-semihosting = "0.5.3" bbqueue = "0.4.1" +rtt-target = { version = "0.2.0", features = ["cortex-m"] } nrf52810-hal = { version = "0.12", features = ["rt"], optional = true } nrf52832-hal = { version = "0.12", features = ["rt"], optional = true } diff --git a/demos/nrf52-demo/README.md b/demos/nrf52-demo/README.md index 05764600..b4792e43 100644 --- a/demos/nrf52-demo/README.md +++ b/demos/nrf52-demo/README.md @@ -2,6 +2,8 @@ This is a demo application we're using when hacking on Rubble. It runs on any chip in the nRF52 family. +It exposes a dummy read-only battery service attribute, as well as a read/write attribute that +toggles an on-board LED (pin 17 by default). The demo allows establishing a connection and provides a GATT server. A *lot* of things are logged over the UART, so it's recommended to hook that up. diff --git a/demos/nrf52-demo/src/attrs.rs b/demos/nrf52-demo/src/attrs.rs new file mode 100644 index 00000000..322999ed --- /dev/null +++ b/demos/nrf52-demo/src/attrs.rs @@ -0,0 +1,213 @@ +//! Defines a custom `AttributeValueProvider`. + +#[cfg(feature = "52810")] +use nrf52810_hal as hal; +#[cfg(feature = "52832")] +use nrf52832_hal as hal; +#[cfg(feature = "52833")] +use nrf52833_hal as hal; +#[cfg(feature = "52840")] +use nrf52840_hal as hal; + +use hal::{ + gpio::{Output, Pin, PushPull}, + prelude::OutputPin, +}; +use rubble::{ + att::{AttUuid, Attribute, AttributeAccessPermissions, AttributeProvider, Handle, HandleRange}, + uuid::{Uuid128, Uuid16}, + Error, +}; + +pub struct LedBlinkAttrs { + // Attributes exposed to clients that don't change. + // This includes the "primary service" and "characteristic" attributes. + // Some attributes are copied from the declaration of `BatteryServiceAttrs` in the gatt module. + static_attributes: [Attribute<&'static [u8]>; 6], + // State and resources to be modified/queried when packets are received. + // The `AttributeValueProvider` interface allows attributes to be generated lazily; those + // attributes should use these fields. + led_pin: Pin>, + led_buf: [u8; 1], +} + +const PRIMARY_SERVICE_UUID16: Uuid16 = Uuid16(0x2800); +const CHARACTERISTIC_UUID16: Uuid16 = Uuid16(0x2803); +const GENERIC_ATTRIBUTE_UUID16: Uuid16 = Uuid16(0x1801); +const BATTERY_LEVEL_UUID16: Uuid16 = Uuid16(0x2A19); + +// Randomly generated +// a86a62f0-5d26-4538-b364-5654961515c9 +const LED_UUID128: [u8; 16] = [ + 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF0, 0x62, 0x6A, 0xA8, +]; +// Replace bytes 12/13 (0x62F0) of the 128-bit UUID with 62F1 +const LED_STATE_CHAR_UUID128: [u8; 16] = [ + 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, 0x6A, 0xA8, +]; + +const LED_CHAR_DECL_VALUE: [u8; 19] = [ + 0x02 | 0x08, // 0x02 = read, 0x08 = write with response + // 2 byte handle pointing to characteristic value + 0x03, + 0x00, + // 128-bit UUID of characteristic value (copied from above constant) + 0xC9, + 0x15, + 0x15, + 0x96, + 0x54, + 0x56, + 0x64, + 0xB3, + 0x38, + 0x45, + 0x26, + 0x5D, + 0xF1, + 0x62, + 0x6A, + 0xA8, +]; + +impl LedBlinkAttrs { + pub fn new(mut led_pin: Pin>) -> Self { + // Turn off by default (active low) + led_pin.set_high().unwrap(); + Self { + static_attributes: [ + Attribute::new( + PRIMARY_SERVICE_UUID16.into(), + Handle::from_raw(0x0001), + &LED_UUID128, + ), + Attribute::new( + CHARACTERISTIC_UUID16.into(), + Handle::from_raw(0x0002), + &LED_CHAR_DECL_VALUE, + ), + // 0x0003 is skipped because it's lazily generated + // Dummy ending attribute + // This needs to come after our lazily generated data attribute because group_end() + // needs to return a reference + Attribute::new( + GENERIC_ATTRIBUTE_UUID16.into(), + Handle::from_raw(0x0004), + &[], + ), + // Below is copied from `gatt::BatteryServiceAttrs` + Attribute::new( + PRIMARY_SERVICE_UUID16.into(), + Handle::from_raw(0x0005), + &[0x0F, 0x18], // "Battery Service" = 0x180F + ), + Attribute::new( + CHARACTERISTIC_UUID16.into(), + Handle::from_raw(0x0006), + &[ + 0x02, // 1 byte properties: READ = 0x02 + 0x07, 0x00, // 2 bytes handle = 0x0007 + 0x19, 0x2A, // 2 bytes UUID = 0x2A19 (Battery Level) + ], + ), + // Characteristic value (Battery Level) + Attribute::new( + BATTERY_LEVEL_UUID16.into(), + Handle::from_raw(0x0007), + &[48u8], + ), + ], + led_pin, + led_buf: [0u8], + } + } +} + +impl LedBlinkAttrs { + // Lazily produces an attribute to be read/written, representing the LED state. + fn led_data_attr(&self) -> Attribute<[u8; 1]> { + Attribute::new( + Uuid128::from_bytes(LED_STATE_CHAR_UUID128).into(), + Handle::from_raw(0x0003), + self.led_buf, + ) + } +} + +impl AttributeProvider for LedBlinkAttrs { + /// Retrieves the permissions for attribute with the given handle. + fn attr_access_permissions(&self, handle: Handle) -> AttributeAccessPermissions { + match handle.as_u16() { + 0x0003 => AttributeAccessPermissions::ReadableAndWriteable, + _ => AttributeAccessPermissions::Readable, + } + } + + /// Attempts to write data to the attribute with the given handle. + /// If any of your attributes are writeable, this function must be implemented. + fn write_attr(&mut self, handle: Handle, data: &[u8]) -> Result<(), Error> { + match handle.as_u16() { + 0x0003 => { + if data.is_empty() { + return Err(Error::InvalidLength); + } + // If we receive a 1, activate the LED; otherwise deactivate it + // Assumes LED is active low + if data[0] == 1 { + self.led_pin.set_low().unwrap(); + } else { + self.led_pin.set_high().unwrap(); + } + // Copy written value into buffer to display back for reading + self.led_buf.copy_from_slice(data); + Ok(()) + } + _ => panic!("Attempted to write an unwriteable attribute"), + } + } + + fn is_grouping_attr(&self, uuid: AttUuid) -> bool { + uuid == PRIMARY_SERVICE_UUID16 || uuid == CHARACTERISTIC_UUID16 + } + + fn group_end(&self, handle: Handle) -> Option<&Attribute>> { + match handle.as_u16() { + // Handles for the LED primary service and characteristic + // The group end is a dummy attribute; strictly speaking it's not required + // but we can't use the lazily generated attribute because this funtion requires + // returning a reference + 0x0001 | 0x0002 => Some(&self.static_attributes[2]), + // Handles for Battery Service + 0x0005 | 0x0006 => Some(&self.static_attributes[5]), + _ => None, + } + } + + /// Applies a function to all attributes with handles within the specified range + fn for_attrs_in_range( + &mut self, + range: HandleRange, + mut f: impl FnMut(&Self, &Attribute>) -> Result<(), Error>, + ) -> Result<(), Error> { + // Handles start at 1, not 0, but we're not directly indexing + let start = range.start().as_u16(); + let end = range.end().as_u16(); + let range_u16 = start..=end; + // Can't just iterate from start to end because of the presence of lazy attributes + // Ranges are empty if start >= end + for attr in &self.static_attributes { + if range_u16.contains(&attr.handle.as_u16()) { + f(self, attr)?; + } + } + // Check lazy attributes + // Note that with this implementation, if a static attribute has handle greater than a + // lazy attribute, the order in which f() is applied is not preserved. + // This may matter for the purposes of short-circuiting an operation if it cannot be applied + // to a particular attribute + if range_u16.contains(&0x0003) { + f(self, &self.led_data_attr())?; + }; + Ok(()) + } +} diff --git a/demos/nrf52-demo/src/main.rs b/demos/nrf52-demo/src/main.rs index 257dfb6c..eb8c9f50 100644 --- a/demos/nrf52-demo/src/main.rs +++ b/demos/nrf52-demo/src/main.rs @@ -35,6 +35,7 @@ macro_rules! config { #[macro_use] mod config; +mod attrs; mod logger; // Import the right HAL/PAC crate, depending on the target chip @@ -48,24 +49,38 @@ use nrf52833_hal as hal; use nrf52840_hal as hal; use bbqueue::Consumer; -use core::fmt::Write; -use core::sync::atomic::{compiler_fence, Ordering}; -use hal::uarte::{Baudrate, Parity, Uarte}; -use hal::{gpio::Level, pac::UARTE0}; -use rubble::l2cap::{BleChannelMap, L2CAPState}; -use rubble::link::queue::{PacketQueue, SimpleQueue}; -use rubble::link::{ad_structure::AdStructure, LinkLayer, Responder, MIN_PDU_BUF}; -use rubble::time::{Duration, Timer}; -use rubble::{config::Config, gatt::BatteryServiceAttrs, security::NoSecurity}; -use rubble_nrf5x::radio::{BleRadio, PacketBuffer}; -use rubble_nrf5x::{timer::BleTimer, utils::get_device_address}; +use core::{ + fmt::Write, + sync::atomic::{compiler_fence, Ordering}, +}; +use hal::{ + gpio::Level, + pac::UARTE0, + uarte::{Baudrate, Parity, Uarte}, +}; +use rubble::{ + config::Config, + l2cap::{BleChannelMap, L2CAPState}, + link::{ + ad_structure::AdStructure, + queue::{PacketQueue, SimpleQueue}, + LinkLayer, Responder, MIN_PDU_BUF, + }, + security::NoSecurity, + time::{Duration, Timer}, +}; +use rubble_nrf5x::{ + radio::{BleRadio, PacketBuffer}, + timer::BleTimer, + utils::get_device_address, +}; pub enum AppConfig {} impl Config for AppConfig { type Timer = BleTimer; type Transmitter = BleRadio; - type ChannelMapper = BleChannelMap; + type ChannelMapper = BleChannelMap; type PacketQueue = &'static mut SimpleQueue; } @@ -121,10 +136,14 @@ const APP: () = { // Create the actual BLE stack objects let mut ble_ll = LinkLayer::::new(device_address, ble_timer); + // Assumes pin 17 corresponds to an LED. + // On the NRF52DK board, this is LED 1. let ble_r = Responder::new( tx, rx, - L2CAPState::new(BleChannelMap::with_attributes(BatteryServiceAttrs::new())), + L2CAPState::new(BleChannelMap::with_attributes(attrs::LedBlinkAttrs::new( + p0.p0_17.into_push_pull_output(Level::High).degrade(), + ))), ); // Send advertisement and set up regular interrupt diff --git a/demos/nrf52-writeable-attributes/.cargo/config b/demos/nrf52-writeable-attributes/.cargo/config deleted file mode 100644 index 7a738d65..00000000 --- a/demos/nrf52-writeable-attributes/.cargo/config +++ /dev/null @@ -1,8 +0,0 @@ -[build] -target = 'thumbv7em-none-eabi' - -[target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = 'arm-none-eabi-gdb' -rustflags = [ - "-C", "link-arg=-Tlink.x", -] diff --git a/demos/nrf52-writeable-attributes/Cargo.toml b/demos/nrf52-writeable-attributes/Cargo.toml deleted file mode 100644 index 5230bc70..00000000 --- a/demos/nrf52-writeable-attributes/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -authors = ["Jonathan Shi "] -description = "Rubble BLE demo for the nRF52 MCU family with writeable attributes" -categories = ["embedded", "no-std"] -keywords = ["arm", "nrf", "bluetooth", "low", "energy"] -repository = "https://github.com/jonas-schievink/rubble/" -license = "0BSD" -name = "nrf52-writeable-attributes" -version = "0.0.0" -edition = "2018" -publish = false - -[dependencies] -rubble = { path = "../../rubble", default-features = false } -rubble-nrf5x = { path = "../../rubble-nrf5x" } -demo-utils = { path = "../demo-utils" } -cortex-m = "0.6.1" -cortex-m-rtic = "0.5.3" -cortex-m-rt = "0.6.11" -rtt-target = { version = "0.2.0", features = ["cortex-m"] } - -nrf52810-hal = { version = "0.12", features = ["rt"], optional = true } -nrf52832-hal = { version = "0.12", features = ["rt"], optional = true } -nrf52833-hal = { version = "0.12", features = ["rt"], optional = true } -nrf52840-hal = { version = "0.12", features = ["rt"], optional = true } - -# Disable documentation to avoid spurious rustdoc warnings -[[bin]] -name = "nrf52-writeable-attributes" -doc = false -test = false - -[features] -52810 = ["rubble-nrf5x/52810", "nrf52810-hal"] -52832 = ["rubble-nrf5x/52832", "nrf52832-hal"] -52833 = ["rubble-nrf5x/52833", "nrf52833-hal"] -52840 = ["rubble-nrf5x/52840", "nrf52840-hal"] diff --git a/demos/nrf52-writeable-attributes/src/main.rs b/demos/nrf52-writeable-attributes/src/main.rs deleted file mode 100644 index 65efeadf..00000000 --- a/demos/nrf52-writeable-attributes/src/main.rs +++ /dev/null @@ -1,372 +0,0 @@ -//! Starts a GATT server that exposes a single attribute which, when written to, -//! toggles an LED on/off (on pin 17 by default). -#![no_std] -#![no_main] - -#[cfg(feature = "52810")] -use nrf52810_hal as hal; -#[cfg(feature = "52832")] -use nrf52832_hal as hal; -#[cfg(feature = "52833")] -use nrf52833_hal as hal; -#[cfg(feature = "52840")] -use nrf52840_hal as hal; - -use core::cmp; -use core::sync::atomic::{compiler_fence, Ordering}; -use hal::gpio::{Level, Output, Pin, PushPull}; -use hal::prelude::OutputPin; -use rtt_target::{rprintln, rtt_init_print}; -use rubble::{ - att::{AttUuid, Attribute, AttributeAccessPermissions, AttributeProvider, Handle, HandleRange}, - config::Config, - l2cap::{BleChannelMap, L2CAPState}, - link::{ - ad_structure::AdStructure, - queue::{PacketQueue, SimpleQueue}, - LinkLayer, Responder, MIN_PDU_BUF, - }, - security::NoSecurity, - time::{Duration, Timer}, - uuid::{Uuid128, Uuid16}, - Error, -}; -use rubble_nrf5x::{ - radio::{BleRadio, PacketBuffer}, - timer::BleTimer, - utils::get_device_address, -}; - -pub struct LedBlinkAttrs { - // Attributes exposed to clients that don't change. - // This includes the "primary service" and "characteristic" attributes. - static_attributes: [Attribute<&'static [u8]>; 3], - // State and resources to be modified/queried when packets are received. - // The AttributeValueProvider allows attributes to be generated lazily; those attributes should - // use these fields. - led_pin: Pin>, - led_buf: [u8; 1], -} - -const PRIMARY_SERVICE_UUID16: Uuid16 = Uuid16(0x2800); -const CHARACTERISTIC_UUID16: Uuid16 = Uuid16(0x2803); -const GENERIC_ATTRIBUTE_UUID16: Uuid16 = Uuid16(0x1801); - -// Randomly generated -// a86a62f0-5d26-4538-b364-5654961515c9 -const LED_UUID128: [u8; 16] = [ - 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF0, 0x62, 0x6A, 0xA8, -]; -// Replace bytes 12/13 (0x62F0) of the 128-bit UUID with 62F1 -const LED_STATE_CHAR_UUID128: [u8; 16] = [ - 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, 0x6A, 0xA8, -]; - -const LED_CHAR_DECL_VALUE: [u8; 19] = [ - 0x02 | 0x08, // 0x02 = read, 0x08 = write with response - // 2 byte handle pointing to characteristic value - 0x03, - 0x00, - // 128-bit UUID of characteristic value (copied from above constant) - 0xC9, - 0x15, - 0x15, - 0x96, - 0x54, - 0x56, - 0x64, - 0xB3, - 0x38, - 0x45, - 0x26, - 0x5D, - 0xF1, - 0x62, - 0x6A, - 0xA8, -]; - -impl LedBlinkAttrs { - fn new(mut led_pin: Pin>) -> Self { - // Turn off by default (active low) - led_pin.set_high().unwrap(); - Self { - static_attributes: [ - Attribute::new( - PRIMARY_SERVICE_UUID16.into(), - Handle::from_raw(0x0001), - &LED_UUID128, - ), - Attribute::new( - CHARACTERISTIC_UUID16.into(), - Handle::from_raw(0x0002), - &LED_CHAR_DECL_VALUE, - ), - // Dummy ending attribute - // This needs to come after our lazily generated data attribute because group_end() - // needs to return a reference - Attribute::new( - GENERIC_ATTRIBUTE_UUID16.into(), - Handle::from_raw(0x0004), - &[], - ), - ], - led_pin, - led_buf: [0u8], - } - } -} - -impl LedBlinkAttrs { - // Lazily produces an attribute to be read/written, representing the LED state. - fn led_data_attr(&self) -> Attribute<[u8; 1]> { - Attribute::new( - Uuid128::from_bytes(LED_STATE_CHAR_UUID128).into(), - Handle::from_raw(0x0003), - self.led_buf, - ) - } -} - -impl AttributeProvider for LedBlinkAttrs { - /// Retrieves the permissions for attribute with the given handle. - fn attr_access_permissions(&self, handle: Handle) -> AttributeAccessPermissions { - match handle.as_u16() { - 0x0003 => AttributeAccessPermissions::ReadableAndWriteable, - _ => AttributeAccessPermissions::Readable, - } - } - - /// Attempts to write data to the attribute with the given handle. - /// If any of your attributes are writeable, this function must be implemented. - fn write_attr(&mut self, handle: Handle, data: &[u8]) -> Result<(), Error> { - match handle.as_u16() { - 0x0003 => { - rprintln!("Received data: {:#?}", data); - if data.is_empty() { - return Err(Error::InvalidLength); - } - // If we receive a 1, activate the LED; otherwise deactivate it - // Assumes LED is active low - if data[0] == 1 { - rprintln!("Setting LED high"); - self.led_pin.set_low().unwrap(); - } else { - rprintln!("Setting LED low"); - self.led_pin.set_high().unwrap(); - } - // Copy written value into buffer to display back for reading - self.led_buf.copy_from_slice(data); - Ok(()) - } - _ => panic!("Attempted to write an unwriteable attribute"), - } - } - - fn is_grouping_attr(&self, uuid: AttUuid) -> bool { - uuid == PRIMARY_SERVICE_UUID16 || uuid == CHARACTERISTIC_UUID16 - } - - fn group_end(&self, handle: Handle) -> Option<&Attribute>> { - match handle.as_u16() { - // Handles for the primary service and characteristic - // The group end is a dummy attribute; strictly speaking it's not required - // but we can't use the lazily generated attribute because this funtion requires - // returning a reference - 0x0001 | 0x0002 => Some(&self.static_attributes[2]), - _ => None, - } - } - - /// Boilerplate to apply a function to all attributes with handles within the specified range - /// This was copied from the implementation of gatt:BatteryServiceAttrs - /// with some slight modifications to allow for lazily generated attributes - fn for_attrs_in_range( - &mut self, - range: HandleRange, - mut f: impl FnMut(&Self, &Attribute>) -> Result<(), Error>, - ) -> Result<(), Error> { - let count = self.static_attributes.len(); - let start = usize::from(range.start().as_u16() - 1); // handles start at 1, not 0 - let end = usize::from(range.end().as_u16() - 1); - - let attrs = if start >= count { - &[] - } else { - let end = cmp::min(count - 1, end); - &self.static_attributes[start..=end] - }; - - for attr in attrs { - f(self, attr)?; - } - // Check lazy attributes - // Note that with this implementation, if a static attribute has handle greater than a - // lazy attribute, the order in which f() is applied is not preserved. - // This may matter for the purposes of short-circuiting an operation if it cannot be applied - // to a particular attribute - if (start..=end).contains(&0x0003) { - f(self, &self.led_data_attr())?; - }; - Ok(()) - } -} - -pub enum AppConfig {} - -impl Config for AppConfig { - type Timer = BleTimer; - type Transmitter = BleRadio; - type ChannelMapper = BleChannelMap; - type PacketQueue = &'static mut SimpleQueue; -} - -#[rtic::app(device = crate::hal::pac, peripherals = true)] -const APP: () = { - struct Resources { - // BLE boilerplate - #[init([0; MIN_PDU_BUF])] - ble_tx_buf: PacketBuffer, - #[init([0; MIN_PDU_BUF])] - ble_rx_buf: PacketBuffer, - #[init(SimpleQueue::new())] - tx_queue: SimpleQueue, - #[init(SimpleQueue::new())] - rx_queue: SimpleQueue, - ble_ll: LinkLayer, - ble_r: Responder, - radio: BleRadio, - } - - #[init(resources = [ble_tx_buf, ble_rx_buf, tx_queue, rx_queue])] - fn init(ctx: init::Context) -> init::LateResources { - rtt_init_print!(); - rprintln!("RTT initialized"); - // On reset, the internal high frequency clock is already used, but we - // also need to switch to the external HF oscillator. This is needed - // for Bluetooth to work. - let _clocks = hal::clocks::Clocks::new(ctx.device.CLOCK).enable_ext_hfosc(); - - let ble_timer = BleTimer::init(ctx.device.TIMER0); - - let p0 = hal::gpio::p0::Parts::new(ctx.device.P0); - - // Determine device address - let device_address = get_device_address(); - - let mut radio = BleRadio::new( - ctx.device.RADIO, - &ctx.device.FICR, - ctx.resources.ble_tx_buf, - ctx.resources.ble_rx_buf, - ); - - // Create TX/RX queues - let (tx, tx_cons) = ctx.resources.tx_queue.split(); - let (rx_prod, rx) = ctx.resources.rx_queue.split(); - - // Create the actual BLE stack objects - let mut ble_ll = LinkLayer::::new(device_address, ble_timer); - - // Assumes pin 17 corresponds to an LED. - // On the NRF52DK board, this is LED 1. - let ble_r = Responder::new( - tx, - rx, - L2CAPState::new(BleChannelMap::with_attributes(LedBlinkAttrs::new( - p0.p0_17.into_push_pull_output(Level::High).degrade(), - ))), - ); - - // Send advertisement and set up regular interrupt - let name = "Rubble Write Demo"; - let next_update = ble_ll - .start_advertise( - Duration::from_millis(1000), - &[AdStructure::CompleteLocalName(name)], - &mut radio, - tx_cons, - rx_prod, - ) - .unwrap(); - - ble_ll.timer().configure_interrupt(next_update); - rprintln!("Advertising with name '{}'", name); - - init::LateResources { - radio, - ble_ll, - ble_r, - } - } - - #[task(binds = RADIO, resources = [radio, ble_ll], spawn = [ble_worker], priority = 3)] - fn radio(ctx: radio::Context) { - let ble_ll: &mut LinkLayer = ctx.resources.ble_ll; - if let Some(cmd) = ctx - .resources - .radio - .recv_interrupt(ble_ll.timer().now(), ble_ll) - { - ctx.resources.radio.configure_receiver(cmd.radio); - ble_ll.timer().configure_interrupt(cmd.next_update); - - if cmd.queued_work { - // If there's any lower-priority work to be done, ensure that happens. - // If we fail to spawn the task, it's already scheduled. - ctx.spawn.ble_worker().ok(); - } - } - } - - #[task(binds = TIMER0, resources = [radio, ble_ll], spawn = [ble_worker], priority = 3)] - fn timer0(ctx: timer0::Context) { - let timer = ctx.resources.ble_ll.timer(); - if !timer.is_interrupt_pending() { - return; - } - timer.clear_interrupt(); - - let cmd = ctx.resources.ble_ll.update_timer(ctx.resources.radio); - ctx.resources.radio.configure_receiver(cmd.radio); - - ctx.resources - .ble_ll - .timer() - .configure_interrupt(cmd.next_update); - - if cmd.queued_work { - // If there's any lower-priority work to be done, ensure that happens. - // If we fail to spawn the task, it's already scheduled. - ctx.spawn.ble_worker().ok(); - } - } - - #[idle] - fn idle(_ctx: idle::Context) -> ! { - loop { - compiler_fence(Ordering::SeqCst); - } - } - - #[task(resources = [ble_r], priority = 2)] - fn ble_worker(ctx: ble_worker::Context) { - // Fully drain the packet queue - while ctx.resources.ble_r.has_work() { - ctx.resources.ble_r.process_one().unwrap(); - } - } - - extern "C" { - fn WDT(); - } -}; - -#[panic_handler] -fn panic(e: &core::panic::PanicInfo) -> ! { - rprintln!("Unhandled panic; stopping"); - rprintln!("{}", e); - loop { - cortex_m::asm::bkpt(); - } -} From d3ed643c233740720ce707404e345492481832c4 Mon Sep 17 00:00:00 2001 From: noloerino Date: Sun, 10 Jan 2021 19:05:52 -0800 Subject: [PATCH 19/20] log instead of unwrap on failed writereq --- demos/nrf52-demo/Cargo.toml | 1 - demos/nrf52-demo/src/attrs.rs | 8 ++++---- demos/nrf52-demo/src/main.rs | 4 ++-- rubble/src/att/server.rs | 3 ++- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/demos/nrf52-demo/Cargo.toml b/demos/nrf52-demo/Cargo.toml index ceb0db3f..91b86db8 100644 --- a/demos/nrf52-demo/Cargo.toml +++ b/demos/nrf52-demo/Cargo.toml @@ -19,7 +19,6 @@ cortex-m-rtic = "0.5.3" cortex-m-rt = "0.6.11" panic-semihosting = "0.5.3" bbqueue = "0.4.1" -rtt-target = { version = "0.2.0", features = ["cortex-m"] } nrf52810-hal = { version = "0.12", features = ["rt"], optional = true } nrf52832-hal = { version = "0.12", features = ["rt"], optional = true } diff --git a/demos/nrf52-demo/src/attrs.rs b/demos/nrf52-demo/src/attrs.rs index 322999ed..5117ef0c 100644 --- a/demos/nrf52-demo/src/attrs.rs +++ b/demos/nrf52-demo/src/attrs.rs @@ -19,7 +19,7 @@ use rubble::{ Error, }; -pub struct LedBlinkAttrs { +pub struct DemoAttrs { // Attributes exposed to clients that don't change. // This includes the "primary service" and "characteristic" attributes. // Some attributes are copied from the declaration of `BatteryServiceAttrs` in the gatt module. @@ -70,7 +70,7 @@ const LED_CHAR_DECL_VALUE: [u8; 19] = [ 0xA8, ]; -impl LedBlinkAttrs { +impl DemoAttrs { pub fn new(mut led_pin: Pin>) -> Self { // Turn off by default (active low) led_pin.set_high().unwrap(); @@ -123,7 +123,7 @@ impl LedBlinkAttrs { } } -impl LedBlinkAttrs { +impl DemoAttrs { // Lazily produces an attribute to be read/written, representing the LED state. fn led_data_attr(&self) -> Attribute<[u8; 1]> { Attribute::new( @@ -134,7 +134,7 @@ impl LedBlinkAttrs { } } -impl AttributeProvider for LedBlinkAttrs { +impl AttributeProvider for DemoAttrs { /// Retrieves the permissions for attribute with the given handle. fn attr_access_permissions(&self, handle: Handle) -> AttributeAccessPermissions { match handle.as_u16() { diff --git a/demos/nrf52-demo/src/main.rs b/demos/nrf52-demo/src/main.rs index eb8c9f50..d89bdd26 100644 --- a/demos/nrf52-demo/src/main.rs +++ b/demos/nrf52-demo/src/main.rs @@ -80,7 +80,7 @@ pub enum AppConfig {} impl Config for AppConfig { type Timer = BleTimer; type Transmitter = BleRadio; - type ChannelMapper = BleChannelMap; + type ChannelMapper = BleChannelMap; type PacketQueue = &'static mut SimpleQueue; } @@ -141,7 +141,7 @@ const APP: () = { let ble_r = Responder::new( tx, rx, - L2CAPState::new(BleChannelMap::with_attributes(attrs::LedBlinkAttrs::new( + L2CAPState::new(BleChannelMap::with_attributes(attrs::DemoAttrs::new( p0.p0_17.into_push_pull_output(Level::High).degrade(), ))), ); diff --git a/rubble/src/att/server.rs b/rubble/src/att/server.rs index 5e4195a6..c7ce25cb 100644 --- a/rubble/src/att/server.rs +++ b/rubble/src/att/server.rs @@ -245,7 +245,8 @@ impl AttributeServer { writer.write_u8(Opcode::WriteRsp.into())?; Ok(()) }) - .unwrap(); + .map_err(|err| error!("error while handling write request: {:?}", err)) + .ok(); Ok(()) } else { Err(AttError::new(ErrorCode::WriteNotPermitted, *handle)) From f4922684b2c8b52c9e94ee3312290c684348b1bc Mon Sep 17 00:00:00 2001 From: noloerino Date: Sun, 10 Jan 2021 19:08:26 -0800 Subject: [PATCH 20/20] log instead of unwrap on failed write cmd --- rubble/src/att/server.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rubble/src/att/server.rs b/rubble/src/att/server.rs index c7ce25cb..db28b9d9 100644 --- a/rubble/src/att/server.rs +++ b/rubble/src/att/server.rs @@ -255,7 +255,10 @@ impl AttributeServer { AttPdu::WriteCommand { handle, value } => { // WriteCommand shouldn't respond to the client even on failure if self.attrs.attr_access_permissions(*handle).is_writeable() { - self.attrs.write_attr(*handle, value.as_ref()).unwrap(); + self.attrs + .write_attr(*handle, value.as_ref()) + .map_err(|err| error!("error while handling write command: {:?}", err)) + .ok(); } Ok(()) }