timex_datalink/protocol_4/
sound_theme.rs

1//! SoundTheme implementation for Protocol 4
2//!
3//! This module handles sound themes for Timex Datalink watches.
4
5use crate::PacketGenerator;
6use crate::helpers::cpacket_paginator::paginate_cpackets;
7
8/// SoundTheme structure for Protocol 4
9pub struct SoundTheme {
10    /// Sound theme data bytes
11    pub sound_theme_data: Vec<u8>,
12}
13
14impl PacketGenerator for SoundTheme {
15    fn packets(&self) -> Vec<Vec<u8>> {
16        // Constants from Ruby implementation
17        const CPACKET_SECT: [u8; 2] = [0x90, 0x03];
18        const CPACKET_DATA: [u8; 2] = [0x91, 0x03];
19        const CPACKET_END: [u8; 2] = [0x92, 0x03];
20        const CPACKET_DATA_LENGTH: usize = 32;
21        
22        // Check if data has the SPC file header and remove it if present
23        const SOUND_DATA_HEADER: &[u8] = &[0x25, 0x04, 0x19, 0x69];
24        
25        let sound_data = if self.sound_theme_data.starts_with(SOUND_DATA_HEADER) {
26            &self.sound_theme_data[SOUND_DATA_HEADER.len()..]
27        } else {
28            &self.sound_theme_data
29        };
30        
31        // Calculate offset similar to Ruby implementation
32        let offset = 0x100 - sound_data.len();
33        
34        // Create load_sect packet
35        let payloads = paginate_cpackets(&CPACKET_DATA, CPACKET_DATA_LENGTH, sound_data);
36        let mut load_sect = Vec::new();
37        load_sect.extend_from_slice(&CPACKET_SECT);
38        load_sect.push(payloads.len() as u8);
39        load_sect.push(offset as u8);
40        
41        // Create end packet
42        let end_packet = CPACKET_END.to_vec();
43        
44        // Combine all packets
45        let mut all_packets = Vec::with_capacity(payloads.len() + 2);
46        all_packets.push(load_sect);
47        all_packets.extend(payloads);
48        all_packets.push(end_packet);
49        
50        // Apply CRC wrapping
51        use crate::helpers::crc_packets_wrapper::wrap_packets_with_crc;
52        wrap_packets_with_crc(all_packets)
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    // Include the actual SPC file at compile time
61    // The path is relative to the Cargo.toml file
62    const EXAMPLE_SPC: &[u8] = include_bytes!("../../fixtures/EXAMPLE.SPC");
63
64    #[test]
65    fn test_sound_theme() {
66        let sound_theme = SoundTheme {
67            sound_theme_data: EXAMPLE_SPC.to_vec(),
68        };
69
70        // From golden fixture: sound_theme.jsonl
71        #[rustfmt::skip]
72        let expected = vec![
73            vec![7, 144, 3, 2, 215, 254, 41],
74            vec![38, 145, 3, 1, 98, 105, 110, 97, 114, 121, 32, 115, 111, 117, 110, 100, 32, 100, 97, 116, 97, 32, 116, 104, 97, 116, 32, 103, 101, 116, 115, 32, 115, 101, 110, 116, 28, 235],
75            vec![15, 145, 3, 2, 32, 118, 101, 114, 98, 97, 116, 105, 109, 75, 236],
76            vec![5, 146, 3, 96, 61]
77        ];
78
79        assert_eq!(sound_theme.packets(), expected);
80    }
81}