#![no_std] #![no_main] #![feature(type_alias_impl_trait)] #[path = "../example_common.rs"] mod example_common; use core::mem; use defmt::{info, *}; use embassy_executor::Spawner; 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); #[embassy_executor::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 { 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, 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 { // 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::()) }; 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 { 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 { 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 { 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.bas.on_write(handle, data); None } } #[embassy_executor::main] async fn main(spawner: Spawner) { 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")); unwrap!(spawner.spawn(softdevice_task(sd))); #[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); } } }