1#[cfg(not(feature = "std"))]
6use alloc::{borrow::ToOwned, vec::Vec};
7
8use uuid::Uuid;
9
10use crate::{
11 HierarchyLevel, NodeId, CHAR_COMMAND_UUID, CHAR_NODE_INFO_UUID, CHAR_STATUS_UUID,
12 CHAR_SYNC_DATA_UUID, CHAR_SYNC_STATE_UUID, HIVE_SERVICE_UUID,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct CharacteristicProperties(u8);
18
19impl CharacteristicProperties {
20 pub const READ: u8 = 0x02;
22 pub const WRITE_WITHOUT_RESPONSE: u8 = 0x04;
24 pub const WRITE: u8 = 0x08;
26 pub const NOTIFY: u8 = 0x10;
28 pub const INDICATE: u8 = 0x20;
30
31 pub const fn new(flags: u8) -> Self {
33 Self(flags)
34 }
35
36 pub fn can_read(&self) -> bool {
38 self.0 & Self::READ != 0
39 }
40
41 pub fn can_write(&self) -> bool {
43 self.0 & Self::WRITE != 0
44 }
45
46 pub fn can_notify(&self) -> bool {
48 self.0 & Self::NOTIFY != 0
49 }
50
51 pub fn can_indicate(&self) -> bool {
53 self.0 & Self::INDICATE != 0
54 }
55
56 pub fn flags(&self) -> u8 {
58 self.0
59 }
60}
61
62pub struct HiveCharacteristicUuids;
64
65impl HiveCharacteristicUuids {
66 pub fn node_info() -> Uuid {
68 Self::derive_uuid(CHAR_NODE_INFO_UUID)
69 }
70
71 pub fn sync_state() -> Uuid {
73 Self::derive_uuid(CHAR_SYNC_STATE_UUID)
74 }
75
76 pub fn sync_data() -> Uuid {
78 Self::derive_uuid(CHAR_SYNC_DATA_UUID)
79 }
80
81 pub fn command() -> Uuid {
83 Self::derive_uuid(CHAR_COMMAND_UUID)
84 }
85
86 pub fn status() -> Uuid {
88 Self::derive_uuid(CHAR_STATUS_UUID)
89 }
90
91 fn derive_uuid(char_id: u16) -> Uuid {
96 let mut bytes = HIVE_SERVICE_UUID.as_bytes().to_owned();
97 bytes[2] = (char_id >> 8) as u8;
98 bytes[3] = char_id as u8;
99 Uuid::from_bytes(bytes)
100 }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct NodeInfo {
108 pub node_id: NodeId,
110 pub protocol_version: u8,
112 pub hierarchy_level: HierarchyLevel,
114 pub capabilities: u16,
116 pub battery_percent: u8,
118}
119
120impl NodeInfo {
121 pub const ENCODED_SIZE: usize = 9;
123
124 pub fn new(node_id: NodeId, hierarchy_level: HierarchyLevel, capabilities: u16) -> Self {
126 Self {
127 node_id,
128 protocol_version: 1,
129 hierarchy_level,
130 capabilities,
131 battery_percent: 255,
132 }
133 }
134
135 pub fn encode(&self) -> [u8; Self::ENCODED_SIZE] {
137 let mut buf = [0u8; Self::ENCODED_SIZE];
138 let node_id = self.node_id.as_u32();
139
140 buf[0] = (node_id >> 24) as u8;
141 buf[1] = (node_id >> 16) as u8;
142 buf[2] = (node_id >> 8) as u8;
143 buf[3] = node_id as u8;
144 buf[4] = self.protocol_version;
145 buf[5] = self.hierarchy_level.into();
146 buf[6] = (self.capabilities >> 8) as u8;
147 buf[7] = self.capabilities as u8;
148 buf[8] = self.battery_percent;
149
150 buf
151 }
152
153 pub fn decode(data: &[u8]) -> Option<Self> {
155 if data.len() < Self::ENCODED_SIZE {
156 return None;
157 }
158
159 let node_id = NodeId::new(
160 ((data[0] as u32) << 24)
161 | ((data[1] as u32) << 16)
162 | ((data[2] as u32) << 8)
163 | (data[3] as u32),
164 );
165 let protocol_version = data[4];
166 let hierarchy_level = HierarchyLevel::from(data[5]);
167 let capabilities = ((data[6] as u16) << 8) | (data[7] as u16);
168 let battery_percent = data[8];
169
170 Some(Self {
171 node_id,
172 protocol_version,
173 hierarchy_level,
174 capabilities,
175 battery_percent,
176 })
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
182#[repr(u8)]
183pub enum SyncState {
184 #[default]
186 Idle = 0,
187 Syncing = 1,
189 Complete = 2,
191 Error = 3,
193}
194
195impl From<u8> for SyncState {
196 fn from(value: u8) -> Self {
197 match value {
198 0 => SyncState::Idle,
199 1 => SyncState::Syncing,
200 2 => SyncState::Complete,
201 3 => SyncState::Error,
202 _ => SyncState::Idle,
203 }
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct SyncStateData {
212 pub state: SyncState,
214 pub progress: u8,
216 pub pending_docs: u16,
218 pub last_sync: u32,
220}
221
222impl SyncStateData {
223 pub const ENCODED_SIZE: usize = 8;
225
226 pub fn new(state: SyncState) -> Self {
228 Self {
229 state,
230 progress: 0,
231 pending_docs: 0,
232 last_sync: 0,
233 }
234 }
235
236 pub fn encode(&self) -> [u8; Self::ENCODED_SIZE] {
238 let mut buf = [0u8; Self::ENCODED_SIZE];
239 buf[0] = self.state as u8;
240 buf[1] = self.progress;
241 buf[2] = (self.pending_docs >> 8) as u8;
242 buf[3] = self.pending_docs as u8;
243 buf[4] = (self.last_sync >> 24) as u8;
244 buf[5] = (self.last_sync >> 16) as u8;
245 buf[6] = (self.last_sync >> 8) as u8;
246 buf[7] = self.last_sync as u8;
247 buf
248 }
249
250 pub fn decode(data: &[u8]) -> Option<Self> {
252 if data.len() < Self::ENCODED_SIZE {
253 return None;
254 }
255
256 Some(Self {
257 state: SyncState::from(data[0]),
258 progress: data[1],
259 pending_docs: ((data[2] as u16) << 8) | (data[3] as u16),
260 last_sync: ((data[4] as u32) << 24)
261 | ((data[5] as u32) << 16)
262 | ((data[6] as u32) << 8)
263 | (data[7] as u32),
264 })
265 }
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270#[repr(u8)]
271pub enum SyncDataOp {
272 Document = 0x01,
274 Vector = 0x02,
276 Ack = 0x03,
278 End = 0xFF,
280}
281
282impl From<u8> for SyncDataOp {
283 fn from(value: u8) -> Self {
284 match value {
285 0x01 => SyncDataOp::Document,
286 0x02 => SyncDataOp::Vector,
287 0x03 => SyncDataOp::Ack,
288 0xFF => SyncDataOp::End,
289 _ => SyncDataOp::Document,
290 }
291 }
292}
293
294#[derive(Debug, Clone)]
298pub struct SyncDataHeader {
299 pub op: SyncDataOp,
301 pub seq: u16,
303 pub total_fragments: u8,
305 pub fragment_index: u8,
307}
308
309impl SyncDataHeader {
310 pub const SIZE: usize = 5;
312
313 pub fn new(op: SyncDataOp, seq: u16) -> Self {
315 Self {
316 op,
317 seq,
318 total_fragments: 1,
319 fragment_index: 0,
320 }
321 }
322
323 pub fn encode(&self) -> [u8; Self::SIZE] {
325 [
326 self.op as u8,
327 (self.seq >> 8) as u8,
328 self.seq as u8,
329 self.total_fragments,
330 self.fragment_index,
331 ]
332 }
333
334 pub fn decode(data: &[u8]) -> Option<Self> {
336 if data.len() < Self::SIZE {
337 return None;
338 }
339
340 Some(Self {
341 op: SyncDataOp::from(data[0]),
342 seq: ((data[1] as u16) << 8) | (data[2] as u16),
343 total_fragments: data[3],
344 fragment_index: data[4],
345 })
346 }
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq)]
351#[repr(u8)]
352pub enum CommandType {
353 StartSync = 0x01,
355 StopSync = 0x02,
357 RefreshInfo = 0x03,
359 SetHierarchy = 0x10,
361 Ping = 0xFE,
363 Reset = 0xFF,
365}
366
367impl From<u8> for CommandType {
368 fn from(value: u8) -> Self {
369 match value {
370 0x01 => CommandType::StartSync,
371 0x02 => CommandType::StopSync,
372 0x03 => CommandType::RefreshInfo,
373 0x10 => CommandType::SetHierarchy,
374 0xFE => CommandType::Ping,
375 0xFF => CommandType::Reset,
376 _ => CommandType::Ping,
377 }
378 }
379}
380
381#[derive(Debug, Clone)]
383pub struct Command {
384 pub cmd_type: CommandType,
386 pub payload: Vec<u8>,
388}
389
390impl Command {
391 pub fn new(cmd_type: CommandType) -> Self {
393 Self {
394 cmd_type,
395 payload: Vec::new(),
396 }
397 }
398
399 pub fn with_payload(cmd_type: CommandType, payload: Vec<u8>) -> Self {
401 Self { cmd_type, payload }
402 }
403
404 pub fn encode(&self) -> Vec<u8> {
406 let mut buf = Vec::with_capacity(1 + self.payload.len());
407 buf.push(self.cmd_type as u8);
408 buf.extend_from_slice(&self.payload);
409 buf
410 }
411
412 pub fn decode(data: &[u8]) -> Option<Self> {
414 if data.is_empty() {
415 return None;
416 }
417
418 Some(Self {
419 cmd_type: CommandType::from(data[0]),
420 payload: data[1..].to_vec(),
421 })
422 }
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
427pub struct StatusFlags(u8);
428
429impl StatusFlags {
430 pub const CONNECTED: u8 = 0x01;
432 pub const SYNCING: u8 = 0x02;
434 pub const PENDING_DATA: u8 = 0x04;
436 pub const LOW_BATTERY: u8 = 0x08;
438 pub const ERROR: u8 = 0x80;
440
441 pub const fn new(flags: u8) -> Self {
443 Self(flags)
444 }
445
446 pub fn is_connected(&self) -> bool {
448 self.0 & Self::CONNECTED != 0
449 }
450
451 pub fn is_syncing(&self) -> bool {
453 self.0 & Self::SYNCING != 0
454 }
455
456 pub fn has_pending_data(&self) -> bool {
458 self.0 & Self::PENDING_DATA != 0
459 }
460
461 pub fn is_low_battery(&self) -> bool {
463 self.0 & Self::LOW_BATTERY != 0
464 }
465
466 pub fn has_error(&self) -> bool {
468 self.0 & Self::ERROR != 0
469 }
470
471 pub fn flags(&self) -> u8 {
473 self.0
474 }
475}
476
477#[derive(Debug, Clone, PartialEq, Eq)]
479pub struct StatusData {
480 pub flags: StatusFlags,
482 pub child_count: u8,
484 pub parent_rssi: i8,
486 pub uptime_minutes: u16,
488}
489
490impl StatusData {
491 pub const ENCODED_SIZE: usize = 5;
493
494 pub fn new() -> Self {
496 Self {
497 flags: StatusFlags::default(),
498 child_count: 0,
499 parent_rssi: 127, uptime_minutes: 0,
501 }
502 }
503
504 pub fn encode(&self) -> [u8; Self::ENCODED_SIZE] {
506 [
507 self.flags.flags(),
508 self.child_count,
509 self.parent_rssi as u8,
510 (self.uptime_minutes >> 8) as u8,
511 self.uptime_minutes as u8,
512 ]
513 }
514
515 pub fn decode(data: &[u8]) -> Option<Self> {
517 if data.len() < Self::ENCODED_SIZE {
518 return None;
519 }
520
521 Some(Self {
522 flags: StatusFlags::new(data[0]),
523 child_count: data[1],
524 parent_rssi: data[2] as i8,
525 uptime_minutes: ((data[3] as u16) << 8) | (data[4] as u16),
526 })
527 }
528}
529
530impl Default for StatusData {
531 fn default() -> Self {
532 Self::new()
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539 use crate::capabilities;
540
541 #[test]
542 fn test_characteristic_properties() {
543 let props = CharacteristicProperties::new(
544 CharacteristicProperties::READ | CharacteristicProperties::NOTIFY,
545 );
546 assert!(props.can_read());
547 assert!(props.can_notify());
548 assert!(!props.can_write());
549 assert!(!props.can_indicate());
550 }
551
552 #[test]
553 fn test_characteristic_uuids() {
554 let node_info = HiveCharacteristicUuids::node_info();
555 let sync_state = HiveCharacteristicUuids::sync_state();
556
557 assert_ne!(node_info, sync_state);
559
560 assert_ne!(node_info, HIVE_SERVICE_UUID);
562 }
563
564 #[test]
565 fn test_node_info_encode_decode() {
566 let info = NodeInfo::new(
567 NodeId::new(0x12345678),
568 HierarchyLevel::Squad,
569 capabilities::CAN_RELAY | capabilities::HAS_GPS,
570 );
571
572 let encoded = info.encode();
573 assert_eq!(encoded.len(), NodeInfo::ENCODED_SIZE);
574
575 let decoded = NodeInfo::decode(&encoded).unwrap();
576 assert_eq!(decoded.node_id, info.node_id);
577 assert_eq!(decoded.hierarchy_level, info.hierarchy_level);
578 assert_eq!(decoded.capabilities, info.capabilities);
579 }
580
581 #[test]
582 fn test_sync_state_encode_decode() {
583 let state = SyncStateData {
584 state: SyncState::Syncing,
585 progress: 50,
586 pending_docs: 10,
587 last_sync: 1234567890,
588 };
589
590 let encoded = state.encode();
591 assert_eq!(encoded.len(), SyncStateData::ENCODED_SIZE);
592
593 let decoded = SyncStateData::decode(&encoded).unwrap();
594 assert_eq!(decoded.state, state.state);
595 assert_eq!(decoded.progress, state.progress);
596 assert_eq!(decoded.pending_docs, state.pending_docs);
597 assert_eq!(decoded.last_sync, state.last_sync);
598 }
599
600 #[test]
601 fn test_sync_data_header() {
602 let header = SyncDataHeader::new(SyncDataOp::Document, 42);
603
604 let encoded = header.encode();
605 assert_eq!(encoded.len(), SyncDataHeader::SIZE);
606
607 let decoded = SyncDataHeader::decode(&encoded).unwrap();
608 assert_eq!(decoded.op, SyncDataOp::Document);
609 assert_eq!(decoded.seq, 42);
610 }
611
612 #[test]
613 fn test_command_encode_decode() {
614 let cmd = Command::with_payload(CommandType::SetHierarchy, vec![2]); let encoded = cmd.encode();
617 assert_eq!(encoded[0], CommandType::SetHierarchy as u8);
618 assert_eq!(encoded[1], 2);
619
620 let decoded = Command::decode(&encoded).unwrap();
621 assert_eq!(decoded.cmd_type, CommandType::SetHierarchy);
622 assert_eq!(decoded.payload, vec![2]);
623 }
624
625 #[test]
626 fn test_status_flags() {
627 let flags = StatusFlags::new(StatusFlags::CONNECTED | StatusFlags::SYNCING);
628 assert!(flags.is_connected());
629 assert!(flags.is_syncing());
630 assert!(!flags.has_pending_data());
631 assert!(!flags.has_error());
632 }
633
634 #[test]
635 fn test_status_data_encode_decode() {
636 let status = StatusData {
637 flags: StatusFlags::new(StatusFlags::CONNECTED),
638 child_count: 3,
639 parent_rssi: -60,
640 uptime_minutes: 1440, };
642
643 let encoded = status.encode();
644 assert_eq!(encoded.len(), StatusData::ENCODED_SIZE);
645
646 let decoded = StatusData::decode(&encoded).unwrap();
647 assert!(decoded.flags.is_connected());
648 assert_eq!(decoded.child_count, 3);
649 assert_eq!(decoded.parent_rssi, -60);
650 assert_eq!(decoded.uptime_minutes, 1440);
651 }
652}