kenwood_thd75/radio/
memory.rs

1//! Memory channel read/write methods.
2
3use crate::error::{Error, ProtocolError};
4use crate::protocol::{Command, Response};
5use crate::transport::Transport;
6use crate::types::ChannelMemory;
7
8use super::Radio;
9
10impl<T: Transport> Radio<T> {
11    /// Read a memory channel by number (ME read).
12    ///
13    /// # Errors
14    ///
15    /// Returns an error if the command fails or the response is unexpected.
16    pub async fn read_channel(&mut self, channel: u16) -> Result<ChannelMemory, Error> {
17        tracing::debug!(channel, "reading memory channel");
18        let response = self.execute(Command::GetMemoryChannel { channel }).await?;
19        match response {
20            Response::MemoryChannel { data, .. } => Ok(data),
21            other => Err(Error::Protocol(ProtocolError::UnexpectedResponse {
22                expected: "MemoryChannel".into(),
23                actual: format!("{other:?}").into_bytes(),
24            })),
25        }
26    }
27
28    /// Read multiple memory channels efficiently.
29    ///
30    /// Reads channels in the given range and returns only occupied channels
31    /// (skips channels that return N/not available).
32    ///
33    /// # Errors
34    ///
35    /// Returns an error if a transport or protocol error occurs (other than
36    /// the radio returning N for an empty channel).
37    pub async fn read_channels(
38        &mut self,
39        range: std::ops::Range<u16>,
40    ) -> Result<Vec<(u16, ChannelMemory)>, Error> {
41        tracing::debug!(
42            start = range.start,
43            end = range.end,
44            "reading memory channels"
45        );
46        let mut results = Vec::new();
47        for ch in range {
48            match self.read_channel(ch).await {
49                Ok(data) => {
50                    // Skip channels with a zero frequency (empty).
51                    if data.rx_frequency.as_hz() != 0 {
52                        results.push((ch, data));
53                    }
54                }
55                Err(Error::NotAvailable) => {
56                    // Channel is empty/not programmed — skip.
57                }
58                Err(e) => return Err(e),
59            }
60        }
61        Ok(results)
62    }
63
64    /// Write a memory channel by number (ME write).
65    ///
66    /// # Errors
67    ///
68    /// Returns an error if the command fails or the response is unexpected.
69    pub async fn write_channel(&mut self, channel: u16, data: &ChannelMemory) -> Result<(), Error> {
70        tracing::info!(channel, "writing memory channel");
71        let response = self
72            .execute(Command::SetMemoryChannel {
73                channel,
74                data: data.clone(),
75            })
76            .await?;
77        match response {
78            Response::MemoryChannel { .. } => Ok(()),
79            other => Err(Error::Protocol(ProtocolError::UnexpectedResponse {
80                expected: "MemoryChannel".into(),
81                actual: format!("{other:?}").into_bytes(),
82            })),
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::transport::MockTransport;
91
92    /// ME response for channel 5 with a valid frequency.
93    const ME_RESP_005: &[u8] =
94        b"ME 005,0440000000,0005000000,5,2,0,0,0,0,0,0,0,0,0,0,08,08,000,0,,0,00,0\r";
95
96    #[tokio::test]
97    async fn read_channels_returns_populated() {
98        let mut mock = MockTransport::new();
99        // Channel 0: not available.
100        mock.expect(b"ME 000\r", b"N\r");
101        // Channel 1: populated.
102        mock.expect(
103            b"ME 001\r",
104            b"ME 001,0146520000,0000600000,5,0,0,0,0,0,0,0,0,0,0,0,08,08,000,0,,0,00,0\r",
105        );
106        // Channel 2: not available.
107        mock.expect(b"ME 002\r", b"N\r");
108
109        let mut radio = Radio::connect(mock).await.unwrap();
110        let channels = radio.read_channels(0..3).await.unwrap();
111        assert_eq!(channels.len(), 1);
112        assert_eq!(channels[0].0, 1);
113        assert_eq!(channels[0].1.rx_frequency.as_hz(), 146_520_000);
114    }
115
116    #[tokio::test]
117    async fn read_channels_empty_range() {
118        let mock = MockTransport::new();
119        let mut radio = Radio::connect(mock).await.unwrap();
120        let channels = radio.read_channels(0..0).await.unwrap();
121        assert!(channels.is_empty());
122    }
123
124    #[tokio::test]
125    async fn read_channel_populated() {
126        let mut mock = MockTransport::new();
127        mock.expect(b"ME 005\r", ME_RESP_005);
128        let mut radio = Radio::connect(mock).await.unwrap();
129        let data = radio.read_channel(5).await.unwrap();
130        assert_eq!(data.rx_frequency.as_hz(), 440_000_000);
131    }
132
133    #[tokio::test]
134    async fn read_channel_not_available() {
135        let mut mock = MockTransport::new();
136        mock.expect(b"ME 999\r", b"N\r");
137        let mut radio = Radio::connect(mock).await.unwrap();
138        let result = radio.read_channel(999).await;
139        assert!(matches!(result, Err(Error::NotAvailable)));
140    }
141}