Skip to main content

EndDevice

Struct EndDevice 

Source
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<'_>

Source

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.

Source

pub const fn relaying(&self) -> bool

Reports whether the next uplink goes through a relay.

§Returns

true while relay mode is on.

Source

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);
Source

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.

Source

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.

Source

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.

Source

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.

Source

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>

Source

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");
Source

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>

Source

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.

Source

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.

Source

pub fn with_frame_counters(self, up: u32, down: Option<u32>) -> EndDevice<'p>

Carries frame counters over from before a restart.

§Arguments
  • up - the next uplink counter to use.
  • down - the last downlink counter accepted, if any was.
§Returns

The device.

Source

pub fn set_battery(&mut self, battery: Battery)

Sets what the device reports its battery as, when a network asks.

§Arguments
  • battery - the level.

Asks the network, with the next uplink, how well it hears the device.

The answer arrives in a Delivery’s link_check.

Source

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.

Source

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.

Source

pub fn dev_addr(&self) -> Option<u32>

Returns the address the device is on the network by.

§Returns

The address, or None before joining.

Source

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.

Source

pub fn fcnt_up(&self) -> u32

Returns the next uplink frame counter.

§Returns

The counter a device stores to carry over a restart.

Source

pub fn fcnt_down(&self) -> Option<u32>

Returns the last downlink frame counter accepted.

§Returns

The counter, or None before any downlink.

Source

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.

Source

pub fn channels(&self) -> impl Iterator<Item = (usize, Channel)> + '_

Returns the channels the device may send on.

§Returns

Each enabled channel with its index.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn send_empty(&mut self, now_us: u64) -> Result<Transmission, DeviceError>

Builds an uplink with no payload.

It carries the answers the device owes, an acknowledgment of a confirmed downlink, or an ADR acknowledgment request, whichever are due.

§Arguments
  • now_us - the time, in microseconds.
§Returns

What to transmit, with its receive windows.

§Errors

As send.

Source

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 a DevStatusAns reports.
§Returns

The join, or the downlink read and acted on.

§Errors

As heard_in.

Source

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 a DevStatusAns reports.
§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());
Source

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.

Source

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.

Auto Trait Implementations§

§

impl<'p> Freeze for EndDevice<'p>

§

impl<'p> RefUnwindSafe for EndDevice<'p>

§

impl<'p> Send for EndDevice<'p>

§

impl<'p> Sync for EndDevice<'p>

§

impl<'p> Unpin for EndDevice<'p>

§

impl<'p> UnsafeUnpin for EndDevice<'p>

§

impl<'p> UnwindSafe for EndDevice<'p>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.