pub struct Relay<'p> { /* private fields */ }Expand description
A LoRaWAN relay: an end device that scans for wake-on-radio frames, acknowledges and forwards the uplinks behind them, and sends the network’s answers on in each end device’s RXR window.
Like EndDevice, it owns no radio and no clock. One forwarded uplink runs like this:
next_scansays when and where to detect a preamble.- A WOR frame received there goes to
heard_wor, which says whether to acknowledge it and when to listen for the uplink. - That uplink goes to
heard_uplink, andforwardsends it to the network in the relay’s own uplink on portLA_FPORT_RELAY. - What the relay’s receive windows hear goes to
heard_in, which reads the relay commands among its MAC commands and turns a downlink for the end device into anRxrDownlink.
§Examples
A relay forwards a sensor’s reading and sends the network’s answer back:
use pamoja_lora::region::Region;
use pamoja_lorawan::device::{EndDevice, ReceiveWindow, Settings};
use pamoja_lorawan::mac::MacCommand;
use pamoja_lorawan::relay::{
root_wor_s_key, wor_uplink, CadToRx, Carrier, ForwardedUplink, Relay, RelayConfig,
RelayHeard, RelaySettings, Wake, WorKeys, XtalAccuracy, LA_FPORT_RELAY,
};
use pamoja_lorawan::{Downlink, Session, Uplink};
let plan = Region::Eu868.plan();
let relay_session = Session::new(0x2601_0001, [0x11; 16], [0x22; 16]);
let device = EndDevice::personalized(plan, relay_session, Settings::new(2, 14))?;
let settings = RelaySettings::new(XtalAccuracy::Ppm10, CadToRx::Symbols2);
let mut relay = Relay::new(device, settings);
relay.start(RelayConfig::region_default(plan).expect("EU868 has relay channels"))?;
// The network trusts a sensor to it, as an UpdateUplinkListReq would.
let sensor = Session::new(0x2601_1BDA, [0x2B; 16], [0x99; 16]);
let root = root_wor_s_key(&[0x2B; 16]);
relay.trust(0, sensor.dev_addr(), &root, 0, 63, 0);
// The first scan starts now, on 865.1 MHz.
let scan = relay.next_scan(0).expect("running");
assert_eq!(scan.carrier, Carrier::new(865_100_000, 3));
// The sensor's WOR frame says its uplink follows on 868.1 MHz at DR5.
let uplink = Carrier::new(868_100_000, 5);
let wor = wor_uplink(&WorKeys::derive(&root, sensor.dev_addr()), sensor.dev_addr(), 0, uplink, scan.carrier)?;
let Wake::Uplink { acknowledgment, listen, .. } = relay.heard_wor(&scan, &wor, -90, 4, 900_000)?
else {
panic!("a trusted device's uplink follows");
};
assert_eq!(acknowledgment.expect("room to acknowledge").carrier, Carrier::new(865_300_000, 3));
let listen = listen.expect("forwarding is available");
// The uplink arrives, and goes out in the relay's own uplink on port 226.
let frame = sensor.encode_uplink(&Uplink::new(0, 1, b"21.5"))?;
let due = relay.heard_uplink(frame.as_bytes(), -88, 6, listen.start_us + 60_000)?;
let transmission = relay.forward(due)?;
// The network reads it back, and answers the sensor through the relay.
let sent = relay_session.decode(transmission.frame.as_bytes(), 0)?;
assert_eq!(sent.fport(), Some(LA_FPORT_RELAY));
assert_eq!(ForwardedUplink::parse(sent.payload())?.phy_payload, frame.as_bytes());
let answer = sensor.encode_downlink(&Downlink::new(0, 1, b"ok"))?;
let reply = relay_session.encode_downlink(&Downlink::new(0, LA_FPORT_RELAY, answer.as_bytes()))?;
let RelayHeard::Downlink { downlink, .. } = relay.heard_in(ReceiveWindow::Rx1, reply.as_bytes(), 7)?
else {
panic!("a downlink for the sensor");
};
assert_eq!(downlink.frame(), answer.as_bytes());
assert_eq!(downlink.carrier.frequency_hz, 865_100_000, "on the WOR frequency");
assert_eq!(downlink.start_us, listen.start_us + 60_000 + 18_000_000);Implementations§
Source§impl<'p> Relay<'p>
impl<'p> Relay<'p>
Sourcepub const fn new(device: EndDevice<'p>, settings: RelaySettings) -> Relay<'p>
pub const fn new(device: EndDevice<'p>, settings: RelaySettings) -> Relay<'p>
Makes a relay of an end device.
It does not scan until start or its network’s RelayConfReq gives
it channels, and it forwards every join request and trusts no one until its network
says otherwise.
§Arguments
device- the relay’s own device, joined or to join.settings- what its radio can do.
§Returns
The relay.
Sourcepub fn device_mut(&mut self) -> &mut EndDevice<'p>
pub fn device_mut(&mut self) -> &mut EndDevice<'p>
Sourcepub fn into_device(self) -> EndDevice<'p>
pub fn into_device(self) -> EndDevice<'p>
Sourcepub const fn settings(&self) -> RelaySettings
pub const fn settings(&self) -> RelaySettings
Sourcepub const fn config(&self) -> Option<RelayConfig>
pub const fn config(&self) -> Option<RelayConfig>
Returns the channels the relay scans.
§Returns
The configuration, or None while the relay is stopped.
Sourcepub fn region_channel(&self, index: u8) -> Option<RelayChannel>
pub fn region_channel(&self, index: u8) -> Option<RelayChannel>
Sourcepub fn start(&mut self, config: RelayConfig) -> Result<(), RelayError>
pub fn start(&mut self, config: RelayConfig) -> Result<(), RelayError>
Starts scanning, or changes what a running relay scans from its next scan on.
§Arguments
config- the channels and how often.
§Errors
Returns RelayError::Configuration for a channel that is not LoRa or not in the
radio’s range, or scans so frequent that detecting a preamble and starting to receive
on one channel does not fit before the next.
Sourcepub const fn join_filter(&self) -> &JoinFilter
pub const fn join_filter(&self) -> &JoinFilter
Sourcepub fn join_filter_mut(&mut self) -> &mut JoinFilter
pub fn join_filter_mut(&mut self) -> &mut JoinFilter
Sourcepub const fn trusted(&self) -> &TrustedDevices
pub const fn trusted(&self) -> &TrustedDevices
Sourcepub fn trusted_mut(&mut self) -> &mut TrustedDevices
pub fn trusted_mut(&mut self) -> &mut TrustedDevices
Sourcepub fn trust(
&mut self,
index: u8,
dev_addr: u32,
root_wor_s_key: &[u8; 16],
next_wfcnt: u32,
reload_rate: u8,
bucket_size: u8,
) -> bool
pub fn trust( &mut self, index: u8, dev_addr: u32, root_wor_s_key: &[u8; 16], next_wfcnt: u32, reload_rate: u8, bucket_size: u8, ) -> bool
Trusts an end device, as an UpdateUplinkListReq with the same fields does.
§Arguments
index- the entry, 0 to 15.dev_addr- the device’s address.root_wor_s_key- its root relay session key.next_wfcnt- the WOR frame counter to expect from it next.reload_rate- its uplinks forwarded an hour, 63 for no limit.bucket_size- the coded bucket size multiplier, TS011-1.0.1 table 55.
§Returns
false for an index past 15.
Sourcepub const fn limits(&self) -> &ForwardLimits
pub const fn limits(&self) -> &ForwardLimits
Sourcepub fn limits_mut(&mut self) -> &mut ForwardLimits
pub fn limits_mut(&mut self) -> &mut ForwardLimits
Sourcepub fn next_scan(&mut self, now_us: u64) -> Option<Scan>
pub fn next_scan(&mut self, now_us: u64) -> Option<Scan>
Says when and where to scan next.
The first call after the relay starts begins its scan at now_us, with every
forwarding limit at one hour’s reload, as TS011-1.0.1 section 8.8 has. Scans fall on
whole periods from then, and a second channel’s half a period between, so a relay
that was busy picks the schedule up at the next of them, section 3.2.1.
§Arguments
now_us- the time, in microseconds.
§Returns
The scan, or None while the relay is stopped.
Sourcepub fn heard_wor(
&mut self,
scan: &Scan,
frame: &[u8],
rssi_dbm: i16,
snr_db: i8,
ended_us: u64,
) -> Result<Wake, RelayError>
pub fn heard_wor( &mut self, scan: &Scan, frame: &[u8], rssi_dbm: i16, snr_db: i8, ended_us: u64, ) -> Result<Wake, RelayError>
Reads a wake-on-radio frame a scan heard, TS011-1.0.1 sections 3.2.4 and 3.3.
A join request’s frame has the relay listen for the request, if the join request limit allows. A trusted device’s frame is verified against the counter the relay expects from it, and answered with a WOR ACK saying whether the relay forwards the uplink, which it listens for only if it does. A frame from a device the relay does not trust queues a notification of it for the network, once while one is waiting, if the notification limit allows.
§Arguments
scan- the scan that heard it.frame- the frame.rssi_dbm- its signal strength.snr_db- its signal-to-noise ratio.ended_us- when it finished arriving, in microseconds.
§Returns
What to do next.
§Errors
Returns RelayError::Stopped while stopped, RelayError::Busy while a forwarded
uplink waits to go out, RelayError::Frame for a frame that does not decode or, from
a trusted device’s address, does not verify, RelayError::Carrier for an uplink
on a carrier the relay cannot hear, and RelayError::Limited when a limit keeps it
from listening or notifying.
Sourcepub fn heard_uplink(
&mut self,
frame: &[u8],
rssi_dbm: i16,
snr_db: i8,
ended_us: u64,
) -> Result<u64, RelayError>
pub fn heard_uplink( &mut self, frame: &[u8], rssi_dbm: i16, snr_db: i8, ended_us: u64, ) -> Result<u64, RelayError>
Reads the uplink a wake-on-radio frame announced, and holds it to forward, TS011-1.0.1 sections 3.5, 8.1 and 8.6.
A join request must be one, and pass the join request filter. A data uplink must come from the device whose WOR frame announced it. Either spends a token from every limit it counts against.
§Arguments
frame- the uplink.rssi_dbm- its signal strength, which the forwarded metadata carries.snr_db- its signal-to-noise ratio, likewise.ended_us- when it finished arriving, in microseconds.
§Returns
When to forward it: RELAY_FWD_DELAY_US after it ended.
§Errors
Returns RelayError::NotListening with no uplink announced, RelayError::Frame
for a frame longer than the relay forwards or not of the kind announced,
RelayError::Foreign for another device’s uplink, RelayError::Filtered for a
join request the filter drops, and RelayError::Limited when a limit is reached.
Sourcepub fn uplink_missed(&mut self)
pub fn uplink_missed(&mut self)
Clears the uplink a wake-on-radio frame announced, once listening for it heard nothing.
Sourcepub fn forward_due(&self) -> Option<u64>
pub fn forward_due(&self) -> Option<u64>
Returns when the forwarded uplink waiting to go out is due.
§Returns
The time, or None with nothing waiting.
Sourcepub fn forward(&mut self, now_us: u64) -> Result<Transmission, RelayError>
pub fn forward(&mut self, now_us: u64) -> Result<Transmission, RelayError>
Sends the uplink heard_uplink holds, in the relay’s own
uplink on port LA_FPORT_RELAY, TS011-1.0.1 section 9.1.
It stays held when the relay’s device cannot send yet, and when the answers the
device owes crowd it out of this frame, which
Transmission::carries_payload reports; call again once the device is free.
§Arguments
now_us- the time, in microseconds.
§Returns
What the relay’s device transmits, with its receive windows.
§Errors
Returns RelayError::NothingHeld with nothing held, and RelayError::Device with
DeviceError::Wait before it is due or whatever else keeps the device from
sending.
Sourcepub fn heard_in(
&mut self,
window: ReceiveWindow,
frame: &[u8],
snr_db: i8,
) -> Result<RelayHeard, RelayError>
pub fn heard_in( &mut self, window: ReceiveWindow, frame: &[u8], snr_db: i8, ) -> Result<RelayHeard, RelayError>
Reads a frame heard in a given receive window of the relay’s last transmission.
Relay commands among its MAC commands reconfigure the relay, answered in order with
the rest. A downlink on port LA_FPORT_RELAY answering a forwarded uplink becomes
an RxrDownlink for the end device, TS011-1.0.1 section 9.2.
§Arguments
window- the window the radio heard the frame in.frame- the bytes the radio received.snr_db- the frame’s signal-to-noise ratio.
§Returns
What the frame was.
§Errors
Returns RelayError::Device for whatever the relay’s device refuses, as
EndDevice::heard_in does.
Sourcepub fn heard(
&mut self,
frame: &[u8],
snr_db: i8,
) -> Result<RelayHeard, RelayError>
pub fn heard( &mut self, frame: &[u8], snr_db: i8, ) -> Result<RelayHeard, RelayError>
Sourcepub fn nothing_heard(&mut self, now_us: u64) -> Result<Next, RelayError>
pub fn nothing_heard(&mut self, now_us: u64) -> Result<Next, RelayError>
Says what comes next once the relay’s receive windows closed with nothing for it, as
EndDevice::nothing_heard does. A forwarded uplink not repeated no longer waits on
an answer.
§Arguments
now_us- the time the second window closed, in microseconds.
§Returns
Whether to repeat the frame, join again, or move on.
§Errors
Returns RelayError::Device with DeviceError::NothingPending with no
transmission waiting on its windows.