1use crate::errors::{Error, Result};
4
5const DEFAULT_FREQUENCY_HZ: u64 = 900_000_000;
6const DEFAULT_SAMPLE_RATE_HZ: u32 = 10_000_000;
7const DEFAULT_LNA_GAIN_DB: u8 = 8;
8const DEFAULT_VGA_GAIN_DB: u8 = 20;
9const DEFAULT_TX_VGA_GAIN_DB: u8 = 0;
10
11#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct Config {
14 frequency_hz: u64,
15 sample_rate_hz: u32,
16 lna_gain_db: u8,
17 vga_gain_db: u8,
18 tx_vga_gain_db: u8,
19 amp_enabled: bool,
20 bias_tee_enabled: bool,
21}
22
23impl Default for Config {
24 fn default() -> Self {
25 Self {
26 frequency_hz: DEFAULT_FREQUENCY_HZ,
27 sample_rate_hz: DEFAULT_SAMPLE_RATE_HZ,
28 lna_gain_db: DEFAULT_LNA_GAIN_DB,
29 vga_gain_db: DEFAULT_VGA_GAIN_DB,
30 tx_vga_gain_db: DEFAULT_TX_VGA_GAIN_DB,
31 amp_enabled: false,
32 bias_tee_enabled: false,
33 }
34 }
35}
36
37impl Config {
38 pub fn builder() -> ConfigBuilder {
40 ConfigBuilder::default()
41 }
42
43 pub const fn frequency_hz(&self) -> u64 {
45 self.frequency_hz
46 }
47
48 pub const fn sample_rate_hz(&self) -> u32 {
50 self.sample_rate_hz
51 }
52
53 pub const fn lna_gain_db(&self) -> u8 {
55 self.lna_gain_db
56 }
57
58 pub const fn vga_gain_db(&self) -> u8 {
60 self.vga_gain_db
61 }
62
63 pub const fn tx_vga_gain_db(&self) -> u8 {
65 self.tx_vga_gain_db
66 }
67
68 pub const fn amp_enabled(&self) -> bool {
70 self.amp_enabled
71 }
72
73 pub const fn bias_tee_enabled(&self) -> bool {
75 self.bias_tee_enabled
76 }
77
78 pub(crate) fn set_frequency_hz_internal(&mut self, value: u64) {
79 self.frequency_hz = value;
80 }
81
82 pub(crate) fn set_sample_rate_hz_internal(&mut self, value: u32) {
83 self.sample_rate_hz = value;
84 }
85
86 pub(crate) fn set_lna_gain_db_internal(&mut self, value: u8) {
87 self.lna_gain_db = value;
88 }
89
90 pub(crate) fn set_vga_gain_db_internal(&mut self, value: u8) {
91 self.vga_gain_db = value;
92 }
93
94 pub(crate) fn set_tx_vga_gain_db_internal(&mut self, value: u8) {
95 self.tx_vga_gain_db = value;
96 }
97
98 pub(crate) fn set_amp_enabled_internal(&mut self, value: bool) {
99 self.amp_enabled = value;
100 }
101
102 pub(crate) fn set_bias_tee_enabled_internal(&mut self, value: bool) {
103 self.bias_tee_enabled = value;
104 }
105}
106
107#[derive(Clone, Debug, Default)]
109pub struct ConfigBuilder {
110 config: Config,
111}
112
113impl ConfigBuilder {
114 pub fn frequency_hz(mut self, value: u64) -> Self {
116 self.config.frequency_hz = value;
117 self
118 }
119
120 pub fn sample_rate_hz(mut self, value: u32) -> Self {
122 self.config.sample_rate_hz = value;
123 self
124 }
125
126 pub fn lna_gain_db(mut self, value: u8) -> Self {
128 self.config.lna_gain_db = value;
129 self
130 }
131
132 pub fn vga_gain_db(mut self, value: u8) -> Self {
134 self.config.vga_gain_db = value;
135 self
136 }
137
138 pub fn tx_vga_gain_db(mut self, value: u8) -> Self {
140 self.config.tx_vga_gain_db = value;
141 self
142 }
143
144 pub fn amp_enable(mut self, enabled: bool) -> Self {
146 self.config.amp_enabled = enabled;
147 self
148 }
149
150 pub fn bias_tee(mut self, enabled: bool) -> Self {
152 self.config.bias_tee_enabled = enabled;
153 self
154 }
155
156 pub fn build(self) -> Result<Config> {
158 validate_config(&self.config)?;
159 Ok(self.config)
160 }
161}
162
163pub(crate) fn validate_config(config: &Config) -> Result<()> {
164 validate_frequency(config.frequency_hz)?;
165 validate_sample_rate(config.sample_rate_hz)?;
166 validate_lna_gain(config.lna_gain_db)?;
167 validate_vga_gain(config.vga_gain_db)?;
168 validate_tx_vga_gain(config.tx_vga_gain_db)?;
169 Ok(())
170}
171
172pub(crate) fn validate_frequency(value: u64) -> Result<()> {
173 if !(1_000_000..=6_000_000_000).contains(&value) {
174 return Err(Error::invalid_config(
175 "frequency_hz",
176 "must be between 1 MHz and 6 GHz inclusive",
177 ));
178 }
179 Ok(())
180}
181
182pub(crate) fn validate_sample_rate(value: u32) -> Result<()> {
183 if !(2_000_000..=20_000_000).contains(&value) {
184 return Err(Error::invalid_config(
185 "sample_rate_hz",
186 "must be between 2 MHz and 20 MHz inclusive",
187 ));
188 }
189 Ok(())
190}
191
192pub(crate) fn validate_lna_gain(value: u8) -> Result<()> {
193 if value > 40 || !value.is_multiple_of(8) {
194 return Err(Error::invalid_config(
195 "lna_gain_db",
196 "must be 0 through 40 dB in 8 dB steps",
197 ));
198 }
199 Ok(())
200}
201
202pub(crate) fn validate_vga_gain(value: u8) -> Result<()> {
203 if value > 62 || !value.is_multiple_of(2) {
204 return Err(Error::invalid_config(
205 "vga_gain_db",
206 "must be 0 through 62 dB in 2 dB steps",
207 ));
208 }
209 Ok(())
210}
211
212pub(crate) fn validate_tx_vga_gain(value: u8) -> Result<()> {
213 if value > 47 {
214 return Err(Error::invalid_config(
215 "tx_vga_gain_db",
216 "must be between 0 dB and 47 dB inclusive",
217 ));
218 }
219 Ok(())
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn defaults_match_hackrf_transfer() {
228 let config = Config::default();
229 assert_eq!(config.frequency_hz(), 900_000_000);
230 assert_eq!(config.sample_rate_hz(), 10_000_000);
231 assert_eq!(config.lna_gain_db(), 8);
232 assert_eq!(config.vga_gain_db(), 20);
233 assert_eq!(config.tx_vga_gain_db(), 0);
234 assert!(!config.amp_enabled());
235 assert!(!config.bias_tee_enabled());
236 }
237
238 #[test]
239 fn validates_documented_ranges_and_steps() {
240 assert!(Config::builder().frequency_hz(999_999).build().is_err());
241 assert!(
242 Config::builder()
243 .frequency_hz(6_000_000_001)
244 .build()
245 .is_err()
246 );
247 assert!(Config::builder().sample_rate_hz(1_999_999).build().is_err());
248 assert!(
249 Config::builder()
250 .sample_rate_hz(20_000_001)
251 .build()
252 .is_err()
253 );
254 assert!(Config::builder().lna_gain_db(7).build().is_err());
255 assert!(Config::builder().vga_gain_db(3).build().is_err());
256 assert!(Config::builder().tx_vga_gain_db(48).build().is_err());
257 assert!(
258 Config::builder()
259 .lna_gain_db(40)
260 .vga_gain_db(62)
261 .build()
262 .is_ok()
263 );
264 }
265}