pamoja.lora
Idiomatic LoRa link-budget facade.
LoRa buys kilometers of range on license-free bands at tiny power, and the price is time: a transmission occupies the channel for a duration the radio settings fix, and the regional rules cap how much of the time a node may transmit. This is the arithmetic that keeps a node inside that budget, with no radio involved.
It also works out how far a link reaches: the EIRP an antenna and cable leave, the free-space loss of a path, the first Fresnel zone, the sensitivity of a receiver, and the power 47 CFR 15.247 allows through an antenna, in decibels resolved to a hundredth.
1"""Idiomatic LoRa link-budget facade. 2 3LoRa buys kilometers of range on license-free bands at tiny power, and the price 4is time: a transmission occupies the channel for a duration the radio settings 5fix, and the regional rules cap how much of the time a node may transmit. This is 6the arithmetic that keeps a node inside that budget, with no radio involved. 7 8It also works out how far a link reaches: the EIRP an antenna and cable leave, the 9free-space loss of a path, the first Fresnel zone, the sensitivity of a receiver, 10and the power 47 CFR 15.247 allows through an antenna, in decibels resolved to a 11hundredth. 12""" 13 14from __future__ import annotations 15 16from pamoja._native import ( 17 ChannelPlan, 18 ChannelPlanBuilder, 19 LoraBeacon, 20 LoraChannelBlock, 21 LinkBudget, 22 LoraDataRate, 23 LoraJoinPlan, 24 LoraJoinPlanPlace, 25 LoraLink, 26 LoraMaskControl, 27 LoraMaxPayload, 28 LoraPlanInfo, 29 LoraPlanRules, 30 LoraRelayChannel, 31 LoraSubBand, 32 lora_demodulator_snr_db, 33 lora_fcc_max_conducted_dbm, 34 lora_free_space_loss_db, 35 lora_fresnel_radius_mm, 36 lora_noise_floor_dbm, 37) 38 39__all__ = [ 40 "CN470_PLANS", 41 "ChannelPlan", 42 "ChannelPlanBuilder", 43 "GATEWAY_NOISE_FIGURE_DB", 44 "LinkBudget", 45 "LoraBeacon", 46 "LoraChannelBlock", 47 "LoraDataRate", 48 "LoraJoinPlan", 49 "LoraJoinPlanPlace", 50 "LoraLink", 51 "LoraMaskControl", 52 "LoraMaxPayload", 53 "LoraPlanInfo", 54 "LoraPlanRules", 55 "LoraRelayChannel", 56 "LoraSubBand", 57 "RADIO_NOISE_FIGURE_DB", 58 "REGIONS", 59 "demodulator_snr_db", 60 "fcc_max_conducted_dbm", 61 "free_space_loss_db", 62 "fresnel_radius_mm", 63 "link", 64 "messages_per_hour", 65 "messages_per_hour_at", 66 "cn470_plan", 67 "noise_floor_dbm", 68 "plan_for", 69] 70 71#: The bands with a published channel plan. 72REGIONS = ( 73 "EU868", 74 "US915", 75 "EU433", 76 "AU915", 77 "CN470", 78 "AS923", 79 "KR920", 80 "IN865", 81 "RU864", 82) 83 84#: The CN470-510 channel plans, as :func:`cn470_plan` takes them: the four 85#: RP002-1.0.5 plans for 20 MHz and 26 MHz antennas, then the 96-channel plan of the 86#: LoRaWAN 1.0.3 Regional Parameters revision A. 87CN470_PLANS = ( 88 "antenna_20mhz_a", 89 "antenna_20mhz_b", 90 "antenna_26mhz_a", 91 "antenna_26mhz_b", 92 "channels_96", 93) 94 95#: A typical noise figure for a Semtech sub-GHz LoRa radio, in dB. Semtech AN1200.22 96#: takes it as the receiver behind the SX1272 and SX1276 datasheet sensitivities. 97RADIO_NOISE_FIGURE_DB = 6.0 98 99#: A typical noise figure for a LoRaWAN gateway receiver, in dB, from Semtech TN1300.05. 100GATEWAY_NOISE_FIGURE_DB = 3.0 101 102 103def link( 104 spreading_factor: int, 105 bandwidth_hz: int, 106 coding_rate_denominator: int = 5, 107 preamble_symbols: int = 8, 108 explicit_header: bool = True, 109 crc: bool = True, 110) -> LoraLink: 111 """Describe a LoRa link, clamping every value to its LoRa range. 112 113 The defaults are coding rate 4/5, an eight-symbol preamble, an explicit 114 header, and CRC on, which is a typical uplink. 115 116 :param spreading_factor: The spreading factor, clamped to 5 (fastest) to 12 117 (longest range). 118 :param bandwidth_hz: The channel bandwidth in hertz, such as ``125_000``. 119 :param coding_rate_denominator: The coding-rate denominator, clamped to 5 to 8. 120 :param preamble_symbols: The preamble length in symbols. 121 :param explicit_header: Whether the frame carries an explicit header. 122 :param crc: Whether the frame carries a CRC. 123 :returns: The link, which answers for its own airtime and off time. 124 """ 125 return LoraLink( 126 spreading_factor, 127 bandwidth_hz, 128 coding_rate_denominator, 129 preamble_symbols, 130 explicit_header, 131 crc, 132 ) 133 134 135def messages_per_hour( 136 settings: LoraLink, payload_len: int, duty_cycle_permille: int 137) -> int: 138 """Return how many transmissions of a payload fit in an hour under a limit. 139 140 The airtime plus the silence it forces is what one transmission really costs, 141 so this is the message budget a deployment plans against. 142 143 :param settings: The link settings. 144 :param payload_len: The payload length in bytes. 145 :param duty_cycle_permille: The limit in parts per thousand, so ``10`` is 1%. 146 :returns: The number of whole transmissions per hour, or ``0`` when the limit 147 forbids transmitting. 148 """ 149 return settings.messages_per_hour(payload_len, duty_cycle_permille) 150 151 152def plan_for(region: str) -> ChannelPlan: 153 """Return the published channel plan for a region. 154 155 A channel plan is what a regulator and the LoRa Alliance publish about one 156 band: which data rates exist, what each carries, how much of the time a node 157 may hold a frequency, and where it listens for a downlink. The plan reports 158 those facts and costs a transmission out against them; it never refuses one, 159 because a deployment may hold licensed spectrum or be working under emergency 160 provisions and only the operator knows which. 161 162 :param region: The band to describe, such as ``EU868``. See :data:`REGIONS`. 163 :returns: The plan, which answers every question about that band. 164 :raises ValueError: If no published region goes by that name. 165 166 >>> plan = plan_for("EU868") 167 >>> plan.name 168 'EU863-870' 169 >>> plan.link_settings(0).spreading_factor 170 12 171 >>> plan.duty_cycle_permille(868_100_000) 172 10 173 """ 174 return ChannelPlan.for_region(region) 175 176 177def cn470_plan(plan: str) -> ChannelPlan: 178 """Return one of the CN470-510 channel plans. 179 180 A device joining over the air uses the twenty common join channels the four 181 RP002-1.0.5 plans share, and moves to the plan its join channel names. 182 183 :param plan: The plan, one of :data:`CN470_PLANS`. 184 :returns: The plan, which also carries the runs of join channels that select 185 each of the others. 186 :raises ValueError: If no CN470 plan goes by that name. 187 188 >>> plan = cn470_plan("antenna_26mhz_a") 189 >>> place = plan.join_plan_for_channel(9) 190 >>> plan.join_plans()[place.index].plan 191 'antenna_20mhz_b' 192 >>> place.accept_hz 193 499900000 194 """ 195 return ChannelPlan.for_cn470(plan) 196 197 198def messages_per_hour_at( 199 plan: ChannelPlan, data_rate: int, payload_len: int, frequency_hz: int 200) -> int | None: 201 """Return how many transmissions fit in an hour at a data rate the plan defines. 202 203 This is the budget question a deployment actually asks: not what the radio 204 can do, but how often it may speak on this band at this setting. The duty 205 cycle of the frequency it transmits on decides the answer. 206 207 :param plan: The channel plan to read. 208 :param data_rate: The uplink data-rate number. 209 :param payload_len: The payload length in bytes. 210 :param frequency_hz: The frequency the node transmits on. 211 :returns: The number of whole transmissions per hour, or ``None`` when the 212 plan does not describe that data rate or frequency. 213 214 >>> plan = plan_for("EU868") 215 >>> messages_per_hour_at(plan, 5, 20, 868_100_000) > 0 216 True 217 """ 218 settings = plan.link_settings(data_rate) 219 permille = plan.duty_cycle_permille(frequency_hz) 220 if settings is None or permille is None: 221 return None 222 return messages_per_hour(settings, payload_len, permille) 223 224 225def noise_floor_dbm(bandwidth_hz: int) -> float: 226 """Return the thermal noise power in a channel, in dBm. 227 228 This is -174 dBm/Hz plus ``10 log10`` of the bandwidth, as Semtech AN1200.22 229 derives it. LoRa demodulates below this floor by the processing gain its 230 spreading factor buys, and :func:`demodulator_snr_db` gives how far. 231 232 :param bandwidth_hz: The channel bandwidth in hertz; ``0`` counts as one hertz. 233 :returns: The noise power in dBm, to a hundredth of a decibel. 234 235 >>> noise_floor_dbm(125_000) 236 -123.03 237 """ 238 return lora_noise_floor_dbm(bandwidth_hz) 239 240 241def demodulator_snr_db(spreading_factor: int) -> float: 242 """Return the signal-to-noise ratio the LoRa demodulator needs, in dB. 243 244 These are the typical figures of Table 6-1 in the Semtech SX1261/2 datasheet, 245 from -2.5 dB at SF5 to -20 dB at SF12. A negative ratio is a signal received 246 below the noise. 247 248 :param spreading_factor: The spreading factor, clamped to 5 to 12. 249 :returns: The required SNR in dB. 250 251 >>> demodulator_snr_db(12) 252 -20.0 253 """ 254 return lora_demodulator_snr_db(spreading_factor) 255 256 257def free_space_loss_db(distance_m: int, frequency_hz: int) -> float: 258 """Return the free-space basic transmission loss between isotropic antennas, in dB. 259 260 This is Recommendation ITU-R P.525-5, equation (5): the loss with nothing in the 261 way, which grows by 6.02 dB each time the distance doubles. Terrain that enters 262 the first Fresnel ellipsoid adds to it; see :func:`fresnel_radius_mm`. 263 264 :param distance_m: The distance between the antennas in meters. 265 :param frequency_hz: The carrier frequency in hertz. 266 :returns: The loss in dB, to a hundredth of a decibel. 267 268 >>> free_space_loss_db(5_000, 868_100_000) 269 105.2 270 """ 271 return lora_free_space_loss_db(distance_m, frequency_hz) 272 273 274def fresnel_radius_mm(near_m: int, far_m: int, frequency_hz: int) -> int: 275 """Return the radius of the first Fresnel ellipsoid at a point on a path, in millimeters. 276 277 This is Recommendation ITU-R P.526-16, equation (2). The radius is widest halfway 278 along the path, and the diffraction zone starts where the clearance falls to 60% 279 of it. 280 281 :param near_m: The distance from one antenna to the point, in meters. 282 :param far_m: The distance from the point to the other antenna, in meters. 283 :param frequency_hz: The carrier frequency in hertz. 284 :returns: The radius in millimeters, or ``0`` for a path of no length. 285 286 >>> fresnel_radius_mm(2_500, 2_500, 868_100_000) 287 20777 288 """ 289 return lora_fresnel_radius_mm(near_m, far_m, frequency_hz) 290 291 292def fcc_max_conducted_dbm( 293 antenna_gain_dbi: float, hopping_channels: int | None = None 294) -> float | None: 295 """Return the most conducted power 47 CFR 15.247 allows through an antenna, in dBm. 296 297 The limit applies to a 902-928 MHz transmitter: 1 W for digital modulation and 298 for hopping on at least 50 channels, and 0.25 W for hopping on 25 to 49, less 299 every decibel the antenna gain exceeds 6 dBi. 300 301 :param antenna_gain_dbi: The directional gain of the transmitting antenna. 302 :param hopping_channels: The number of hopping channels, or ``None`` for a 303 system using digital modulation. 304 :returns: The limit in dBm, or ``None`` for hopping on fewer than 25 channels, 305 which paragraph (b)(2) sets no limit for. 306 307 >>> fcc_max_conducted_dbm(9.0) 308 27.0 309 >>> fcc_max_conducted_dbm(2.15, hopping_channels=24) is None 310 True 311 """ 312 return lora_fcc_max_conducted_dbm(antenna_gain_dbi, hopping_channels)
A regional channel plan, published or private.
Returns the published plan for a region.
Raises ValueError if no published region goes by that name.
Returns one of the CN470-510 channel plans.
RP002-1.0.5 divides the band into four plans, for 20 MHz and 26 MHz antennas,
each with a type A and B; channels_96 is the plan of the LoRaWAN 1.0.3
Regional Parameters revision A. Raises ValueError for any other name.
Returns the short code of every published region, as for_region takes.
These are the codes a deployment is configured with. The band name the
specification uses, such as EU863-870, is the plan's name.
Returns the data rate a number selects, or None past the end of the
plan's table.
A number the region reserves is a data rate of kind reserved, which is
different from a number the plan never defines.
Returns the radio settings an uplink data rate selects, ready to hand to
airtime_us, or None if the number is reserved or not carried by LoRa.
Returns what a data rate may carry in one frame, or None where the plan
publishes no limit for it.
Returns the share of time a transmitter may hold a frequency, in parts per
thousand, or None if the frequency falls in no sub-band this plan
describes.
This reports the limit; it does not impose it. Pair it with
min_off_time_us to turn the limit into the silence a frame costs.
Returns the power ceiling that applies at a frequency, in dBm EIRP, falling back to the plan's default where no sub-band says otherwise.
Returns the radiated power a transmit-power index selects, in dBm, or
None if the index is past the highest the plan defines.
Returns the downlink data rate the first receive window listens at, or
None if the uplink data rate or offset is outside what the plan defines.
Returns where the second receive window listens, as a frequency in hertz and a data rate.
Returns the next lower data rate to fall back to during adaptive back-off,
or None at the slowest rate the plan has.
A device that has lost the network steps down this chain, trading airtime for range until it is heard again.
Returns the center frequency of one of the plan's default channels, or
None past the last one the plan starts a device with.
Returns the plan's channel blocks: the join set, the default set, or the
numbered downlink channels a fixed plan answers the first receive window on.
Returns where the first receive window listens after an uplink on a channel.
On a plan with no numbered downlink channels that is the uplink's own frequency; otherwise it is the downlink channel the uplink channel maps to.
Returns the frequency of a numbered downlink channel, or None past the last.
Returns the runs of join channels that select a plan, which only the published CN470-510 plans carry.
Returns the run of join channels a join channel belongs to, and where the accept
and the second receive window fall for it, or None if no run holds it.
A channel plan under construction.
Tables are indexed by position, so entries are added in data-rate order and a
number the plan does not use is added as a reserved data rate. What a region
would share between directions is filled in by build.
Adds the next data rate in a direction.
A plan that never adds a downlink rate uses its uplink table in both directions, which is what every region but the 900 MHz plans does.
Adds the next entry of one payload table.
Pass no payload for a data rate that carries nothing. A downlink table left empty mirrors the matching uplink one.
Adds a run of evenly spaced channels to the join, default, or downlink set.
Sets whether the network creates channels (dynamic) or only switches numbered
ones (fixed), and for a dynamic plan the numbering a type 1 channel list is read
against, mhz800 or mhz900.
Sets what each ChMaskCntl value does, all eight in value order.
Sets the order a device tries the join channels in, random or octet_passes.
Sets what the transmit power indexes count down from, eirp or conducted, and
for a conducted ceiling the antenna gain it allows for.
Adds a sub-band and the transmit limits inside it.
A deployment on licensed spectrum gives its sub-band a duty cycle of
1000, which reports as unrestricted.
Adds the next default relay channel, whose position is the index a relay configuration names. A data rate that is not one of the plan's LoRa downlink rates is refused when the plan is built.
Adds the RX1 downlink data rates for the next uplink data rate.
Every row must be as wide as the plan's highest RX1 offset allows.
Adds the next entry of the adaptive back-off chain.
Pass no data rate at the slowest, which has nothing below it. A chain left empty steps down one data rate at a time.
Sets the receive windows.
max_rx1_offset fixes how wide every RX1 row must be.
The gains and losses of a LoRa link, from the transmitting radio to the receiving one.
Every value is in decibels, resolved to the hundredth of a decibel the Rust crate holds.
The equivalent isotropically radiated power, in dBm.
The transmit power plus the transmitting antenna gain, less the transmitting cable loss. This is the figure regional power ceilings limit.
The power that reaches the receiving radio across a path, in dBm.
The weakest signal the receiver can demodulate on a link, in dBm.
How far above the sensitivity a signal arrives across a path, in dB.
Negative where the path loses more than the link survives.
The most transmit power that keeps the EIRP at or under a ceiling, in dBm.
A higher-gain antenna leaves less power for the radio.
The loss in the cable and connectors between the receiving antenna and its radio, in dB.
The Class B beacon settings of a plan.
A run of evenly spaced channels.
One data rate: what a number on the wire means for the radio.
Only the attributes belonging to kind are set; the rest are None.
A run of join channels that puts a device on a plan.
The run of join channels one join channel belongs to.
The radio settings of a LoRa link.
Whether the link uses low data rate optimization.
It is on when a symbol lasts longer than 16 ms, which is SF11 and SF12 at 125 kHz and SF12 at 250 kHz. The airtime assumes it, so a radio set up from these settings must turn it on too.
The time on air of a payload, in microseconds.
This is the channel occupancy a transmission costs, which sets both the duty-cycle budget and most of the energy the transmission spends.
The minimum silence after a transmission to honor a duty-cycle limit.
The limit is in parts per thousand, so 10 is 1%. A limit of 0 forbids
transmitting at all, which comes back as None, and one of 1000 or more, the whole
of the time, owes no silence.
What one ChMaskCntl value of a LinkADRReq does.
Only the attributes belonging to kind are set; the rest are None.
The eight low bits switch banks of eight with their 500 kHz channel, and the ninth the 500 kHz channels past them.
What one data rate may carry in a single frame.
The scalar facts of a plan, read in one call.
How a plan defines and uses its channels.
For a dynamic plan, the numbering a type 1 channel list is read against:
mhz800, mhz900, or None.
A default channel of a LoRaWAN relay, TS011-1.0.1.
A slice of a band with its own transmit limits.
242def demodulator_snr_db(spreading_factor: int) -> float: 243 """Return the signal-to-noise ratio the LoRa demodulator needs, in dB. 244 245 These are the typical figures of Table 6-1 in the Semtech SX1261/2 datasheet, 246 from -2.5 dB at SF5 to -20 dB at SF12. A negative ratio is a signal received 247 below the noise. 248 249 :param spreading_factor: The spreading factor, clamped to 5 to 12. 250 :returns: The required SNR in dB. 251 252 >>> demodulator_snr_db(12) 253 -20.0 254 """ 255 return lora_demodulator_snr_db(spreading_factor)
Return the signal-to-noise ratio the LoRa demodulator needs, in dB.
These are the typical figures of Table 6-1 in the Semtech SX1261/2 datasheet, from -2.5 dB at SF5 to -20 dB at SF12. A negative ratio is a signal received below the noise.
Parameters
- spreading_factor: The spreading factor, clamped to 5 to 12. :returns: The required SNR in dB.
>>> demodulator_snr_db(12)
-20.0
293def fcc_max_conducted_dbm( 294 antenna_gain_dbi: float, hopping_channels: int | None = None 295) -> float | None: 296 """Return the most conducted power 47 CFR 15.247 allows through an antenna, in dBm. 297 298 The limit applies to a 902-928 MHz transmitter: 1 W for digital modulation and 299 for hopping on at least 50 channels, and 0.25 W for hopping on 25 to 49, less 300 every decibel the antenna gain exceeds 6 dBi. 301 302 :param antenna_gain_dbi: The directional gain of the transmitting antenna. 303 :param hopping_channels: The number of hopping channels, or ``None`` for a 304 system using digital modulation. 305 :returns: The limit in dBm, or ``None`` for hopping on fewer than 25 channels, 306 which paragraph (b)(2) sets no limit for. 307 308 >>> fcc_max_conducted_dbm(9.0) 309 27.0 310 >>> fcc_max_conducted_dbm(2.15, hopping_channels=24) is None 311 True 312 """ 313 return lora_fcc_max_conducted_dbm(antenna_gain_dbi, hopping_channels)
Return the most conducted power 47 CFR 15.247 allows through an antenna, in dBm.
The limit applies to a 902-928 MHz transmitter: 1 W for digital modulation and for hopping on at least 50 channels, and 0.25 W for hopping on 25 to 49, less every decibel the antenna gain exceeds 6 dBi.
Parameters
- antenna_gain_dbi: The directional gain of the transmitting antenna.
- hopping_channels: The number of hopping channels, or
Nonefor a system using digital modulation. :returns: The limit in dBm, orNonefor hopping on fewer than 25 channels, which paragraph (b)(2) sets no limit for.
>>> fcc_max_conducted_dbm(9.0)
27.0
>>> fcc_max_conducted_dbm(2.15, hopping_channels=24) is None
True
258def free_space_loss_db(distance_m: int, frequency_hz: int) -> float: 259 """Return the free-space basic transmission loss between isotropic antennas, in dB. 260 261 This is Recommendation ITU-R P.525-5, equation (5): the loss with nothing in the 262 way, which grows by 6.02 dB each time the distance doubles. Terrain that enters 263 the first Fresnel ellipsoid adds to it; see :func:`fresnel_radius_mm`. 264 265 :param distance_m: The distance between the antennas in meters. 266 :param frequency_hz: The carrier frequency in hertz. 267 :returns: The loss in dB, to a hundredth of a decibel. 268 269 >>> free_space_loss_db(5_000, 868_100_000) 270 105.2 271 """ 272 return lora_free_space_loss_db(distance_m, frequency_hz)
Return the free-space basic transmission loss between isotropic antennas, in dB.
This is Recommendation ITU-R P.525-5, equation (5): the loss with nothing in the
way, which grows by 6.02 dB each time the distance doubles. Terrain that enters
the first Fresnel ellipsoid adds to it; see fresnel_radius_mm().
Parameters
- distance_m: The distance between the antennas in meters.
- frequency_hz: The carrier frequency in hertz. :returns: The loss in dB, to a hundredth of a decibel.
>>> free_space_loss_db(5_000, 868_100_000)
105.2
275def fresnel_radius_mm(near_m: int, far_m: int, frequency_hz: int) -> int: 276 """Return the radius of the first Fresnel ellipsoid at a point on a path, in millimeters. 277 278 This is Recommendation ITU-R P.526-16, equation (2). The radius is widest halfway 279 along the path, and the diffraction zone starts where the clearance falls to 60% 280 of it. 281 282 :param near_m: The distance from one antenna to the point, in meters. 283 :param far_m: The distance from the point to the other antenna, in meters. 284 :param frequency_hz: The carrier frequency in hertz. 285 :returns: The radius in millimeters, or ``0`` for a path of no length. 286 287 >>> fresnel_radius_mm(2_500, 2_500, 868_100_000) 288 20777 289 """ 290 return lora_fresnel_radius_mm(near_m, far_m, frequency_hz)
Return the radius of the first Fresnel ellipsoid at a point on a path, in millimeters.
This is Recommendation ITU-R P.526-16, equation (2). The radius is widest halfway along the path, and the diffraction zone starts where the clearance falls to 60% of it.
Parameters
- near_m: The distance from one antenna to the point, in meters.
- far_m: The distance from the point to the other antenna, in meters.
- frequency_hz: The carrier frequency in hertz.
:returns: The radius in millimeters, or
0for a path of no length.
>>> fresnel_radius_mm(2_500, 2_500, 868_100_000)
20777
104def link( 105 spreading_factor: int, 106 bandwidth_hz: int, 107 coding_rate_denominator: int = 5, 108 preamble_symbols: int = 8, 109 explicit_header: bool = True, 110 crc: bool = True, 111) -> LoraLink: 112 """Describe a LoRa link, clamping every value to its LoRa range. 113 114 The defaults are coding rate 4/5, an eight-symbol preamble, an explicit 115 header, and CRC on, which is a typical uplink. 116 117 :param spreading_factor: The spreading factor, clamped to 5 (fastest) to 12 118 (longest range). 119 :param bandwidth_hz: The channel bandwidth in hertz, such as ``125_000``. 120 :param coding_rate_denominator: The coding-rate denominator, clamped to 5 to 8. 121 :param preamble_symbols: The preamble length in symbols. 122 :param explicit_header: Whether the frame carries an explicit header. 123 :param crc: Whether the frame carries a CRC. 124 :returns: The link, which answers for its own airtime and off time. 125 """ 126 return LoraLink( 127 spreading_factor, 128 bandwidth_hz, 129 coding_rate_denominator, 130 preamble_symbols, 131 explicit_header, 132 crc, 133 )
Describe a LoRa link, clamping every value to its LoRa range.
The defaults are coding rate 4/5, an eight-symbol preamble, an explicit header, and CRC on, which is a typical uplink.
Parameters
- spreading_factor: The spreading factor, clamped to 5 (fastest) to 12 (longest range).
- bandwidth_hz: The channel bandwidth in hertz, such as
125_000. - coding_rate_denominator: The coding-rate denominator, clamped to 5 to 8.
- preamble_symbols: The preamble length in symbols.
- explicit_header: Whether the frame carries an explicit header.
- crc: Whether the frame carries a CRC. :returns: The link, which answers for its own airtime and off time.
136def messages_per_hour( 137 settings: LoraLink, payload_len: int, duty_cycle_permille: int 138) -> int: 139 """Return how many transmissions of a payload fit in an hour under a limit. 140 141 The airtime plus the silence it forces is what one transmission really costs, 142 so this is the message budget a deployment plans against. 143 144 :param settings: The link settings. 145 :param payload_len: The payload length in bytes. 146 :param duty_cycle_permille: The limit in parts per thousand, so ``10`` is 1%. 147 :returns: The number of whole transmissions per hour, or ``0`` when the limit 148 forbids transmitting. 149 """ 150 return settings.messages_per_hour(payload_len, duty_cycle_permille)
Return how many transmissions of a payload fit in an hour under a limit.
The airtime plus the silence it forces is what one transmission really costs, so this is the message budget a deployment plans against.
Parameters
- settings: The link settings.
- payload_len: The payload length in bytes.
- duty_cycle_permille: The limit in parts per thousand, so
10is 1%. :returns: The number of whole transmissions per hour, or0when the limit forbids transmitting.
199def messages_per_hour_at( 200 plan: ChannelPlan, data_rate: int, payload_len: int, frequency_hz: int 201) -> int | None: 202 """Return how many transmissions fit in an hour at a data rate the plan defines. 203 204 This is the budget question a deployment actually asks: not what the radio 205 can do, but how often it may speak on this band at this setting. The duty 206 cycle of the frequency it transmits on decides the answer. 207 208 :param plan: The channel plan to read. 209 :param data_rate: The uplink data-rate number. 210 :param payload_len: The payload length in bytes. 211 :param frequency_hz: The frequency the node transmits on. 212 :returns: The number of whole transmissions per hour, or ``None`` when the 213 plan does not describe that data rate or frequency. 214 215 >>> plan = plan_for("EU868") 216 >>> messages_per_hour_at(plan, 5, 20, 868_100_000) > 0 217 True 218 """ 219 settings = plan.link_settings(data_rate) 220 permille = plan.duty_cycle_permille(frequency_hz) 221 if settings is None or permille is None: 222 return None 223 return messages_per_hour(settings, payload_len, permille)
Return how many transmissions fit in an hour at a data rate the plan defines.
This is the budget question a deployment actually asks: not what the radio can do, but how often it may speak on this band at this setting. The duty cycle of the frequency it transmits on decides the answer.
Parameters
- plan: The channel plan to read.
- data_rate: The uplink data-rate number.
- payload_len: The payload length in bytes.
- frequency_hz: The frequency the node transmits on.
:returns: The number of whole transmissions per hour, or
Nonewhen the plan does not describe that data rate or frequency.
>>> plan = plan_for("EU868")
>>> messages_per_hour_at(plan, 5, 20, 868_100_000) > 0
True
178def cn470_plan(plan: str) -> ChannelPlan: 179 """Return one of the CN470-510 channel plans. 180 181 A device joining over the air uses the twenty common join channels the four 182 RP002-1.0.5 plans share, and moves to the plan its join channel names. 183 184 :param plan: The plan, one of :data:`CN470_PLANS`. 185 :returns: The plan, which also carries the runs of join channels that select 186 each of the others. 187 :raises ValueError: If no CN470 plan goes by that name. 188 189 >>> plan = cn470_plan("antenna_26mhz_a") 190 >>> place = plan.join_plan_for_channel(9) 191 >>> plan.join_plans()[place.index].plan 192 'antenna_20mhz_b' 193 >>> place.accept_hz 194 499900000 195 """ 196 return ChannelPlan.for_cn470(plan)
Return one of the CN470-510 channel plans.
A device joining over the air uses the twenty common join channels the four RP002-1.0.5 plans share, and moves to the plan its join channel names.
Parameters
- plan: The plan, one of
CN470_PLANS. :returns: The plan, which also carries the runs of join channels that select each of the others.
Raises
- ValueError: If no CN470 plan goes by that name.
>>> plan = cn470_plan("antenna_26mhz_a")
>>> place = plan.join_plan_for_channel(9)
>>> plan.join_plans()[place.index].plan
'antenna_20mhz_b'
>>> place.accept_hz
499900000
226def noise_floor_dbm(bandwidth_hz: int) -> float: 227 """Return the thermal noise power in a channel, in dBm. 228 229 This is -174 dBm/Hz plus ``10 log10`` of the bandwidth, as Semtech AN1200.22 230 derives it. LoRa demodulates below this floor by the processing gain its 231 spreading factor buys, and :func:`demodulator_snr_db` gives how far. 232 233 :param bandwidth_hz: The channel bandwidth in hertz; ``0`` counts as one hertz. 234 :returns: The noise power in dBm, to a hundredth of a decibel. 235 236 >>> noise_floor_dbm(125_000) 237 -123.03 238 """ 239 return lora_noise_floor_dbm(bandwidth_hz)
Return the thermal noise power in a channel, in dBm.
This is -174 dBm/Hz plus 10 log10 of the bandwidth, as Semtech AN1200.22
derives it. LoRa demodulates below this floor by the processing gain its
spreading factor buys, and demodulator_snr_db() gives how far.
Parameters
- bandwidth_hz: The channel bandwidth in hertz;
0counts as one hertz. :returns: The noise power in dBm, to a hundredth of a decibel.
>>> noise_floor_dbm(125_000)
-123.03
153def plan_for(region: str) -> ChannelPlan: 154 """Return the published channel plan for a region. 155 156 A channel plan is what a regulator and the LoRa Alliance publish about one 157 band: which data rates exist, what each carries, how much of the time a node 158 may hold a frequency, and where it listens for a downlink. The plan reports 159 those facts and costs a transmission out against them; it never refuses one, 160 because a deployment may hold licensed spectrum or be working under emergency 161 provisions and only the operator knows which. 162 163 :param region: The band to describe, such as ``EU868``. See :data:`REGIONS`. 164 :returns: The plan, which answers every question about that band. 165 :raises ValueError: If no published region goes by that name. 166 167 >>> plan = plan_for("EU868") 168 >>> plan.name 169 'EU863-870' 170 >>> plan.link_settings(0).spreading_factor 171 12 172 >>> plan.duty_cycle_permille(868_100_000) 173 10 174 """ 175 return ChannelPlan.for_region(region)
Return the published channel plan for a region.
A channel plan is what a regulator and the LoRa Alliance publish about one band: which data rates exist, what each carries, how much of the time a node may hold a frequency, and where it listens for a downlink. The plan reports those facts and costs a transmission out against them; it never refuses one, because a deployment may hold licensed spectrum or be working under emergency provisions and only the operator knows which.
Parameters
- region: The band to describe, such as
EU868. SeeREGIONS. :returns: The plan, which answers every question about that band.
Raises
- ValueError: If no published region goes by that name.
>>> plan = plan_for("EU868")
>>> plan.name
'EU863-870'
>>> plan.link_settings(0).spreading_factor
12
>>> plan.duty_cycle_permille(868_100_000)
10