use std::sync::{Arc, RwLock};
use crate::error::Result;
use crate::{HierarchyLevel, NodeId, PEAT_SERVICE_UUID};
use super::characteristics::{
CharacteristicProperties, Command, CommandType, NodeInfo, PeatCharacteristicUuids, StatusData,
StatusFlags, SyncDataHeader, SyncDataOp, SyncState, SyncStateData,
};
pub type GattEventCallback = Box<dyn Fn(GattEvent) + Send + Sync>;
#[derive(Debug, Clone)]
pub enum GattEvent {
ClientConnected {
address: String,
},
ClientDisconnected {
address: String,
},
NotificationSubscribed {
characteristic: String,
},
NotificationUnsubscribed {
characteristic: String,
},
CommandReceived {
command: CommandType,
payload: Vec<u8>,
},
SyncDataReceived {
header: SyncDataHeader,
payload: Vec<u8>,
},
MtuChanged {
mtu: u16,
},
}
#[derive(Debug, Clone)]
pub struct CharacteristicDescriptor {
pub uuid: uuid::Uuid,
pub name: &'static str,
pub properties: CharacteristicProperties,
pub encrypted: bool,
}
pub struct PeatCharacteristics;
impl PeatCharacteristics {
pub fn node_info() -> CharacteristicDescriptor {
CharacteristicDescriptor {
uuid: PeatCharacteristicUuids::node_info(),
name: "Node Info",
properties: CharacteristicProperties::new(CharacteristicProperties::READ),
encrypted: true,
}
}
pub fn sync_state() -> CharacteristicDescriptor {
CharacteristicDescriptor {
uuid: PeatCharacteristicUuids::sync_state(),
name: "Sync State",
properties: CharacteristicProperties::new(
CharacteristicProperties::READ | CharacteristicProperties::NOTIFY,
),
encrypted: true,
}
}
pub fn sync_data() -> CharacteristicDescriptor {
CharacteristicDescriptor {
uuid: PeatCharacteristicUuids::sync_data(),
name: "Sync Data",
properties: CharacteristicProperties::new(
CharacteristicProperties::WRITE | CharacteristicProperties::INDICATE,
),
encrypted: true,
}
}
pub fn command() -> CharacteristicDescriptor {
CharacteristicDescriptor {
uuid: PeatCharacteristicUuids::command(),
name: "Command",
properties: CharacteristicProperties::new(CharacteristicProperties::WRITE),
encrypted: true,
}
}
pub fn status() -> CharacteristicDescriptor {
CharacteristicDescriptor {
uuid: PeatCharacteristicUuids::status(),
name: "Status",
properties: CharacteristicProperties::new(
CharacteristicProperties::READ | CharacteristicProperties::NOTIFY,
),
encrypted: true,
}
}
pub fn all() -> Vec<CharacteristicDescriptor> {
vec![
Self::node_info(),
Self::sync_state(),
Self::sync_data(),
Self::command(),
Self::status(),
]
}
}
struct ServiceState {
node_info: NodeInfo,
sync_state: SyncStateData,
status: StatusData,
connected_clients: Vec<String>,
sync_state_subscribers: Vec<String>,
status_subscribers: Vec<String>,
mtu: u16,
}
impl ServiceState {
fn new(node_id: NodeId, hierarchy_level: HierarchyLevel, capabilities: u16) -> Self {
Self {
node_info: NodeInfo::new(node_id, hierarchy_level, capabilities),
sync_state: SyncStateData::new(SyncState::Idle),
status: StatusData::new(),
connected_clients: Vec::new(),
sync_state_subscribers: Vec::new(),
status_subscribers: Vec::new(),
mtu: 23, }
}
}
pub struct PeatGattService {
pub uuid: uuid::Uuid,
state: Arc<RwLock<ServiceState>>,
#[allow(dead_code)]
event_callback: Option<GattEventCallback>,
}
impl PeatGattService {
pub fn new(node_id: NodeId, hierarchy_level: HierarchyLevel, capabilities: u16) -> Self {
Self {
uuid: PEAT_SERVICE_UUID,
state: Arc::new(RwLock::new(ServiceState::new(
node_id,
hierarchy_level,
capabilities,
))),
event_callback: None,
}
}
pub fn set_event_callback(&mut self, callback: GattEventCallback) {
self.event_callback = Some(callback);
}
pub fn service_uuid(&self) -> uuid::Uuid {
self.uuid
}
pub fn characteristics(&self) -> Vec<CharacteristicDescriptor> {
PeatCharacteristics::all()
}
pub fn read_node_info(&self) -> Vec<u8> {
let state = self.state.read().unwrap();
state.node_info.encode().to_vec()
}
pub fn read_sync_state(&self) -> Vec<u8> {
let state = self.state.read().unwrap();
state.sync_state.encode().to_vec()
}
pub fn read_status(&self) -> Vec<u8> {
let state = self.state.read().unwrap();
state.status.encode().to_vec()
}
pub fn write_sync_data(&self, data: &[u8]) -> Result<Option<Vec<u8>>> {
let header = SyncDataHeader::decode(data).ok_or_else(|| {
crate::error::BleError::GattError("Invalid sync data header".to_string())
})?;
let payload = if data.len() > SyncDataHeader::SIZE {
data[SyncDataHeader::SIZE..].to_vec()
} else {
Vec::new()
};
match header.op {
SyncDataOp::Document => {
let mut state = self.state.write().unwrap();
state.sync_state.state = SyncState::Syncing;
state.status.flags =
StatusFlags::new(state.status.flags.flags() | StatusFlags::SYNCING);
let ack = SyncDataHeader::new(SyncDataOp::Ack, header.seq);
Ok(Some(ack.encode().to_vec()))
}
SyncDataOp::Vector => {
let ack = SyncDataHeader::new(SyncDataOp::Ack, header.seq);
Ok(Some(ack.encode().to_vec()))
}
SyncDataOp::End => {
let mut state = self.state.write().unwrap();
state.sync_state.state = SyncState::Complete;
state.sync_state.progress = 100;
state.status.flags =
StatusFlags::new(state.status.flags.flags() & !StatusFlags::SYNCING);
if let Some(ref callback) = self.event_callback {
callback(GattEvent::SyncDataReceived { header, payload });
}
Ok(None)
}
SyncDataOp::Ack => {
Ok(None)
}
}
}
pub fn write_command(&self, data: &[u8]) -> Result<()> {
let command = Command::decode(data)
.ok_or_else(|| crate::error::BleError::GattError("Invalid command data".to_string()))?;
match command.cmd_type {
CommandType::StartSync => {
let mut state = self.state.write().unwrap();
state.sync_state.state = SyncState::Syncing;
state.sync_state.progress = 0;
}
CommandType::StopSync => {
let mut state = self.state.write().unwrap();
state.sync_state.state = SyncState::Idle;
}
CommandType::RefreshInfo => {
}
CommandType::SetHierarchy => {
if !command.payload.is_empty() {
let mut state = self.state.write().unwrap();
state.node_info.hierarchy_level = HierarchyLevel::from(command.payload[0]);
}
}
CommandType::Ping => {
}
CommandType::Reset => {
let mut state = self.state.write().unwrap();
state.sync_state = SyncStateData::new(SyncState::Idle);
}
}
if let Some(ref callback) = self.event_callback {
callback(GattEvent::CommandReceived {
command: command.cmd_type,
payload: command.payload,
});
}
Ok(())
}
pub fn update_battery(&self, percent: u8) {
let mut state = self.state.write().unwrap();
state.node_info.battery_percent = percent.min(100);
if percent < 20 {
state.status.flags =
StatusFlags::new(state.status.flags.flags() | StatusFlags::LOW_BATTERY);
} else {
state.status.flags =
StatusFlags::new(state.status.flags.flags() & !StatusFlags::LOW_BATTERY);
}
}
pub fn update_hierarchy_level(&self, level: HierarchyLevel) {
let mut state = self.state.write().unwrap();
state.node_info.hierarchy_level = level;
}
pub fn update_sync_progress(&self, progress: u8, pending_docs: u16) {
let mut state = self.state.write().unwrap();
state.sync_state.progress = progress.min(100);
state.sync_state.pending_docs = pending_docs;
if progress >= 100 {
state.sync_state.state = SyncState::Complete;
}
}
pub fn update_parent_status(&self, connected: bool, rssi: Option<i8>) {
let mut state = self.state.write().unwrap();
if connected {
state.status.flags =
StatusFlags::new(state.status.flags.flags() | StatusFlags::CONNECTED);
state.status.parent_rssi = rssi.unwrap_or(0);
} else {
state.status.flags =
StatusFlags::new(state.status.flags.flags() & !StatusFlags::CONNECTED);
state.status.parent_rssi = 127; }
}
pub fn update_child_count(&self, count: u8) {
let mut state = self.state.write().unwrap();
state.status.child_count = count;
}
pub fn update_uptime(&self, minutes: u16) {
let mut state = self.state.write().unwrap();
state.status.uptime_minutes = minutes;
}
pub fn on_client_connected(&self, address: String) {
let mut state = self.state.write().unwrap();
if !state.connected_clients.contains(&address) {
state.connected_clients.push(address.clone());
}
if let Some(ref callback) = self.event_callback {
callback(GattEvent::ClientConnected { address });
}
}
pub fn on_client_disconnected(&self, address: &str) {
let mut state = self.state.write().unwrap();
state.connected_clients.retain(|a| a != address);
state.sync_state_subscribers.retain(|a| a != address);
state.status_subscribers.retain(|a| a != address);
if let Some(ref callback) = self.event_callback {
callback(GattEvent::ClientDisconnected {
address: address.to_string(),
});
}
}
pub fn on_subscribe(&self, address: String, characteristic: &str) {
let mut state = self.state.write().unwrap();
match characteristic {
"sync_state" if !state.sync_state_subscribers.contains(&address) => {
state.sync_state_subscribers.push(address);
}
"status" if !state.status_subscribers.contains(&address) => {
state.status_subscribers.push(address);
}
_ => {}
}
if let Some(ref callback) = self.event_callback {
callback(GattEvent::NotificationSubscribed {
characteristic: characteristic.to_string(),
});
}
}
pub fn on_mtu_changed(&self, mtu: u16) {
let mut state = self.state.write().unwrap();
state.mtu = mtu;
if let Some(ref callback) = self.event_callback {
callback(GattEvent::MtuChanged { mtu });
}
}
pub fn mtu(&self) -> u16 {
let state = self.state.read().unwrap();
state.mtu
}
pub fn connected_client_count(&self) -> usize {
let state = self.state.read().unwrap();
state.connected_clients.len()
}
pub fn sync_state_subscribers(&self) -> Vec<String> {
let state = self.state.read().unwrap();
state.sync_state_subscribers.clone()
}
pub fn status_subscribers(&self) -> Vec<String> {
let state = self.state.read().unwrap();
state.status_subscribers.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::capabilities;
#[test]
fn test_gatt_service_creation() {
let service = PeatGattService::new(
NodeId::new(0x12345678),
HierarchyLevel::Squad,
capabilities::CAN_RELAY,
);
assert_eq!(service.service_uuid(), PEAT_SERVICE_UUID);
assert_eq!(service.characteristics().len(), 5);
}
#[test]
fn test_read_node_info() {
let service = PeatGattService::new(
NodeId::new(0x12345678),
HierarchyLevel::Squad,
capabilities::CAN_RELAY,
);
let data = service.read_node_info();
assert_eq!(data.len(), NodeInfo::ENCODED_SIZE);
let info = NodeInfo::decode(&data).unwrap();
assert_eq!(info.node_id, NodeId::new(0x12345678));
assert_eq!(info.hierarchy_level, HierarchyLevel::Squad);
}
#[test]
fn test_write_command() {
let service = PeatGattService::new(NodeId::new(0x12345678), HierarchyLevel::Platform, 0);
let cmd = Command::with_payload(CommandType::SetHierarchy, vec![2]); service.write_command(&cmd.encode()).unwrap();
let data = service.read_node_info();
let info = NodeInfo::decode(&data).unwrap();
assert_eq!(info.hierarchy_level, HierarchyLevel::Platoon);
}
#[test]
fn test_sync_data_flow() {
let service = PeatGattService::new(NodeId::new(0x12345678), HierarchyLevel::Platform, 0);
let cmd = Command::new(CommandType::StartSync);
service.write_command(&cmd.encode()).unwrap();
let state_data = service.read_sync_state();
let state = SyncStateData::decode(&state_data).unwrap();
assert_eq!(state.state, SyncState::Syncing);
let mut header = SyncDataHeader::new(SyncDataOp::Document, 1);
let mut data = header.encode().to_vec();
data.extend_from_slice(b"test document data");
let response = service.write_sync_data(&data).unwrap();
assert!(response.is_some());
header = SyncDataHeader::new(SyncDataOp::End, 2);
service.write_sync_data(&header.encode()).unwrap();
let state_data = service.read_sync_state();
let state = SyncStateData::decode(&state_data).unwrap();
assert_eq!(state.state, SyncState::Complete);
}
#[test]
fn test_battery_update() {
let service = PeatGattService::new(NodeId::new(0x12345678), HierarchyLevel::Platform, 0);
service.update_battery(15);
let data = service.read_node_info();
let info = NodeInfo::decode(&data).unwrap();
assert_eq!(info.battery_percent, 15);
let status_data = service.read_status();
let status = StatusData::decode(&status_data).unwrap();
assert!(status.flags.is_low_battery());
}
#[test]
fn test_client_connection() {
let service = PeatGattService::new(NodeId::new(0x12345678), HierarchyLevel::Platform, 0);
service.on_client_connected("AA:BB:CC:DD:EE:FF".to_string());
assert_eq!(service.connected_client_count(), 1);
service.on_client_disconnected("AA:BB:CC:DD:EE:FF");
assert_eq!(service.connected_client_count(), 0);
}
#[test]
fn test_mtu_negotiation() {
let service = PeatGattService::new(NodeId::new(0x12345678), HierarchyLevel::Platform, 0);
assert_eq!(service.mtu(), 23);
service.on_mtu_changed(251);
assert_eq!(service.mtu(), 251);
}
#[test]
fn test_peat_characteristics() {
let chars = PeatCharacteristics::all();
assert_eq!(chars.len(), 5);
let node_info = PeatCharacteristics::node_info();
assert!(node_info.properties.can_read());
assert!(!node_info.properties.can_write());
let sync_data = PeatCharacteristics::sync_data();
assert!(sync_data.properties.can_write());
assert!(sync_data.properties.can_indicate());
}
}