use crate::LinkSettings;
#[cfg(feature = "alloc")]
mod owned;
mod plans;
#[cfg(test)]
mod tests;
#[cfg(feature = "alloc")]
pub use owned::{ChannelPlanBuilder, OwnedChannelPlan, PayloadTable, PlanError};
pub use plans::Region;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Modulation {
LoRa {
spreading_factor: u8,
bandwidth_hz: u32,
},
Fsk {
bitrate_bps: u32,
},
LrFhss {
coding_rate_numerator: u8,
coding_rate_denominator: u8,
bandwidth_hz: u32,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DataRate {
pub modulation: Modulation,
pub bitrate_bps: u32,
}
impl DataRate {
pub const fn lora(spreading_factor: u8, bandwidth_hz: u32, bitrate_bps: u32) -> Self {
Self {
modulation: Modulation::LoRa {
spreading_factor,
bandwidth_hz,
},
bitrate_bps,
}
}
pub const fn fsk(bitrate_bps: u32) -> Self {
Self {
modulation: Modulation::Fsk { bitrate_bps },
bitrate_bps,
}
}
pub const fn lr_fhss(
numerator: u8,
denominator: u8,
bandwidth_hz: u32,
bitrate_bps: u32,
) -> Self {
Self {
modulation: Modulation::LrFhss {
coding_rate_numerator: numerator,
coding_rate_denominator: denominator,
bandwidth_hz,
},
bitrate_bps,
}
}
pub fn link_settings(&self) -> Option<LinkSettings> {
match self.modulation {
Modulation::LoRa {
spreading_factor,
bandwidth_hz,
} => Some(LinkSettings::new(spreading_factor, bandwidth_hz)),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MaxPayload {
pub mac_payload: u16,
pub application: u16,
}
impl MaxPayload {
pub const fn new(mac_payload: u16, application: u16) -> Self {
Self {
mac_payload,
application,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ChannelBlock {
pub start_hz: u32,
pub step_hz: u32,
pub count: u16,
pub min_data_rate: u8,
pub max_data_rate: u8,
}
impl ChannelBlock {
pub const fn new(
start_hz: u32,
step_hz: u32,
count: u16,
min_data_rate: u8,
max_data_rate: u8,
) -> Self {
Self {
start_hz,
step_hz,
count,
min_data_rate,
max_data_rate,
}
}
pub const fn frequency_hz(&self, index: u16) -> Option<u32> {
if index >= self.count {
return None;
}
Some(self.start_hz + self.step_hz * index as u32)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SubBand {
pub start_hz: u32,
pub end_hz: u32,
pub duty_cycle_permille: u32,
pub max_eirp_dbm: i8,
}
impl SubBand {
pub const fn new(
start_hz: u32,
end_hz: u32,
duty_cycle_permille: u32,
max_eirp_dbm: i8,
) -> Self {
Self {
start_hz,
end_hz,
duty_cycle_permille,
max_eirp_dbm,
}
}
pub const fn contains(&self, frequency_hz: u32) -> bool {
frequency_hz >= self.start_hz && frequency_hz <= self.end_hz
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Beacon {
pub data_rate: u8,
pub frequency_hz: u32,
pub ping_slot_frequency_hz: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct ChannelPlan<'a> {
pub name: &'a str,
pub uplink_data_rates: &'a [Option<DataRate>],
pub downlink_data_rates: &'a [Option<DataRate>],
pub max_payload_repeater: &'a [Option<MaxPayload>],
pub max_payload_direct: &'a [Option<MaxPayload>],
pub downlink_max_payload_repeater: &'a [Option<MaxPayload>],
pub downlink_max_payload_direct: &'a [Option<MaxPayload>],
pub max_payload_dwell_limited: Option<&'a [Option<MaxPayload>]>,
pub join_channels: &'a [ChannelBlock],
pub default_channels: &'a [ChannelBlock],
pub sub_bands: &'a [SubBand],
pub default_max_eirp_dbm: i8,
pub tx_power_step_db: u8,
pub max_tx_power_index: u8,
pub rx1_data_rate_offsets: &'a [&'a [u8]],
pub rx1_data_rate_offsets_dwell_limited: Option<&'a [&'a [u8]]>,
pub max_rx1_data_rate_offset: u8,
pub rx2_frequency_hz: u32,
pub rx2_data_rate: u8,
pub data_rate_backoff: &'a [Option<u8>],
pub beacon: Beacon,
pub has_dwell_time_limit: bool,
}
impl ChannelPlan<'_> {
pub fn uplink_data_rate(&self, data_rate: u8) -> Option<DataRate> {
*self.uplink_data_rates.get(usize::from(data_rate))?
}
pub fn downlink_data_rate(&self, data_rate: u8) -> Option<DataRate> {
*self.downlink_data_rates.get(usize::from(data_rate))?
}
pub fn link_settings(&self, data_rate: u8) -> Option<LinkSettings> {
self.uplink_data_rate(data_rate)?.link_settings()
}
pub fn max_payload(&self, data_rate: u8, behind_repeater: bool) -> Option<MaxPayload> {
let table = if behind_repeater {
self.max_payload_repeater
} else {
self.max_payload_direct
};
*table.get(usize::from(data_rate))?
}
pub fn downlink_max_payload(&self, data_rate: u8, behind_repeater: bool) -> Option<MaxPayload> {
let table = if behind_repeater {
self.downlink_max_payload_repeater
} else {
self.downlink_max_payload_direct
};
*table.get(usize::from(data_rate))?
}
pub fn max_payload_dwell_limited(&self, data_rate: u8) -> Option<MaxPayload> {
*self
.max_payload_dwell_limited?
.get(usize::from(data_rate))?
}
pub fn duty_cycle_permille(&self, frequency_hz: u32) -> Option<u32> {
self.sub_bands
.iter()
.find(|band| band.contains(frequency_hz))
.map(|band| band.duty_cycle_permille)
}
pub fn max_eirp_dbm(&self, frequency_hz: u32) -> i8 {
self.sub_bands
.iter()
.find(|band| band.contains(frequency_hz))
.map_or(self.default_max_eirp_dbm, |band| band.max_eirp_dbm)
}
pub fn tx_power_dbm(&self, index: u8, max_eirp_dbm: i8) -> Option<i8> {
if index > self.max_tx_power_index {
return None;
}
let step = i16::from(self.tx_power_step_db) * i16::from(index);
Some((i16::from(max_eirp_dbm) - step) as i8)
}
pub fn rx1_data_rate(&self, uplink_data_rate: u8, offset: u8) -> Option<u8> {
if offset > self.max_rx1_data_rate_offset {
return None;
}
self.rx1_data_rate_offsets
.get(usize::from(uplink_data_rate))?
.get(usize::from(offset))
.copied()
}
pub fn rx1_data_rate_dwell_limited(&self, uplink_data_rate: u8, offset: u8) -> Option<u8> {
if offset > self.max_rx1_data_rate_offset {
return None;
}
self.rx1_data_rate_offsets_dwell_limited?
.get(usize::from(uplink_data_rate))?
.get(usize::from(offset))
.copied()
}
pub fn rx2(&self) -> (u32, u8) {
(self.rx2_frequency_hz, self.rx2_data_rate)
}
pub fn next_backoff_data_rate(&self, data_rate: u8) -> Option<u8> {
*self.data_rate_backoff.get(usize::from(data_rate))?
}
pub fn channel_frequency_hz(&self, channel: u16) -> Option<u32> {
let mut remaining = channel;
for block in self.default_channels {
if remaining < block.count {
return block.frequency_hz(remaining);
}
remaining -= block.count;
}
None
}
pub fn default_channel_count(&self) -> u16 {
self.default_channels
.iter()
.map(|block| block.count)
.sum::<u16>()
}
}