use crate::att::Perms;
use crate::gatt::{Builder, Characteristic, Db, Service, ServiceDef};
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct DeviceInfoService {
manufacturer_name: Option<String>,
model_num: Option<String>,
serial_num: Option<String>,
hardware_rev: Option<String>,
firmware_rev: Option<String>,
software_rev: Option<String>,
system_id: Option<Eui64>,
regulatory_data: Option<RegulatoryData>,
pnp_id: Option<PnpId>,
}
macro_rules! with_str {
($($(#[$doc:meta])* $f:ident),*$(,)?) => {$(::paste::paste! {
$(#[$doc])*
#[inline(always)]
#[must_use]
pub fn [<with_ $f>](mut self, v: impl AsRef<str>) -> Self {
self.$f = Some(v.as_ref().to_owned());
self
}
})*}
}
impl DeviceInfoService {
#[inline(always)]
#[must_use]
pub fn new() -> Self {
Self::default()
}
with_str! {
manufacturer_name,
model_num,
serial_num,
hardware_rev,
firmware_rev,
software_rev,
}
#[inline(always)]
#[must_use]
pub const fn with_system_id(mut self, v: Eui64) -> Self {
self.system_id = Some(v);
self
}
#[allow(clippy::missing_const_for_fn)]
#[inline(always)]
#[must_use]
pub fn with_regulatory_data(mut self, v: RegulatoryData) -> Self {
self.regulatory_data = Some(v);
self
}
#[inline(always)]
#[must_use]
pub const fn with_pnp_id(mut self, v: PnpId) -> Self {
self.pnp_id = Some(v);
self
}
pub fn define(self, db: &mut Builder<Db>, perms: impl Into<Perms>) {
fn chr(db: &mut Builder<ServiceDef>, c: Characteristic, p: Perms, v: impl AsRef<[u8]>) {
db.ro_characteristic(c, p, v, |_| {});
}
let p = perms.into();
db.primary_service(Service::DeviceInformation, [], |db| {
use Characteristic::*;
if let Some(v) = self.manufacturer_name.as_ref() {
chr(db, ManufacturerNameString, p, v);
}
if let Some(v) = self.model_num.as_ref() {
chr(db, ModelNumberString, p, v);
}
if let Some(v) = self.serial_num.as_ref() {
chr(db, SerialNumberString, p, v);
}
if let Some(v) = self.hardware_rev.as_ref() {
chr(db, HardwareRevisionString, p, v);
}
if let Some(v) = self.firmware_rev.as_ref() {
chr(db, FirmwareRevisionString, p, v);
}
if let Some(v) = self.software_rev.as_ref() {
chr(db, SoftwareRevisionString, p, v);
}
if let Some(v) = self.system_id.as_ref() {
chr(db, SystemId, p, v.0.to_le_bytes());
}
if let Some(v) = self.regulatory_data.as_ref() {
chr(db, IeeeRegulatoryCertificationDataList, p, &v.0);
}
if let Some(v) = self.pnp_id.as_ref() {
chr(db, PnpId, p, v.to_bytes());
}
});
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct Eui64(u64);
impl Eui64 {
#[inline(always)]
#[must_use]
pub const fn new(v: u64) -> Self {
Self(v)
}
}
#[derive(Clone, Debug)]
pub struct RegulatoryData(Vec<u8>);
impl From<Vec<u8>> for RegulatoryData {
#[inline(always)]
fn from(v: Vec<u8>) -> Self {
Self(v)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PnpId {
pub vid: VendorId,
pub pid: u16,
pub ver: u16,
}
impl PnpId {
#[must_use]
pub const fn new(vid: VendorId, pid: u16, ver: (u8, u8, u8)) -> Option<Self> {
if ver.1 > 0xf || ver.2 > 0xf {
return None;
}
let ver = (ver.0 as u16) << 8 | (ver.1 as u16) << 4 | ver.2 as u16;
Some(Self { vid, pid, ver })
}
#[must_use]
pub fn to_bytes(self) -> [u8; 7] {
let mut v = [0; 7];
let (src, vid) = match self.vid {
VendorId::Bluetooth(vid) => (0x01, vid),
VendorId::USB(vid) => (0x02, vid),
};
v[0] = src;
v[1..3].copy_from_slice(&vid.to_le_bytes());
v[3..5].copy_from_slice(&self.pid.to_le_bytes());
v[5..7].copy_from_slice(&self.ver.to_le_bytes());
v
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum VendorId {
Bluetooth(u16),
USB(u16),
}