pamoja_lorawan/device/relayed.rs
1//! Sending through a relay: when the wake-on-radio frame goes out, on which channel, with
2//! how long a preamble, and what the relay's answer changes, TS011-1.0.1 chapters 3 and 5.
3
4use pamoja_lora::region::{Modulation, RelayChannel};
5use pamoja_lora::LinkSettings;
6
7use super::{DeviceError, EndDevice, Window};
8use crate::relay::{
9 open_wor_ack, wor_join_request, wor_uplink, CadPeriodicity, CadToRx, Carrier, Forward,
10 RelayActivation, RelaySync, Synchronization, XtalAccuracy, FORWARD_OVERHEAD, RXR_DELAY_US,
11 WOR_ACK_DELAY_US, WOR_ACK_LEN, WOR_ATTEMPTS_WO_ACK, WOR_DATA_DELAY_US, WOR_JOIN_REQUEST_LEN,
12 WOR_UPLINK_LEN,
13};
14
15/// How many join attempts a device left to itself makes before one goes through a relay,
16/// TS011-1.0.1 appendix 5.
17const JOINS_PER_RELAYED: u32 = 4;
18
19/// How many uplinks may go unanswered before a device left to itself starts using a relay,
20/// TS011-1.0.1 appendix 5.
21const QUIET_UPLINKS_TO_RELAY: u16 = 16;
22
23/// The wake-on-radio frame an uplink goes out behind, and where it is heard, TS011-1.0.1
24/// section 5.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub struct WakeUp {
27 frame: [u8; WOR_UPLINK_LEN],
28 len: usize,
29 /// When to start sending it, in the caller's microseconds.
30 pub start_us: u64,
31 /// Where and how fast.
32 pub carrier: Carrier,
33 /// The LoRa settings, with the preamble this frame needs: an explicit header and a
34 /// payload CRC, sent with inverted IQ, as RP002-1.0.5 table 124 has.
35 pub link: LinkSettings,
36 /// The power to ask of the radio, the region's default.
37 pub output_dbm: i8,
38 /// How long it holds the air, in microseconds.
39 pub airtime_us: u64,
40}
41
42impl WakeUp {
43 /// Returns the frame to send.
44 ///
45 /// # Returns
46 ///
47 /// Five bytes ahead of a join request, fifteen ahead of an uplink.
48 #[must_use]
49 pub fn frame(&self) -> &[u8] {
50 &self.frame[..self.len]
51 }
52}
53
54/// When and where a relay's acknowledgment would arrive.
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct AckWindow {
57 /// When it starts, in the caller's microseconds.
58 pub start_us: u64,
59 /// Where and how fast: the channel's acknowledgment frequency, at the WOR frame's data
60 /// rate.
61 pub carrier: Carrier,
62 /// The LoRa settings to listen with, RP002-1.0.5 table 125.
63 pub link: LinkSettings,
64 /// How long the acknowledgment lasts, in microseconds.
65 pub airtime_us: u64,
66}
67
68/// The wake-on-radio exchange an uplink under a relay goes out behind.
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub struct RelayExchange {
71 /// The frame that wakes the relay.
72 pub wake_up: WakeUp,
73 /// Where the relay's acknowledgment would arrive, or `None` ahead of a join request,
74 /// which TS011-1.0.1 section 3.3 has no relay acknowledge.
75 pub ack: Option<AckWindow>,
76 /// When the uplink itself goes out, in the caller's microseconds. It is the same
77 /// whether or not the acknowledgment arrives, as section 3.7.2 asks.
78 pub uplink_start_us: u64,
79 /// The window a forwarded downlink arrives in, timed from the end of the uplink like
80 /// the other two, RP002-1.0.5 table 126.
81 pub rxr: Window,
82}
83
84/// What an end device does once its wake-on-radio frame went unanswered.
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum WorNext {
87 /// Send the uplink at the time the exchange named anyway, TS011-1.0.1 section 3.4.
88 Uplink,
89 /// Wake the relay again first, as the network's `BackOff` asks.
90 WakeUp(RelayExchange),
91}
92
93/// What a relay's last acknowledgment said about itself, TS011-1.0.1 table 14.
94#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
95pub struct RelayStatus {
96 /// How often it scans.
97 pub cad_periodicity: CadPeriodicity,
98 /// How accurate its crystal is.
99 pub xtal_accuracy: XtalAccuracy,
100 /// How long it takes to start receiving.
101 pub cad_to_rx: CadToRx,
102 /// The data rate it forwards at, which bounds what the device may send.
103 pub relay_data_rate: u8,
104 /// Whether it will forward.
105 pub forward: Forward,
106}
107
108/// The wake-on-radio frame a device sent and is waiting on.
109#[derive(Clone, Copy, Debug, PartialEq, Eq)]
110struct Sent {
111 join: bool,
112 started_us: u64,
113 preamble_symbols: u16,
114 wor: Carrier,
115 ack: Carrier,
116 uplink: Carrier,
117 wfcnt: u32,
118 second_channel: bool,
119 symbol_us: u64,
120}
121
122/// What an end device keeps to send through a relay.
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub(crate) struct Relayed {
125 pub(crate) activation: RelayActivation,
126 pub(crate) smart_level: u8,
127 pub(crate) back_off: u8,
128 pub(crate) second_channel: Option<RelayChannel>,
129 pub(crate) enabled: bool,
130 pub(crate) automatic: bool,
131 pub(crate) wfcnt: u32,
132 pub(crate) attempts: u8,
133 pub(crate) quiet_uplinks: u16,
134 pub(crate) channel: u8,
135 pub(crate) fixed_channel: bool,
136 pub(crate) heard: Option<RelayStatus>,
137 sync: Option<Synchronization>,
138 reference_second: bool,
139 sent: Option<Sent>,
140}
141
142impl Relayed {
143 pub(crate) const fn new() -> Relayed {
144 Relayed {
145 activation: RelayActivation::DeviceControlled,
146 smart_level: 0,
147 back_off: 0,
148 second_channel: None,
149 enabled: false,
150 automatic: true,
151 wfcnt: 0,
152 attempts: 0,
153 quiet_uplinks: 0,
154 channel: 0,
155 fixed_channel: false,
156 heard: None,
157 sync: None,
158 reference_second: false,
159 sent: None,
160 }
161 }
162
163 /// What the device knows about when its relay listens.
164 ///
165 /// A device is out of the initialized state once a relay has answered it at all, on a
166 /// wake-on-radio acknowledgment or a downlink in the relay window, which is also when
167 /// it stops alternating the region's default channels, TS011-1.0.1 section 3.9.
168 pub(crate) const fn sync(&self) -> RelaySync {
169 match (
170 self.sync.is_some(),
171 self.heard.is_some() || self.fixed_channel,
172 ) {
173 (true, _) => RelaySync::Synchronized,
174 (false, true) => RelaySync::Unsynchronized,
175 (false, false) => RelaySync::Initialized,
176 }
177 }
178
179 /// A new join request: the mode a network set lapses with the session, and a device
180 /// left to itself tries a relay on one join in four, TS011-1.0.1 section 10.2 and
181 /// appendix 5.
182 pub(crate) fn joining(&mut self, joins: u32) {
183 self.activation = RelayActivation::DeviceControlled;
184 if self.automatic {
185 self.enabled = joins % JOINS_PER_RELAYED == JOINS_PER_RELAYED - 1;
186 }
187 self.attempts = 0;
188 self.sent = None;
189 }
190
191 /// A join accept arrived: the wake-on-radio counter restarts, and a device left to
192 /// itself keeps the relay only if the accept came back through one.
193 pub(crate) fn joined(&mut self, through_relay: bool) {
194 self.wfcnt = 0;
195 self.attempts = 0;
196 self.quiet_uplinks = 0;
197 self.sent = None;
198 self.sync = None;
199 self.fixed_channel = through_relay;
200 if self.automatic {
201 self.enabled = through_relay;
202 }
203 }
204
205 /// A downlink arrived in the relay window, so the relay heard the device and it need no
206 /// longer alternate channels.
207 pub(crate) fn heard_on_rxr(&mut self) {
208 self.fixed_channel = true;
209 }
210
211 /// Takes what an `EndDeviceConfReq` sets, TS011-1.0.1 section 10.2.
212 pub(crate) fn configure(
213 &mut self,
214 activation: RelayActivation,
215 smart_level: u8,
216 back_off: u8,
217 second_channel: Option<RelayChannel>,
218 ) {
219 self.activation = activation;
220 self.automatic = activation == RelayActivation::DeviceControlled;
221 self.smart_level = smart_level;
222 self.back_off = back_off;
223 self.second_channel = second_channel;
224 match activation {
225 RelayActivation::Disabled => {
226 self.enabled = false;
227 self.sent = None;
228 }
229 RelayActivation::Enabled => self.enabled = true,
230 RelayActivation::Dynamic => self.quiet_uplinks = 0,
231 RelayActivation::DeviceControlled => {}
232 }
233 }
234
235 /// Forgets the timing, and with it the relay's settings once the frames keep going
236 /// unanswered, TS011-1.0.1 section 3.9.
237 fn lose_synchronization(&mut self) {
238 if self.sync.take().is_none() {
239 self.heard = None;
240 }
241 }
242
243 /// Counts an uplink that went unanswered, and says whether a device left to itself
244 /// should start using a relay, TS011-1.0.1 appendix 5 and table 41.
245 pub(crate) fn uplink_unanswered(&mut self, smart_uplinks: u16) {
246 self.quiet_uplinks = self.quiet_uplinks.saturating_add(1);
247 let enough = match self.activation {
248 RelayActivation::Dynamic => smart_uplinks,
249 RelayActivation::DeviceControlled if self.automatic => QUIET_UPLINKS_TO_RELAY,
250 _ => return,
251 };
252 if self.quiet_uplinks >= enough {
253 self.enabled = true;
254 }
255 }
256
257 /// Counts a downlink, which is what a device in dynamic mode is waiting for.
258 pub(crate) fn heard_downlink(&mut self) {
259 self.quiet_uplinks = 0;
260 }
261}
262
263impl EndDevice<'_> {
264 /// Returns how the device decides to use a relay, TS011-1.0.1 table 40.
265 ///
266 /// # Returns
267 ///
268 /// The mode, which the network sets with `EndDeviceConfReq` and which every join puts
269 /// back to [`RelayActivation::DeviceControlled`].
270 #[must_use]
271 pub const fn relay_activation(&self) -> RelayActivation {
272 self.relayed.activation
273 }
274
275 /// Reports whether the next uplink goes through a relay.
276 ///
277 /// # Returns
278 ///
279 /// `true` while relay mode is on.
280 #[must_use]
281 pub const fn relaying(&self) -> bool {
282 self.relayed.enabled
283 }
284
285 /// Turns relay mode on or off, and takes the decision from the device itself.
286 ///
287 /// A device left to itself, which is where every device starts, decides this as
288 /// TS011-1.0.1 appendix 5 recommends: it tries a relay on one join in four, keeps it
289 /// only if the join accept comes back through one, turns it on again after sixteen
290 /// unanswered uplinks, and gives it up after eight frames a relay never answered. From
291 /// this call on, that is the caller's to decide, until the network takes it over with
292 /// `EndDeviceConfReq` or hands it back with its end-device controlled mode. What a
293 /// device knows about a relay it stops hearing is still given up either way.
294 ///
295 /// # Arguments
296 ///
297 /// * `on` - whether to send through a relay.
298 ///
299 /// # Returns
300 ///
301 /// `false` when the network has taken the decision with `EndDeviceConfReq`, leaving the
302 /// mode as it was.
303 ///
304 /// # Examples
305 ///
306 /// A reading goes out behind a frame that wakes the relay:
307 ///
308 /// ```
309 /// use pamoja_lora::region::Region;
310 /// use pamoja_lorawan::device::{EndDevice, Settings, WorNext};
311 /// use pamoja_lorawan::relay::Carrier;
312 /// use pamoja_lorawan::Session;
313 ///
314 /// let session = Session::new(0x2601_1BDA, [0x2B; 16], [0x99; 16]);
315 /// let mut device = EndDevice::personalized(Region::Eu868.plan(), session, Settings::new(2, 14))?;
316 /// assert!(device.use_relay(true));
317 ///
318 /// // The wake-on-radio frame goes out on the region's first relay channel, ahead of
319 /// // the uplink it names.
320 /// let reading = device.send(2, b"21.5", false, 0)?;
321 /// let exchange = reading.relay.expect("relay mode is on");
322 /// assert_eq!(exchange.wake_up.carrier, Carrier::new(865_100_000, 3));
323 /// assert_eq!(exchange.wake_up.start_us, 0);
324 ///
325 /// // A device that has heard from no relay covers a whole second of scanning, and the
326 /// // uplink follows once the acknowledgment would have come and gone.
327 /// assert_eq!(exchange.wake_up.link.preamble_symbols(), 259);
328 /// let ack = exchange.ack.expect("an uplink is acknowledged");
329 /// assert_eq!(exchange.uplink_start_us, ack.start_us + ack.airtime_us + 50_000);
330 ///
331 /// // With nothing in the acknowledgment window, the uplink goes out anyway.
332 /// assert_eq!(device.no_wor_ack(ack.start_us + ack.airtime_us)?, WorNext::Uplink);
333 /// # Ok::<(), pamoja_lorawan::device::DeviceError>(())
334 /// ```
335 pub fn use_relay(&mut self, on: bool) -> bool {
336 match self.relayed.activation {
337 RelayActivation::Dynamic | RelayActivation::DeviceControlled => {
338 self.relayed.enabled = on;
339 self.relayed.automatic = false;
340 if !on {
341 self.relayed.sent = None;
342 }
343 true
344 }
345 _ => false,
346 }
347 }
348
349 /// Returns what the device knows about when its relay listens, TS011-1.0.1 section 3.9.
350 ///
351 /// # Returns
352 ///
353 /// The synchronization state.
354 #[must_use]
355 pub const fn relay_sync(&self) -> RelaySync {
356 self.relayed.sync()
357 }
358
359 /// Returns what the relay's last acknowledgment said about itself.
360 ///
361 /// # Returns
362 ///
363 /// Its scan period, crystal, switching time, forwarding data rate and whether it
364 /// forwards, or `None` before one has arrived.
365 #[must_use]
366 pub const fn relay_status(&self) -> Option<RelayStatus> {
367 self.relayed.heard
368 }
369
370 /// Returns the wake-on-radio frame counter, which every frame raises and a join accept
371 /// resets, TS011-1.0.1 section 5.3.2.
372 ///
373 /// # Returns
374 ///
375 /// The counter the next frame will use.
376 #[must_use]
377 pub const fn wor_counter(&self) -> u32 {
378 self.relayed.wfcnt
379 }
380
381 /// Reads the acknowledgment a relay answered the last wake-on-radio frame with.
382 ///
383 /// The device is now synchronized: it knows when the relay scans, so its next frames
384 /// carry only as much preamble as the two crystals could drift, and it holds its
385 /// payloads to what the relay forwards.
386 ///
387 /// # Arguments
388 ///
389 /// * `frame` - the bytes the radio received in the acknowledgment window.
390 ///
391 /// # Returns
392 ///
393 /// What the relay said about itself.
394 ///
395 /// # Errors
396 ///
397 /// Returns [`DeviceError::NothingPending`] with no wake-on-radio frame waiting on an
398 /// answer, [`DeviceError::NotJoined`] before the device has a session, and
399 /// [`DeviceError::Frame`] for an acknowledgment that does not decode or verify.
400 pub fn heard_wor_ack(&mut self, frame: &[u8]) -> Result<RelayStatus, DeviceError> {
401 let session = self.session.ok_or(DeviceError::NotJoined)?;
402 let sent = self.relayed.sent.ok_or(DeviceError::NothingPending)?;
403 let state = open_wor_ack(
404 frame,
405 &session.wor_keys(),
406 session.dev_addr(),
407 sent.wfcnt,
408 sent.ack,
409 sent.uplink,
410 )?;
411 self.relayed.sent = None;
412 self.relayed.attempts = 0;
413 self.relayed.sync = Some(Synchronization::from_ack(
414 sent.started_us,
415 sent.preamble_symbols,
416 sent.symbol_us,
417 &state,
418 ));
419 self.relayed.reference_second = sent.second_channel;
420 self.relayed.fixed_channel = true;
421 let heard = RelayStatus {
422 cad_periodicity: state.cad_periodicity,
423 xtal_accuracy: state.xtal_accuracy,
424 cad_to_rx: state.cad_to_rx,
425 relay_data_rate: state.relay_data_rate,
426 forward: state.forward,
427 };
428 self.relayed.heard = Some(heard);
429 Ok(heard)
430 }
431
432 /// Says what to do once the acknowledgment window closed with nothing in it.
433 ///
434 /// The uplink goes out anyway unless the network's `BackOff` asks for more attempts
435 /// first. After [`WOR_ATTEMPTS_WO_ACK`] frames without an answer the device gives up
436 /// what it knows: first when the relay scans, then the relay altogether.
437 ///
438 /// # Arguments
439 ///
440 /// * `now_us` - the time, in microseconds.
441 ///
442 /// # Returns
443 ///
444 /// Whether to send the uplink or wake the relay again.
445 ///
446 /// # Errors
447 ///
448 /// Returns [`DeviceError::NothingPending`] with no wake-on-radio frame waiting on an
449 /// answer, and whatever building another frame returns.
450 pub fn no_wor_ack(&mut self, now_us: u64) -> Result<WorNext, DeviceError> {
451 let sent = self.relayed.sent.ok_or(DeviceError::NothingPending)?;
452 self.relayed.sent = None;
453 self.relayed.attempts = self.relayed.attempts.saturating_add(1);
454 if self.relayed.attempts >= WOR_ATTEMPTS_WO_ACK {
455 self.relayed.attempts = 0;
456 self.relayed.lose_synchronization();
457 if self.relayed.automatic
458 && self.relayed.activation == RelayActivation::DeviceControlled
459 {
460 // TS011-1.0.1 appendix 5: a device left to itself stops using a relay that
461 // never answers, and counts its unanswered uplinks afresh before trying
462 // another one.
463 self.relayed.enabled = false;
464 self.relayed.quiet_uplinks = 0;
465 return Ok(WorNext::Uplink);
466 }
467 }
468 // The next frame alternates the region's default channels while the device has no
469 // relay of its own to aim at, TS011-1.0.1 section 3.2.2.
470 if !self.relayed.fixed_channel {
471 self.relayed.channel = self.relayed.channel.wrapping_add(1);
472 }
473 if self.relayed.back_off != 0
474 && !self.relayed.attempts.is_multiple_of(self.relayed.back_off)
475 {
476 match self.wor_exchange(sent.uplink, sent.join, now_us)? {
477 Some(exchange) => return Ok(WorNext::WakeUp(exchange)),
478 None => return Ok(WorNext::Uplink),
479 }
480 }
481 Ok(WorNext::Uplink)
482 }
483
484 /// Builds the wake-on-radio exchange an uplink goes out behind, and records what it
485 /// costs the device's duty cycle.
486 pub(super) fn wor_exchange(
487 &mut self,
488 uplink: Carrier,
489 join: bool,
490 now_us: u64,
491 ) -> Result<Option<RelayExchange>, DeviceError> {
492 if !self.relayed.enabled {
493 return Ok(None);
494 }
495 let Some(channel) = self.wor_channel() else {
496 return Ok(None);
497 };
498 let link = self.wor_link(channel.data_rate)?;
499 let symbol_us = link.symbol_time_us();
500 let earliest = now_us.max(self.free_at(channel.wor_frequency_hz));
501 let second_channel = self.relayed.second_channel.is_some();
502
503 let (start_us, preamble_symbols) = match self.relayed.sync {
504 Some(sync) => {
505 let other = second_channel != self.relayed.reference_second;
506 match sync.next_wor(earliest, self.settings.crystal_ppm, symbol_us, other) {
507 Some(slot) => (slot.start_us, slot.preamble_symbols),
508 None => {
509 self.relayed.sync = None;
510 (earliest, self.unsynchronized_preamble(symbol_us))
511 }
512 }
513 }
514 None => (earliest, self.unsynchronized_preamble(symbol_us)),
515 };
516
517 let wor = Carrier::new(channel.wor_frequency_hz, channel.data_rate);
518 let ack_carrier = Carrier::new(channel.ack_frequency_hz, channel.data_rate);
519 let mut frame = [0u8; WOR_UPLINK_LEN];
520 let (len, wfcnt) = if join {
521 frame[..WOR_JOIN_REQUEST_LEN].copy_from_slice(&wor_join_request(uplink)?);
522 (WOR_JOIN_REQUEST_LEN, 0)
523 } else {
524 let session = self.session.ok_or(DeviceError::NotJoined)?;
525 let wfcnt = self.relayed.wfcnt;
526 frame = wor_uplink(&session.wor_keys(), session.dev_addr(), wfcnt, uplink, wor)?;
527 self.relayed.wfcnt = wfcnt.saturating_add(1);
528 (WOR_UPLINK_LEN, wfcnt)
529 };
530
531 let link = link.with_preamble(preamble_symbols);
532 let airtime_us = link.airtime_us(len);
533 let ended_us = start_us + airtime_us;
534 let ack_link = self.wor_link(channel.data_rate)?;
535 let ack_airtime_us = ack_link.airtime_us(WOR_ACK_LEN);
536 let (ack, uplink_start_us) = if join {
537 (None, ended_us + u64::from(WOR_DATA_DELAY_US))
538 } else {
539 let start_us = ended_us + u64::from(WOR_ACK_DELAY_US);
540 (
541 Some(AckWindow {
542 start_us,
543 carrier: ack_carrier,
544 link: ack_link,
545 airtime_us: ack_airtime_us,
546 }),
547 start_us + ack_airtime_us + u64::from(WOR_DATA_DELAY_US),
548 )
549 };
550
551 let rxr_data_rate = self
552 .plan
553 .rx1_data_rate(uplink.data_rate, 0)
554 .ok_or(DeviceError::DataRate(uplink.data_rate))?;
555 let rxr = Window {
556 delay_us: RXR_DELAY_US,
557 frequency_hz: channel.wor_frequency_hz,
558 data_rate: rxr_data_rate,
559 link: self.wor_link(rxr_data_rate)?,
560 };
561
562 self.record_air(start_us, airtime_us, channel.wor_frequency_hz);
563 self.relayed.sent = Some(Sent {
564 join,
565 started_us: start_us,
566 preamble_symbols,
567 wor,
568 ack: ack_carrier,
569 uplink,
570 wfcnt,
571 second_channel,
572 symbol_us,
573 });
574 Ok(Some(RelayExchange {
575 wake_up: WakeUp {
576 frame,
577 len,
578 start_us,
579 carrier: wor,
580 link,
581 output_dbm: self.output_dbm(channel.wor_frequency_hz, 0),
582 airtime_us,
583 },
584 ack,
585 uplink_start_us,
586 rxr,
587 }))
588 }
589
590 /// The channel the next wake-on-radio frame goes out on: the second channel a network
591 /// set, or the region's default channels in turn, TS011-1.0.1 sections 3.2.2 and 3.2.3.
592 fn wor_channel(&self) -> Option<RelayChannel> {
593 if let Some(second) = self.relayed.second_channel {
594 return Some(second);
595 }
596 let count = self.plan.relay_channels.len();
597 if count == 0 {
598 return None;
599 }
600 self.plan.relay_channel(self.relayed.channel % count as u8)
601 }
602
603 /// The preamble of a device that cannot predict the relay's next scan: one scan period
604 /// of it, or a whole second before any acknowledgment has arrived, TS011-1.0.1
605 /// section 5.2.
606 fn unsynchronized_preamble(&self, symbol_us: u64) -> u16 {
607 let (period, cad_to_rx) = match self.relayed.heard {
608 Some(heard) => (heard.cad_periodicity, heard.cad_to_rx),
609 None => (CadPeriodicity::Ms1000, CadToRx::Symbols8),
610 };
611 crate::relay::unsynchronized_preamble_symbols(period, symbol_us, cad_to_rx)
612 }
613
614 /// The LoRa settings of a data rate the downlink table numbers, with the payload CRC
615 /// every relay frame carries and a receive window does not, RP002-1.0.5 tables 124, 125
616 /// and 127.
617 fn wor_link(&self, data_rate: u8) -> Result<LinkSettings, DeviceError> {
618 match self
619 .plan
620 .downlink_data_rate(data_rate)
621 .map(|rate| rate.modulation)
622 {
623 Some(Modulation::LoRa {
624 spreading_factor,
625 bandwidth_hz,
626 }) => Ok(LinkSettings::new(spreading_factor, bandwidth_hz)),
627 _ => Err(DeviceError::DataRate(data_rate)),
628 }
629 }
630
631 /// What a relay's forwarding leaves for the application payload: its own frame at the
632 /// data rate the relay forwards at, less the forwarded metadata and the end device's
633 /// own header, TS011-1.0.1 section 8.3.
634 pub(super) fn relayed_room(&self) -> Option<usize> {
635 let heard = self.relayed.heard?;
636 let carried = self
637 .plan
638 .max_payload(heard.relay_data_rate, false)
639 .map(|limit| usize::from(limit.application))?;
640 Some(carried.saturating_sub(FORWARD_OVERHEAD + 13))
641 }
642}