pub struct EndDevice<'p> { /* private fields */ }Expand description
A LoRaWAN Class A end device.
See the module documentation for the exchange it runs.
Implementations§
Source§impl EndDevice<'_>
impl EndDevice<'_>
Sourcepub const fn relay_activation(&self) -> RelayActivation
pub const fn relay_activation(&self) -> RelayActivation
Returns how the device decides to use a relay, TS011-1.0.1 table 40.
§Returns
The mode, which the network sets with EndDeviceConfReq and which every join puts
back to RelayActivation::DeviceControlled.
Sourcepub fn use_relay(&mut self, on: bool) -> bool
pub fn use_relay(&mut self, on: bool) -> bool
Turns relay mode on or off, and takes the decision from the device itself.
A device left to itself, which is where every device starts, decides this as
TS011-1.0.1 appendix 5 recommends: it tries a relay on one join in four, keeps it
only if the join accept comes back through one, turns it on again after sixteen
unanswered uplinks, and gives it up after eight frames a relay never answered. From
this call on, that is the caller’s to decide, until the network takes it over with
EndDeviceConfReq or hands it back with its end-device controlled mode. What a
device knows about a relay it stops hearing is still given up either way.
§Arguments
on- whether to send through a relay.
§Returns
false when the network has taken the decision with EndDeviceConfReq, leaving the
mode as it was.
§Examples
A reading goes out behind a frame that wakes the relay:
use pamoja_lora::region::Region;
use pamoja_lorawan::device::{EndDevice, Settings, WorNext};
use pamoja_lorawan::relay::Carrier;
use pamoja_lorawan::Session;
let session = Session::new(0x2601_1BDA, [0x2B; 16], [0x99; 16]);
let mut device = EndDevice::personalized(Region::Eu868.plan(), session, Settings::new(2, 14))?;
assert!(device.use_relay(true));
// The wake-on-radio frame goes out on the region's first relay channel, ahead of
// the uplink it names.
let reading = device.send(2, b"21.5", false, 0)?;
let exchange = reading.relay.expect("relay mode is on");
assert_eq!(exchange.wake_up.carrier, Carrier::new(865_100_000, 3));
assert_eq!(exchange.wake_up.start_us, 0);
// A device that has heard from no relay covers a whole second of scanning, and the
// uplink follows once the acknowledgment would have come and gone.
assert_eq!(exchange.wake_up.link.preamble_symbols(), 259);
let ack = exchange.ack.expect("an uplink is acknowledged");
assert_eq!(exchange.uplink_start_us, ack.start_us + ack.airtime_us + 50_000);
// With nothing in the acknowledgment window, the uplink goes out anyway.
assert_eq!(device.no_wor_ack(ack.start_us + ack.airtime_us)?, WorNext::Uplink);Sourcepub const fn relay_sync(&self) -> RelaySync
pub const fn relay_sync(&self) -> RelaySync
Returns what the device knows about when its relay listens, TS011-1.0.1 section 3.9.
§Returns
The synchronization state.
Sourcepub const fn relay_status(&self) -> Option<RelayStatus>
pub const fn relay_status(&self) -> Option<RelayStatus>
Returns what the relay’s last acknowledgment said about itself.
§Returns
Its scan period, crystal, switching time, forwarding data rate and whether it
forwards, or None before one has arrived.
Sourcepub const fn wor_counter(&self) -> u32
pub const fn wor_counter(&self) -> u32
Returns the wake-on-radio frame counter, which every frame raises and a join accept resets, TS011-1.0.1 section 5.3.2.
§Returns
The counter the next frame will use.
Sourcepub fn heard_wor_ack(
&mut self,
frame: &[u8],
) -> Result<RelayStatus, DeviceError>
pub fn heard_wor_ack( &mut self, frame: &[u8], ) -> Result<RelayStatus, DeviceError>
Reads the acknowledgment a relay answered the last wake-on-radio frame with.
The device is now synchronized: it knows when the relay scans, so its next frames carry only as much preamble as the two crystals could drift, and it holds its payloads to what the relay forwards.
§Arguments
frame- the bytes the radio received in the acknowledgment window.
§Returns
What the relay said about itself.
§Errors
Returns DeviceError::NothingPending with no wake-on-radio frame waiting on an
answer, DeviceError::NotJoined before the device has a session, and
DeviceError::Frame for an acknowledgment that does not decode or verify.
Sourcepub fn no_wor_ack(&mut self, now_us: u64) -> Result<WorNext, DeviceError>
pub fn no_wor_ack(&mut self, now_us: u64) -> Result<WorNext, DeviceError>
Says what to do once the acknowledgment window closed with nothing in it.
The uplink goes out anyway unless the network’s BackOff asks for more attempts
first. After WOR_ATTEMPTS_WO_ACK frames without an answer the device gives up
what it knows: first when the relay scans, then the relay altogether.
§Arguments
now_us- the time, in microseconds.
§Returns
Whether to send the uplink or wake the relay again.
§Errors
Returns DeviceError::NothingPending with no wake-on-radio frame waiting on an
answer, and whatever building another frame returns.
Source§impl<'p> EndDevice<'p>
impl<'p> EndDevice<'p>
Sourcepub fn save(&self, now_us: u64) -> Result<Saved, DeviceError>
pub fn save(&self, now_us: u64) -> Result<Saved, DeviceError>
Saves what the device has settled with its network, to resume after losing power.
Save between exchanges, once the last uplink is done, and again after each one: a state resumed after an uplink it does not know about would send the next frame on a counter the network has already seen.
§Arguments
now_us- the time, in microseconds, which the duty cycle waits are counted from.
§Returns
The state.
§Errors
Returns DeviceError::NotJoined before the device has a session, and
DeviceError::Busy while a transmission waits on its windows or its repeats.
§Examples
use pamoja_lora::region::Region;
use pamoja_lorawan::device::{EndDevice, Next, Saved, Settings};
use pamoja_lorawan::Session;
let settings = Settings::new(2, 14);
let session = Session::new(0x2601_2E43, [0x5A; 16], [0xA5; 16]);
let mut device = EndDevice::personalized(Region::Eu868.plan(), session, settings)?;
device.send(2, b"21.5", false, 0)?;
assert_eq!(device.nothing_heard(3_000_000)?, Next::Done);
// Before sleeping, keep the bytes somewhere that survives it.
let kept = device.save(3_000_000)?.as_bytes().to_vec();
// On waking, build the device as before and carry on from them.
let mut woken = EndDevice::personalized(Region::Eu868.plan(), session, settings)?;
woken.resume(&Saved::from_bytes(&kept).expect("intact"), 0)?;
assert_eq!(woken.fcnt_up(), 1, "the next frame does not reuse a counter");Sourcepub fn resume(&mut self, saved: &Saved, now_us: u64) -> Result<(), DeviceError>
pub fn resume(&mut self, saved: &Saved, now_us: u64) -> Result<(), DeviceError>
Resumes a state save produced.
Build the device as it was first built, with the same plan, identifiers and settings,
and resume it before anything else. On CN470-510 any of the four RP002-1.0.5 plans
will do, since the state names the join channel that chose its plan. The device’s
duty cycle waits run again from now_us for as long as they still had at the save,
which never lets it transmit sooner than it could have without the loss of power.
§Arguments
saved- the stored state.now_us- the time, in microseconds, on the clock the device now runs by.
§Errors
Returns DeviceError::State with StateError::Plan for a state saved on another
plan and StateError::Corrupt for one whose contents are out of range, and
DeviceError::Busy while a transmission waits on its windows or its repeats. The
device is left as it was on any error.
Source§impl<'p> EndDevice<'p>
impl<'p> EndDevice<'p>
Sourcepub fn new(
plan: &'p ChannelPlan<'p>,
credentials: Device,
settings: Settings,
) -> Result<EndDevice<'p>, DeviceError>
pub fn new( plan: &'p ChannelPlan<'p>, credentials: Device, settings: Settings, ) -> Result<EndDevice<'p>, DeviceError>
A device that joins over the air.
§Arguments
plan- the region’s channel plan.credentials- the device’s identifiers and root key.settings- what its radio can do.
§Returns
The device, not yet joined.
§Errors
Returns DeviceError::TooManyChannels for a plan with more default or join channels
than MAX_CHANNELS.
Sourcepub fn personalized(
plan: &'p ChannelPlan<'p>,
session: Session,
settings: Settings,
) -> Result<EndDevice<'p>, DeviceError>
pub fn personalized( plan: &'p ChannelPlan<'p>, session: Session, settings: Settings, ) -> Result<EndDevice<'p>, DeviceError>
A device activated by personalization, with its session provisioned.
Such a device never resets its frame counters, TS001-1.0.4 section 4.3.1.5, so a
device that loses power carries them over with
with_frame_counters.
§Arguments
plan- the region’s channel plan.session- the address and session keys it was provisioned with.settings- what its radio can do.
§Returns
The device, ready to send at its slowest data rate.
§Errors
Returns DeviceError::TooManyChannels for a plan with more default or join channels
than MAX_CHANNELS.
Sourcepub fn set_battery(&mut self, battery: Battery)
pub fn set_battery(&mut self, battery: Battery)
Sourcepub fn request_link_check(&mut self)
pub fn request_link_check(&mut self)
Asks the network, with the next uplink, how well it hears the device.
The answer arrives in a Delivery’s link_check.
Sourcepub fn request_device_time(&mut self)
pub fn request_device_time(&mut self)
Asks the network, with the next uplink, for the time.
The answer arrives in a Delivery’s device_time.
Sourcepub fn is_joined(&self) -> bool
pub fn is_joined(&self) -> bool
Reports whether the device is on a network.
§Returns
true once joined, or from the start for a personalized device.
Sourcepub fn data_rate(&self) -> u8
pub fn data_rate(&self) -> u8
Returns the data rate the next uplink goes out at, before any back-off step.
§Returns
The data rate.
Sourcepub fn fcnt_up(&self) -> u32
pub fn fcnt_up(&self) -> u32
Returns the next uplink frame counter.
§Returns
The counter a device stores to carry over a restart.
Sourcepub fn fcnt_down(&self) -> Option<u32>
pub fn fcnt_down(&self) -> Option<u32>
Returns the last downlink frame counter accepted.
§Returns
The counter, or None before any downlink.
Sourcepub fn transmissions(&self) -> u8
pub fn transmissions(&self) -> u8
Returns how many times each uplink goes out, as the network last set it.
§Returns
NbTrans, from 1 to 15.
Sourcepub fn frequency_span(&self) -> (u32, u32)
pub fn frequency_span(&self) -> (u32, u32)
Returns the lowest and highest frequency the device transmits or listens on.
A radio that calibrates for a band, as an SX126x does, calibrates for this one, so moving between the device’s channels and windows needs no calibration in between.
§Returns
The lower and upper frequency in hertz, across every enabled channel’s uplink and downlink, the join channels and where their accepts arrive, and the second receive window.
Sourcepub fn rx2(&self) -> (u32, u8)
pub fn rx2(&self) -> (u32, u8)
Returns the second receive window’s frequency and data rate.
§Returns
The frequency in hertz and the data rate.
Sourcepub fn receive_delay_us(&self) -> u32
pub fn receive_delay_us(&self) -> u32
Returns the delay from the end of an uplink to the first receive window.
§Returns
The delay in microseconds.
Sourcepub fn join(
&mut self,
dev_nonce: u16,
now_us: u64,
) -> Result<Transmission, DeviceError>
pub fn join( &mut self, dev_nonce: u16, now_us: u64, ) -> Result<Transmission, DeviceError>
Builds a join request.
Each attempt picks a join channel at random and a data rate from the fastest the join channels carry down to the slowest, one step per attempt and round again, which is how TS001-1.0.4 section 6.2.5 asks a device to cover every channel and rate. On a plan that joins in octet passes, US902-928 and AU915-928, each attempt instead takes the next slot of the pass at the rate its channel carries. The air it takes is held to the region’s duty cycle and to the join back-off of section 7.
The accept is due on the uplink’s frequency in a dynamic plan, on the downlink channel a fixed plan answers the join channel on, and on CN470-510 on the frequency table 49 gives the common join channel, in both windows, as Semtech’s LoRaMac-node listens.
§Arguments
dev_nonce- a nonce this device has never used with its join identifier. TS001-1.0.4 counts up from zero and keeps the count across restarts; LoRaWAN 1.0.3 draws one at random.now_us- the time, in microseconds.
§Returns
What to transmit, with the join accept windows.
§Errors
Returns DeviceError::NoCredentials for a personalized device,
DeviceError::Busy while a transmission waits on its windows, and
DeviceError::Wait when the air is not free.
Sourcepub fn send(
&mut self,
port: u8,
payload: &[u8],
confirmed: bool,
now_us: u64,
) -> Result<Transmission, DeviceError>
pub fn send( &mut self, port: u8, payload: &[u8], confirmed: bool, now_us: u64, ) -> Result<Transmission, DeviceError>
Builds an uplink carrying a payload.
The frame takes every answer the device owes the network. If they leave no room for
the payload, the answers go alone and Transmission::carries_payload says the
payload waits.
§Arguments
port- the application port, 1 to 223, or 224 for the certification test port.payload- the application payload.confirmed- whether to ask the network to acknowledge it.now_us- the time, in microseconds.
§Returns
What to transmit, with its receive windows.
§Errors
Returns DeviceError::NotJoined before joining, DeviceError::Busy while a
transmission waits on its windows or its repeats, DeviceError::Wait when the air
is not free, DeviceError::PayloadTooLong for a payload the data rate cannot carry,
and DeviceError::Frame for port 0, which belongs to MAC commands.
Sourcepub fn send_empty(&mut self, now_us: u64) -> Result<Transmission, DeviceError>
pub fn send_empty(&mut self, now_us: u64) -> Result<Transmission, DeviceError>
Sourcepub fn heard(&mut self, frame: &[u8], snr_db: i8) -> Result<Heard, DeviceError>
pub fn heard(&mut self, frame: &[u8], snr_db: i8) -> Result<Heard, DeviceError>
Reads a frame heard in one of the receive windows of the last transmission, without saying which.
A downlink may be as long as the faster of the two windows allows. When the radio
knows the window, heard_in holds the frame to that window’s
own limit, as TS001-1.0.4 section 4.1 asks.
§Arguments
frame- the bytes the radio received.snr_db- the frame’s signal-to-noise ratio, which aDevStatusAnsreports.
§Returns
The join, or the downlink read and acted on.
§Errors
As heard_in.
Sourcepub fn heard_in(
&mut self,
window: ReceiveWindow,
frame: &[u8],
snr_db: i8,
) -> Result<Heard, DeviceError>
pub fn heard_in( &mut self, window: ReceiveWindow, frame: &[u8], snr_db: i8, ) -> Result<Heard, DeviceError>
Reads a frame heard in a given receive window of the last transmission.
Under a relay the windows are three: the first two as ever, and then
ReceiveWindow::Rxr, which carries whatever the relay forwards back, TS011-1.0.1
chapter 7.
A frame that is not for this device, does not verify, or is longer than the window’s
data rate carries leaves the transmission waiting, so the second window still opens.
TS001-1.0.4 section 4.1 has a device discard a MACPayload longer than the region’s
M for the data rate it was received at. RP002-1.0.5 has the device hold a downlink
to the limit with no dwell time and no repeater, whatever its own settings, so no
frame a network may lawfully send is lost.
§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, which aDevStatusAnsreports.
§Returns
The join, or the downlink read and acted on.
§Errors
Returns DeviceError::NothingPending with no transmission waiting,
DeviceError::Foreign for another device’s frame, DeviceError::Replayed and
DeviceError::CounterGap for a counter the device will not accept,
DeviceError::Refused for a join accept with settings the region forbids, and
DeviceError::Frame for a frame that does not decode or is longer than the window
carries.
§Examples
use pamoja_lora::region::Region;
use pamoja_lorawan::device::{DeviceError, EndDevice, ReceiveWindow, Settings};
use pamoja_lorawan::{Downlink, LorawanError, Session};
let session = Session::new(0x2601_1BDA, [0x2B; 16], [0x99; 16]);
let settings = Settings::new(2, 14);
let mut device = EndDevice::personalized(Region::Eu868.plan(), session, settings)?;
// A personalized European device starts at DR0, and the second window listens at DR0
// too, where a MACPayload holds no more than 59 bytes.
device.send(1, b"hi", false, 0)?;
let long = session.encode_downlink(&Downlink::new(0, 1, &[0; 60]))?;
assert_eq!(
device.heard_in(ReceiveWindow::Rx2, long.as_bytes(), 5),
Err(DeviceError::Frame(LorawanError::PayloadTooLong)),
);
let short = session.encode_downlink(&Downlink::new(0, 1, b"ok"))?;
assert!(device.heard_in(ReceiveWindow::Rx2, short.as_bytes(), 5).is_ok());Sourcepub fn nothing_heard(&mut self, now_us: u64) -> Result<Next, DeviceError>
pub fn nothing_heard(&mut self, now_us: u64) -> Result<Next, DeviceError>
Says what comes next once both receive windows closed with nothing for the device.
§Arguments
now_us- the time the second window closed, in microseconds.
§Returns
Whether to repeat the frame, join again, or move on.
§Errors
Returns DeviceError::NothingPending with no transmission waiting on its windows.
Sourcepub fn repeat(&mut self, now_us: u64) -> Result<Transmission, DeviceError>
pub fn repeat(&mut self, now_us: u64) -> Result<Transmission, DeviceError>
Sends the last uplink again, the same frame on a channel chosen afresh.
TS001-1.0.4 section 4.3.1.3 has a device hop between repetitions as usual and keep the frame counter, so the frame is byte for byte the one that went out before.
§Arguments
now_us- the time, in microseconds.
§Returns
What to transmit, with its receive windows.
§Errors
Returns DeviceError::NothingPending with nothing due to repeat, and
DeviceError::Wait before the retransmission timeout or while the air is not free.