summaryrefslogtreecommitdiff
path: root/examples/src/bin/ble_dis_bas_peripheral_builder.rs
diff options
context:
space:
mode:
Diffstat (limited to 'examples/src/bin/ble_dis_bas_peripheral_builder.rs')
-rw-r--r--examples/src/bin/ble_dis_bas_peripheral_builder.rs290
1 files changed, 290 insertions, 0 deletions
diff --git a/examples/src/bin/ble_dis_bas_peripheral_builder.rs b/examples/src/bin/ble_dis_bas_peripheral_builder.rs
new file mode 100644
index 0000000..858b74b
--- /dev/null
+++ b/examples/src/bin/ble_dis_bas_peripheral_builder.rs
@@ -0,0 +1,290 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+#![feature(alloc_error_handler)]
+#![allow(incomplete_features)]
+
+#[path = "../example_common.rs"]
+mod example_common;
+
+use core::mem;
+use cortex_m_rt::entry;
+use defmt::info;
+use defmt::*;
+use embassy::executor::Executor;
+use embassy::util::Forever;
+use nrf_softdevice::ble::gatt_server::builder::ServiceBuilder;
+use nrf_softdevice::ble::gatt_server::characteristic::{Attribute, Metadata, Properties};
+use nrf_softdevice::ble::gatt_server::{CharacteristicHandles, RegisterError};
+use nrf_softdevice::ble::{gatt_server, peripheral, Connection, Uuid};
+use nrf_softdevice::{raw, Softdevice};
+
+const DEVICE_INFORMATION: Uuid = Uuid::new_16(0x180a);
+const BATTERY_SERVICE: Uuid = Uuid::new_16(0x180f);
+
+const BATTERY_LEVEL: Uuid = Uuid::new_16(0x2a19);
+const MODEL_NUMBER: Uuid = Uuid::new_16(0x2a24);
+const SERIAL_NUMBER: Uuid = Uuid::new_16(0x2a25);
+const FIRMWARE_REVISION: Uuid = Uuid::new_16(0x2a26);
+const HARDWARE_REVISION: Uuid = Uuid::new_16(0x2a27);
+const SOFTWARE_REVISION: Uuid = Uuid::new_16(0x2a28);
+const MANUFACTURER_NAME: Uuid = Uuid::new_16(0x2a29);
+const PNP_ID: Uuid = Uuid::new_16(0x2a50);
+
+static EXECUTOR: Forever<Executor> = Forever::new();
+
+#[embassy::task]
+async fn softdevice_task(sd: &'static Softdevice) {
+ sd.run().await;
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy)]
+pub enum VidSource {
+ BluetoothSIG = 1,
+ UsbIF = 2,
+}
+
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+pub struct PnPID {
+ pub vid_source: VidSource,
+ pub vendor_id: u16,
+ pub product_id: u16,
+ pub product_version: u16,
+}
+
+#[derive(Debug, Default, defmt::Format)]
+pub struct DeviceInformation {
+ pub manufacturer_name: Option<&'static str>,
+ pub model_number: Option<&'static str>,
+ pub serial_number: Option<&'static str>,
+ pub hw_rev: Option<&'static str>,
+ pub fw_rev: Option<&'static str>,
+ pub sw_rev: Option<&'static str>,
+}
+
+pub struct DeviceInformationService {}
+
+impl DeviceInformationService {
+ pub fn new(
+ sd: &mut Softdevice,
+ pnp_id: &PnPID,
+ info: DeviceInformation,
+ ) -> Result<Self, RegisterError> {
+ let mut sb = ServiceBuilder::new(sd, DEVICE_INFORMATION)?;
+
+ Self::add_pnp_characteristic(&mut sb, pnp_id)?;
+ Self::add_opt_str_characteristic(&mut sb, MANUFACTURER_NAME, info.manufacturer_name)?;
+ Self::add_opt_str_characteristic(&mut sb, MODEL_NUMBER, info.model_number)?;
+ Self::add_opt_str_characteristic(&mut sb, SERIAL_NUMBER, info.serial_number)?;
+ Self::add_opt_str_characteristic(&mut sb, HARDWARE_REVISION, info.hw_rev)?;
+ Self::add_opt_str_characteristic(&mut sb, FIRMWARE_REVISION, info.fw_rev)?;
+ Self::add_opt_str_characteristic(&mut sb, SOFTWARE_REVISION, info.sw_rev)?;
+
+ let _service_handle = sb.build();
+
+ Ok(DeviceInformationService {})
+ }
+
+ fn add_opt_str_characteristic(
+ sb: &mut ServiceBuilder,
+ uuid: Uuid,
+ val: Option<&'static str>,
+ ) -> Result<Option<CharacteristicHandles>, RegisterError> {
+ if let Some(val) = val {
+ let attr = Attribute::new(val);
+ let md = Metadata::new(Properties::new().read());
+ Ok(Some(sb.add_characteristic(uuid, attr, md)?.build()))
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn add_pnp_characteristic(
+ sb: &mut ServiceBuilder,
+ pnp_id: &PnPID,
+ ) -> Result<CharacteristicHandles, RegisterError> {
+ // SAFETY: `PnPID` is `repr(C, packed)` so viewing it as an immutable slice of bytes is safe.
+ let val = unsafe {
+ core::slice::from_raw_parts(
+ pnp_id as *const _ as *const u8,
+ core::mem::size_of::<PnPID>(),
+ )
+ };
+
+ let attr = Attribute::new(val);
+ let md = Metadata::new(Properties::new().read());
+ Ok(sb.add_characteristic(PNP_ID, attr, md)?.build())
+ }
+}
+
+pub struct BatteryService {
+ value_handle: u16,
+ cccd_handle: u16,
+}
+
+impl BatteryService {
+ pub fn new(sd: &mut Softdevice) -> Result<Self, RegisterError> {
+ let mut service_builder = ServiceBuilder::new(sd, BATTERY_SERVICE)?;
+
+ let attr = Attribute::new(&[0u8]);
+ let metadata = Metadata::new(Properties::new().read().notify());
+ let characteristic_builder =
+ service_builder.add_characteristic(BATTERY_LEVEL, attr, metadata)?;
+ let characteristic_handles = characteristic_builder.build();
+
+ let _service_handle = service_builder.build();
+
+ Ok(BatteryService {
+ value_handle: characteristic_handles.value_handle,
+ cccd_handle: characteristic_handles.cccd_handle,
+ })
+ }
+
+ pub fn battery_level_get(&self, sd: &Softdevice) -> Result<u8, gatt_server::GetValueError> {
+ let buf = &mut [0u8];
+ gatt_server::get_value(sd, self.value_handle, buf)?;
+ Ok(buf[0])
+ }
+
+ pub fn battery_level_set(
+ &self,
+ sd: &Softdevice,
+ val: u8,
+ ) -> Result<(), gatt_server::SetValueError> {
+ gatt_server::set_value(sd, self.value_handle, &[val])
+ }
+ pub fn battery_level_notify(
+ &self,
+ conn: &Connection,
+ val: u8,
+ ) -> Result<(), gatt_server::NotifyValueError> {
+ gatt_server::notify_value(conn, self.value_handle, &[val])
+ }
+
+ pub fn on_write(&self, handle: u16, data: &[u8]) {
+ if handle == self.cccd_handle && !data.is_empty() {
+ info!("battery notifications: {}", (data[0] & 0x01) != 0);
+ }
+ }
+}
+
+struct Server {
+ _dis: DeviceInformationService,
+ bas: BatteryService,
+}
+
+impl Server {
+ pub fn new(sd: &mut Softdevice, serial_number: &'static str) -> Result<Self, RegisterError> {
+ let dis = DeviceInformationService::new(
+ sd,
+ &PnPID {
+ vid_source: VidSource::UsbIF,
+ vendor_id: 0xDEAD,
+ product_id: 0xBEEF,
+ product_version: 0x0000,
+ },
+ DeviceInformation {
+ manufacturer_name: Some("Embassy"),
+ model_number: Some("M1234"),
+ serial_number: Some(serial_number),
+ ..Default::default()
+ },
+ )?;
+
+ let bas = BatteryService::new(sd)?;
+
+ Ok(Self { _dis: dis, bas })
+ }
+}
+
+impl gatt_server::Server for Server {
+ type Event = ();
+
+ fn on_write(&self, handle: u16, data: &[u8]) -> Option<Self::Event> {
+ self.bas.on_write(handle, data);
+ None
+ }
+}
+
+#[embassy::task]
+async fn bluetooth_task(sd: &'static Softdevice, server: Server) {
+ #[rustfmt::skip]
+ let adv_data = &[
+ 0x02, 0x01, raw::BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE as u8,
+ 0x03, 0x03, 0x09, 0x18,
+ 0x0a, 0x09, b'H', b'e', b'l', b'l', b'o', b'R', b'u', b's', b't',
+ ];
+ #[rustfmt::skip]
+ let scan_data = &[
+ 0x03, 0x03, 0x09, 0x18,
+ ];
+
+ loop {
+ let config = peripheral::Config::default();
+ let adv = peripheral::ConnectableAdvertisement::ScannableUndirected {
+ adv_data,
+ scan_data,
+ };
+ let conn = unwrap!(peripheral::advertise_connectable(sd, adv, &config).await);
+
+ info!("advertising done!");
+
+ // Run the GATT server on the connection. This returns when the connection gets disconnected.
+ let res = gatt_server::run(&conn, &server, |_| {}).await;
+
+ if let Err(e) = res {
+ info!("gatt_server run exited with error: {:?}", e);
+ }
+ }
+}
+
+#[entry]
+fn main() -> ! {
+ info!("Hello World!");
+
+ let config = nrf_softdevice::Config {
+ clock: Some(raw::nrf_clock_lf_cfg_t {
+ source: raw::NRF_CLOCK_LF_SRC_RC as u8,
+ rc_ctiv: 4,
+ rc_temp_ctiv: 2,
+ accuracy: 7,
+ }),
+ conn_gap: Some(raw::ble_gap_conn_cfg_t {
+ conn_count: 6,
+ event_length: 24,
+ }),
+ conn_gatt: Some(raw::ble_gatt_conn_cfg_t { att_mtu: 256 }),
+ gatts_attr_tab_size: Some(raw::ble_gatts_cfg_attr_tab_size_t {
+ attr_tab_size: 32768,
+ }),
+ gap_role_count: Some(raw::ble_gap_cfg_role_count_t {
+ adv_set_count: 1,
+ periph_role_count: 3,
+ central_role_count: 3,
+ central_sec_count: 0,
+ _bitfield_1: raw::ble_gap_cfg_role_count_t::new_bitfield_1(0),
+ }),
+ gap_device_name: Some(raw::ble_gap_cfg_device_name_t {
+ p_value: b"HelloRust" as *const u8 as _,
+ current_len: 9,
+ max_len: 9,
+ write_perm: unsafe { mem::zeroed() },
+ _bitfield_1: raw::ble_gap_cfg_device_name_t::new_bitfield_1(
+ raw::BLE_GATTS_VLOC_STACK as u8,
+ ),
+ }),
+ ..Default::default()
+ };
+
+ let sd = Softdevice::enable(&config);
+
+ let server = unwrap!(Server::new(sd, "12345678"));
+
+ let executor = EXECUTOR.put(Executor::new());
+ executor.run(move |spawner| {
+ unwrap!(spawner.spawn(softdevice_task(sd)));
+ unwrap!(spawner.spawn(bluetooth_task(sd, server)));
+ });
+}