1#[cfg(not(feature = "std"))]
21use alloc::{borrow::ToOwned, vec::Vec};
22
23use uuid::Uuid;
24
25use crate::{
26 HierarchyLevel, NodeId, CHAR_COMMAND_UUID, CHAR_NODE_INFO_UUID, CHAR_STATUS_UUID,
27 CHAR_SYNC_DATA_UUID, CHAR_SYNC_STATE_UUID, HIVE_SERVICE_UUID,
28};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct CharacteristicProperties(u8);
33
34impl CharacteristicProperties {
35 pub const READ: u8 = 0x02;
37 pub const WRITE_WITHOUT_RESPONSE: u8 = 0x04;
39 pub const WRITE: u8 = 0x08;
41 pub const NOTIFY: u8 = 0x10;
43 pub const INDICATE: u8 = 0x20;
45
46 pub const fn new(flags: u8) -> Self {
48 Self(flags)
49 }
50
51 pub fn can_read(&self) -> bool {
53 self.0 & Self::READ != 0
54 }
55
56 pub fn can_write(&self) -> bool {
58 self.0 & Self::WRITE != 0
59 }
60
61 pub fn can_notify(&self) -> bool {
63 self.0 & Self::NOTIFY != 0
64 }
65
66 pub fn can_indicate(&self) -> bool {
68 self.0 & Self::INDICATE != 0
69 }
70
71 pub fn flags(&self) -> u8 {
73 self.0
74 }
75}
76
77pub struct HiveCharacteristicUuids;
79
80impl HiveCharacteristicUuids {
81 pub fn node_info() -> Uuid {
83 Self::derive_uuid(CHAR_NODE_INFO_UUID)
84 }
85
86 pub fn sync_state() -> Uuid {
88 Self::derive_uuid(CHAR_SYNC_STATE_UUID)
89 }
90
91 pub fn sync_data() -> Uuid {
93 Self::derive_uuid(CHAR_SYNC_DATA_UUID)
94 }
95
96 pub fn command() -> Uuid {
98 Self::derive_uuid(CHAR_COMMAND_UUID)
99 }
100
101 pub fn status() -> Uuid {
103 Self::derive_uuid(CHAR_STATUS_UUID)
104 }
105
106 fn derive_uuid(char_id: u16) -> Uuid {
111 let mut bytes = HIVE_SERVICE_UUID.as_bytes().to_owned();
112 bytes[2] = (char_id >> 8) as u8;
113 bytes[3] = char_id as u8;
114 Uuid::from_bytes(bytes)
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct NodeInfo {
123 pub node_id: NodeId,
125 pub protocol_version: u8,
127 pub hierarchy_level: HierarchyLevel,
129 pub capabilities: u16,
131 pub battery_percent: u8,
133}
134
135impl NodeInfo {
136 pub const ENCODED_SIZE: usize = 9;
138
139 pub fn new(node_id: NodeId, hierarchy_level: HierarchyLevel, capabilities: u16) -> Self {
141 Self {
142 node_id,
143 protocol_version: 1,
144 hierarchy_level,
145 capabilities,
146 battery_percent: 255,
147 }
148 }
149
150 pub fn encode(&self) -> [u8; Self::ENCODED_SIZE] {
152 let mut buf = [0u8; Self::ENCODED_SIZE];
153 let node_id = self.node_id.as_u32();
154
155 buf[0] = (node_id >> 24) as u8;
156 buf[1] = (node_id >> 16) as u8;
157 buf[2] = (node_id >> 8) as u8;
158 buf[3] = node_id as u8;
159 buf[4] = self.protocol_version;
160 buf[5] = self.hierarchy_level.into();
161 buf[6] = (self.capabilities >> 8) as u8;
162 buf[7] = self.capabilities as u8;
163 buf[8] = self.battery_percent;
164
165 buf
166 }
167
168 pub fn decode(data: &[u8]) -> Option<Self> {
170 if data.len() < Self::ENCODED_SIZE {
171 return None;
172 }
173
174 let node_id = NodeId::new(
175 ((data[0] as u32) << 24)
176 | ((data[1] as u32) << 16)
177 | ((data[2] as u32) << 8)
178 | (data[3] as u32),
179 );
180 let protocol_version = data[4];
181 let hierarchy_level = HierarchyLevel::from(data[5]);
182 let capabilities = ((data[6] as u16) << 8) | (data[7] as u16);
183 let battery_percent = data[8];
184
185 Some(Self {
186 node_id,
187 protocol_version,
188 hierarchy_level,
189 capabilities,
190 battery_percent,
191 })
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
197#[repr(u8)]
198pub enum SyncState {
199 #[default]
201 Idle = 0,
202 Syncing = 1,
204 Complete = 2,
206 Error = 3,
208}
209
210impl From<u8> for SyncState {
211 fn from(value: u8) -> Self {
212 match value {
213 0 => SyncState::Idle,
214 1 => SyncState::Syncing,
215 2 => SyncState::Complete,
216 3 => SyncState::Error,
217 _ => SyncState::Idle,
218 }
219 }
220}
221
222#[derive(Debug, Clone, PartialEq, Eq)]
226pub struct SyncStateData {
227 pub state: SyncState,
229 pub progress: u8,
231 pub pending_docs: u16,
233 pub last_sync: u32,
235}
236
237impl SyncStateData {
238 pub const ENCODED_SIZE: usize = 8;
240
241 pub fn new(state: SyncState) -> Self {
243 Self {
244 state,
245 progress: 0,
246 pending_docs: 0,
247 last_sync: 0,
248 }
249 }
250
251 pub fn encode(&self) -> [u8; Self::ENCODED_SIZE] {
253 let mut buf = [0u8; Self::ENCODED_SIZE];
254 buf[0] = self.state as u8;
255 buf[1] = self.progress;
256 buf[2] = (self.pending_docs >> 8) as u8;
257 buf[3] = self.pending_docs as u8;
258 buf[4] = (self.last_sync >> 24) as u8;
259 buf[5] = (self.last_sync >> 16) as u8;
260 buf[6] = (self.last_sync >> 8) as u8;
261 buf[7] = self.last_sync as u8;
262 buf
263 }
264
265 pub fn decode(data: &[u8]) -> Option<Self> {
267 if data.len() < Self::ENCODED_SIZE {
268 return None;
269 }
270
271 Some(Self {
272 state: SyncState::from(data[0]),
273 progress: data[1],
274 pending_docs: ((data[2] as u16) << 8) | (data[3] as u16),
275 last_sync: ((data[4] as u32) << 24)
276 | ((data[5] as u32) << 16)
277 | ((data[6] as u32) << 8)
278 | (data[7] as u32),
279 })
280 }
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285#[repr(u8)]
286pub enum SyncDataOp {
287 Document = 0x01,
289 Vector = 0x02,
291 Ack = 0x03,
293 End = 0xFF,
295}
296
297impl From<u8> for SyncDataOp {
298 fn from(value: u8) -> Self {
299 match value {
300 0x01 => SyncDataOp::Document,
301 0x02 => SyncDataOp::Vector,
302 0x03 => SyncDataOp::Ack,
303 0xFF => SyncDataOp::End,
304 _ => SyncDataOp::Document,
305 }
306 }
307}
308
309#[derive(Debug, Clone)]
313pub struct SyncDataHeader {
314 pub op: SyncDataOp,
316 pub seq: u16,
318 pub total_fragments: u8,
320 pub fragment_index: u8,
322}
323
324impl SyncDataHeader {
325 pub const SIZE: usize = 5;
327
328 pub fn new(op: SyncDataOp, seq: u16) -> Self {
330 Self {
331 op,
332 seq,
333 total_fragments: 1,
334 fragment_index: 0,
335 }
336 }
337
338 pub fn encode(&self) -> [u8; Self::SIZE] {
340 [
341 self.op as u8,
342 (self.seq >> 8) as u8,
343 self.seq as u8,
344 self.total_fragments,
345 self.fragment_index,
346 ]
347 }
348
349 pub fn decode(data: &[u8]) -> Option<Self> {
351 if data.len() < Self::SIZE {
352 return None;
353 }
354
355 Some(Self {
356 op: SyncDataOp::from(data[0]),
357 seq: ((data[1] as u16) << 8) | (data[2] as u16),
358 total_fragments: data[3],
359 fragment_index: data[4],
360 })
361 }
362}
363
364#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366#[repr(u8)]
367pub enum CommandType {
368 StartSync = 0x01,
370 StopSync = 0x02,
372 RefreshInfo = 0x03,
374 SetHierarchy = 0x10,
376 Ping = 0xFE,
378 Reset = 0xFF,
380}
381
382impl From<u8> for CommandType {
383 fn from(value: u8) -> Self {
384 match value {
385 0x01 => CommandType::StartSync,
386 0x02 => CommandType::StopSync,
387 0x03 => CommandType::RefreshInfo,
388 0x10 => CommandType::SetHierarchy,
389 0xFE => CommandType::Ping,
390 0xFF => CommandType::Reset,
391 _ => CommandType::Ping,
392 }
393 }
394}
395
396#[derive(Debug, Clone)]
398pub struct Command {
399 pub cmd_type: CommandType,
401 pub payload: Vec<u8>,
403}
404
405impl Command {
406 pub fn new(cmd_type: CommandType) -> Self {
408 Self {
409 cmd_type,
410 payload: Vec::new(),
411 }
412 }
413
414 pub fn with_payload(cmd_type: CommandType, payload: Vec<u8>) -> Self {
416 Self { cmd_type, payload }
417 }
418
419 pub fn encode(&self) -> Vec<u8> {
421 let mut buf = Vec::with_capacity(1 + self.payload.len());
422 buf.push(self.cmd_type as u8);
423 buf.extend_from_slice(&self.payload);
424 buf
425 }
426
427 pub fn decode(data: &[u8]) -> Option<Self> {
429 if data.is_empty() {
430 return None;
431 }
432
433 Some(Self {
434 cmd_type: CommandType::from(data[0]),
435 payload: data[1..].to_vec(),
436 })
437 }
438}
439
440#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
442pub struct StatusFlags(u8);
443
444impl StatusFlags {
445 pub const CONNECTED: u8 = 0x01;
447 pub const SYNCING: u8 = 0x02;
449 pub const PENDING_DATA: u8 = 0x04;
451 pub const LOW_BATTERY: u8 = 0x08;
453 pub const ERROR: u8 = 0x80;
455
456 pub const fn new(flags: u8) -> Self {
458 Self(flags)
459 }
460
461 pub fn is_connected(&self) -> bool {
463 self.0 & Self::CONNECTED != 0
464 }
465
466 pub fn is_syncing(&self) -> bool {
468 self.0 & Self::SYNCING != 0
469 }
470
471 pub fn has_pending_data(&self) -> bool {
473 self.0 & Self::PENDING_DATA != 0
474 }
475
476 pub fn is_low_battery(&self) -> bool {
478 self.0 & Self::LOW_BATTERY != 0
479 }
480
481 pub fn has_error(&self) -> bool {
483 self.0 & Self::ERROR != 0
484 }
485
486 pub fn flags(&self) -> u8 {
488 self.0
489 }
490}
491
492#[derive(Debug, Clone, PartialEq, Eq)]
494pub struct StatusData {
495 pub flags: StatusFlags,
497 pub child_count: u8,
499 pub parent_rssi: i8,
501 pub uptime_minutes: u16,
503}
504
505impl StatusData {
506 pub const ENCODED_SIZE: usize = 5;
508
509 pub fn new() -> Self {
511 Self {
512 flags: StatusFlags::default(),
513 child_count: 0,
514 parent_rssi: 127, uptime_minutes: 0,
516 }
517 }
518
519 pub fn encode(&self) -> [u8; Self::ENCODED_SIZE] {
521 [
522 self.flags.flags(),
523 self.child_count,
524 self.parent_rssi as u8,
525 (self.uptime_minutes >> 8) as u8,
526 self.uptime_minutes as u8,
527 ]
528 }
529
530 pub fn decode(data: &[u8]) -> Option<Self> {
532 if data.len() < Self::ENCODED_SIZE {
533 return None;
534 }
535
536 Some(Self {
537 flags: StatusFlags::new(data[0]),
538 child_count: data[1],
539 parent_rssi: data[2] as i8,
540 uptime_minutes: ((data[3] as u16) << 8) | (data[4] as u16),
541 })
542 }
543}
544
545impl Default for StatusData {
546 fn default() -> Self {
547 Self::new()
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use crate::capabilities;
555
556 #[test]
557 fn test_characteristic_properties() {
558 let props = CharacteristicProperties::new(
559 CharacteristicProperties::READ | CharacteristicProperties::NOTIFY,
560 );
561 assert!(props.can_read());
562 assert!(props.can_notify());
563 assert!(!props.can_write());
564 assert!(!props.can_indicate());
565 }
566
567 #[test]
568 fn test_characteristic_uuids() {
569 let node_info = HiveCharacteristicUuids::node_info();
570 let sync_state = HiveCharacteristicUuids::sync_state();
571
572 assert_ne!(node_info, sync_state);
574
575 assert_ne!(node_info, HIVE_SERVICE_UUID);
577 }
578
579 #[test]
580 fn test_node_info_encode_decode() {
581 let info = NodeInfo::new(
582 NodeId::new(0x12345678),
583 HierarchyLevel::Squad,
584 capabilities::CAN_RELAY | capabilities::HAS_GPS,
585 );
586
587 let encoded = info.encode();
588 assert_eq!(encoded.len(), NodeInfo::ENCODED_SIZE);
589
590 let decoded = NodeInfo::decode(&encoded).unwrap();
591 assert_eq!(decoded.node_id, info.node_id);
592 assert_eq!(decoded.hierarchy_level, info.hierarchy_level);
593 assert_eq!(decoded.capabilities, info.capabilities);
594 }
595
596 #[test]
597 fn test_sync_state_encode_decode() {
598 let state = SyncStateData {
599 state: SyncState::Syncing,
600 progress: 50,
601 pending_docs: 10,
602 last_sync: 1234567890,
603 };
604
605 let encoded = state.encode();
606 assert_eq!(encoded.len(), SyncStateData::ENCODED_SIZE);
607
608 let decoded = SyncStateData::decode(&encoded).unwrap();
609 assert_eq!(decoded.state, state.state);
610 assert_eq!(decoded.progress, state.progress);
611 assert_eq!(decoded.pending_docs, state.pending_docs);
612 assert_eq!(decoded.last_sync, state.last_sync);
613 }
614
615 #[test]
616 fn test_sync_data_header() {
617 let header = SyncDataHeader::new(SyncDataOp::Document, 42);
618
619 let encoded = header.encode();
620 assert_eq!(encoded.len(), SyncDataHeader::SIZE);
621
622 let decoded = SyncDataHeader::decode(&encoded).unwrap();
623 assert_eq!(decoded.op, SyncDataOp::Document);
624 assert_eq!(decoded.seq, 42);
625 }
626
627 #[test]
628 fn test_command_encode_decode() {
629 let cmd = Command::with_payload(CommandType::SetHierarchy, vec![2]); let encoded = cmd.encode();
632 assert_eq!(encoded[0], CommandType::SetHierarchy as u8);
633 assert_eq!(encoded[1], 2);
634
635 let decoded = Command::decode(&encoded).unwrap();
636 assert_eq!(decoded.cmd_type, CommandType::SetHierarchy);
637 assert_eq!(decoded.payload, vec![2]);
638 }
639
640 #[test]
641 fn test_status_flags() {
642 let flags = StatusFlags::new(StatusFlags::CONNECTED | StatusFlags::SYNCING);
643 assert!(flags.is_connected());
644 assert!(flags.is_syncing());
645 assert!(!flags.has_pending_data());
646 assert!(!flags.has_error());
647 }
648
649 #[test]
650 fn test_status_data_encode_decode() {
651 let status = StatusData {
652 flags: StatusFlags::new(StatusFlags::CONNECTED),
653 child_count: 3,
654 parent_rssi: -60,
655 uptime_minutes: 1440, };
657
658 let encoded = status.encode();
659 assert_eq!(encoded.len(), StatusData::ENCODED_SIZE);
660
661 let decoded = StatusData::decode(&encoded).unwrap();
662 assert!(decoded.flags.is_connected());
663 assert_eq!(decoded.child_count, 3);
664 assert_eq!(decoded.parent_rssi, -60);
665 assert_eq!(decoded.uptime_minutes, 1440);
666 }
667}