1#![deny(rust_2018_idioms)]
2#![cfg_attr(not(test), no_std)]
3#![doc = include_str!("../README.md")]
4
5#[cfg_attr(feature = "defmt", derive(defmt::Format))]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Bandwidth {
10 _7KHz,
11 _10KHz,
12 _15KHz,
13 _20KHz,
14 _31KHz,
15 _41KHz,
16 _62KHz,
17 _125KHz,
18 _250KHz,
19 _500KHz,
20}
21
22impl Bandwidth {
23 pub const fn hz(self) -> u32 {
24 match self {
25 Bandwidth::_7KHz => 7810u32,
26 Bandwidth::_10KHz => 10420u32,
27 Bandwidth::_15KHz => 15630u32,
28 Bandwidth::_20KHz => 20830u32,
29 Bandwidth::_31KHz => 31250u32,
30 Bandwidth::_41KHz => 41670u32,
31 Bandwidth::_62KHz => 62500u32,
32 Bandwidth::_125KHz => 125000u32,
33 Bandwidth::_250KHz => 250000u32,
34 Bandwidth::_500KHz => 500000u32,
35 }
36 }
37}
38
39impl From<Bandwidth> for u32 {
40 fn from(value: Bandwidth) -> Self {
41 value.hz()
42 }
43}
44
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum SpreadingFactor {
50 _5,
51 _6,
52 _7,
53 _8,
54 _9,
55 _10,
56 _11,
57 _12,
58}
59
60impl SpreadingFactor {
61 pub const fn factor(self) -> u32 {
62 match self {
63 SpreadingFactor::_5 => 5,
64 SpreadingFactor::_6 => 6,
65 SpreadingFactor::_7 => 7,
66 SpreadingFactor::_8 => 8,
67 SpreadingFactor::_9 => 9,
68 SpreadingFactor::_10 => 10,
69 SpreadingFactor::_11 => 11,
70 SpreadingFactor::_12 => 12,
71 }
72 }
73}
74
75impl From<SpreadingFactor> for u32 {
76 fn from(sf: SpreadingFactor) -> Self {
77 sf.factor()
78 }
79}
80
81#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum CodingRate {
87 _4_5,
88 _4_6,
89 _4_7,
90 _4_8,
91}
92
93impl CodingRate {
94 pub const fn denom(&self) -> u32 {
95 match self {
96 CodingRate::_4_5 => 5,
97 CodingRate::_4_6 => 6,
98 CodingRate::_4_7 => 7,
99 CodingRate::_4_8 => 8,
100 }
101 }
102}
103
104#[cfg_attr(feature = "defmt", derive(defmt::Format))]
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107#[derive(Debug, Clone, Copy, PartialEq)]
108pub struct BaseBandModulationParams {
109 pub sf: SpreadingFactor,
110 pub bw: Bandwidth,
111 pub cr: CodingRate,
112 pub ldro: bool,
114 t_sym_us: u32,
116}
117
118impl BaseBandModulationParams {
119 pub const fn new(sf: SpreadingFactor, bw: Bandwidth, cr: CodingRate) -> Self {
124 let t_sym_us = 2u32.pow(sf.factor()) * 1_000_000 / bw.hz();
125 let ldro = t_sym_us >= 16_384;
129 Self { sf, bw, cr, ldro, t_sym_us }
130 }
131
132 pub const fn delay_in_symbols(&self, delay_in_ms: u32) -> u16 {
133 (delay_in_ms * 1000 / self.t_sym_us) as u16
134 }
135
136 pub const fn symbols_to_ms(&self, symbols: u32) -> u32 {
137 (self.t_sym_us * symbols) / 1_000
138 }
139
140 pub const fn time_on_air_us(
143 &self,
144 preamble: Option<u8>,
145 explicit_header: bool,
146 len: u8,
147 ) -> u32 {
148 let sf = self.sf.factor() as i32;
149 let t_sym_us = self.t_sym_us;
150
151 let cr = self.cr.denom() as i32;
152 let de = if self.ldro {
153 1
154 } else {
155 0
156 };
157 let h = if explicit_header {
158 0
159 } else {
160 1
161 };
162
163 const fn div_ceil(num: i32, denom: i32) -> i32 {
164 (num - 1) / denom + 1
165 }
166
167 let big_ratio = div_ceil(8 * len as i32 - 4 * sf + 28 + 16 - 20 * h, 4 * (sf - 2 * de));
168 let big_ratio = if big_ratio > 0 {
169 big_ratio
170 } else {
171 0
172 };
173 let payload_symb_nb = (8 + big_ratio * cr) as u32;
174
175 match preamble {
176 None => t_sym_us * payload_symb_nb,
177 Some(preamble) => (4 * preamble as u32 + 17 + 4 * payload_symb_nb) * t_sym_us / 4,
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 const LORAWAN_OVERHEAD: u8 = 13;
187 const SF5BW500: BaseBandModulationParams =
189 BaseBandModulationParams::new(SpreadingFactor::_5, Bandwidth::_500KHz, CodingRate::_4_5);
190
191 const SF7BW250: BaseBandModulationParams =
193 BaseBandModulationParams::new(SpreadingFactor::_7, Bandwidth::_250KHz, CodingRate::_4_5);
194 const SF7BW125: BaseBandModulationParams =
196 BaseBandModulationParams::new(SpreadingFactor::_7, Bandwidth::_125KHz, CodingRate::_4_5);
197 const SF8BW125: BaseBandModulationParams =
199 BaseBandModulationParams::new(SpreadingFactor::_8, Bandwidth::_125KHz, CodingRate::_4_5);
200 const SF9BW125: BaseBandModulationParams =
202 BaseBandModulationParams::new(SpreadingFactor::_9, Bandwidth::_125KHz, CodingRate::_4_5);
203 const SF10BW125: BaseBandModulationParams =
205 BaseBandModulationParams::new(SpreadingFactor::_10, Bandwidth::_125KHz, CodingRate::_4_5);
206 const SF11BW125: BaseBandModulationParams =
208 BaseBandModulationParams::new(SpreadingFactor::_11, Bandwidth::_125KHz, CodingRate::_4_5);
209 const SF12BW125: BaseBandModulationParams =
211 BaseBandModulationParams::new(SpreadingFactor::_12, Bandwidth::_125KHz, CodingRate::_4_5);
212
213 fn lorawan_airtime_us(params: &BaseBandModulationParams, app_payload_length: u8) -> u32 {
214 params.time_on_air_us(Some(8), true, LORAWAN_OVERHEAD + app_payload_length)
215 }
216
217 #[test]
222 fn time_on_air_for_short_messages() {
223 assert_eq!(1152, SF5BW500.time_on_air_us(None, true, 0));
224 assert_eq!(6656, SF7BW250.time_on_air_us(None, true, 0));
225 assert_eq!(13312, SF7BW125.time_on_air_us(None, true, 0));
226 }
227
228 #[test]
229 fn time_on_air() {
230 let length = 25;
231 assert_eq!(41_088, lorawan_airtime_us(&SF7BW250, length));
232 assert_eq!(82_176, lorawan_airtime_us(&SF7BW125, length));
233 assert_eq!(143_872, lorawan_airtime_us(&SF8BW125, length));
234 assert_eq!(267_264, lorawan_airtime_us(&SF9BW125, length));
235 assert_eq!(493_568, lorawan_airtime_us(&SF10BW125, length));
236 assert_eq!(1_069_056, lorawan_airtime_us(&SF11BW125, length));
237 assert_eq!(1_974_272, lorawan_airtime_us(&SF12BW125, length));
238
239 let length = 26;
240 assert_eq!(41_088, lorawan_airtime_us(&SF7BW250, length));
241 assert_eq!(82_176, lorawan_airtime_us(&SF7BW125, length));
242 assert_eq!(154_112, lorawan_airtime_us(&SF8BW125, length));
243 assert_eq!(267_264, lorawan_airtime_us(&SF9BW125, length));
244 assert_eq!(493_568, lorawan_airtime_us(&SF10BW125, length));
245 assert_eq!(1_069_056, lorawan_airtime_us(&SF11BW125, length));
246 assert_eq!(1_974_272, lorawan_airtime_us(&SF12BW125, length));
247
248 let length = 27;
249 assert_eq!(41_088, lorawan_airtime_us(&SF7BW250, length));
250 assert_eq!(82_176, lorawan_airtime_us(&SF7BW125, length));
251 assert_eq!(154_112, lorawan_airtime_us(&SF8BW125, length));
252 assert_eq!(287_744, lorawan_airtime_us(&SF9BW125, length));
253 assert_eq!(534_528, lorawan_airtime_us(&SF10BW125, length));
254 assert_eq!(1_069_056, lorawan_airtime_us(&SF11BW125, length));
255 assert_eq!(1_974_272, lorawan_airtime_us(&SF12BW125, length));
256
257 let length = 28;
258 assert_eq!(43_648, lorawan_airtime_us(&SF7BW250, length));
259 assert_eq!(87_296, lorawan_airtime_us(&SF7BW125, length));
260 assert_eq!(154_112, lorawan_airtime_us(&SF8BW125, length));
261 assert_eq!(287_744, lorawan_airtime_us(&SF9BW125, length));
262 assert_eq!(534_528, lorawan_airtime_us(&SF10BW125, length));
263 assert_eq!(1_150_976, lorawan_airtime_us(&SF11BW125, length));
264 assert_eq!(2_138_112, lorawan_airtime_us(&SF12BW125, length));
265 }
266}