pamoja.lorawan.relay

A LoRaWAN relay, TS011-1.0.1: what an end device and a relay say to each other.

A relay sleeps, waking every scan period to look for radio activity. An end device out of a gateway's reach first sends a wake-on-radio (WOR) frame whose preamble spans that sleep and which says where its LoRaWAN uplink follows. The relay may acknowledge with its own timing, so the next preamble can be short, then forwards the uplink to the network on port LA_FPORT_RELAY over its own session.

>>> from pamoja.lorawan import session
>>> from pamoja.lorawan import relay
>>> device = session(0x26011BDA, bytes([0x2B]) * 16, bytes([0x99]) * 16)
>>> keys = device.wor_keys()
>>> wor, uplink = relay.Carrier(865_100_000, 3), relay.Carrier(868_100_000, 5)
>>> frame = relay.wor_uplink(keys, device.dev_addr, 1, uplink, wor)
>>> relay.open_wor(frame, keys, 1, wor) == uplink
True
  1"""A LoRaWAN relay, TS011-1.0.1: what an end device and a relay say to each other.
  2
  3A relay sleeps, waking every scan period to look for radio activity. An end device out of a
  4gateway's reach first sends a wake-on-radio (WOR) frame whose preamble spans that sleep and
  5which says where its LoRaWAN uplink follows. The relay may acknowledge with its own timing, so
  6the next preamble can be short, then forwards the uplink to the network on port
  7:data:`LA_FPORT_RELAY` over its own session.
  8
  9>>> from pamoja.lorawan import session
 10>>> from pamoja.lorawan import relay
 11>>> device = session(0x26011BDA, bytes([0x2B]) * 16, bytes([0x99]) * 16)
 12>>> keys = device.wor_keys()
 13>>> wor, uplink = relay.Carrier(865_100_000, 3), relay.Carrier(868_100_000, 5)
 14>>> frame = relay.wor_uplink(keys, device.dev_addr, 1, uplink, wor)
 15>>> relay.open_wor(frame, keys, 1, wor) == uplink
 16True
 17"""
 18
 19from __future__ import annotations
 20
 21import enum
 22
 23from pamoja._native import (
 24    LORAWAN_FORWARD_OVERHEAD,
 25    LORAWAN_LA_FPORT_RELAY,
 26    LORAWAN_MIN_WOR_PREAMBLE_SYMBOLS,
 27    LORAWAN_RELAY_FWD_DELAY_US,
 28    LORAWAN_RXR_DELAY_US,
 29    LORAWAN_TRUSTED_ED_NUMBER,
 30    LORAWAN_WOR_ACK_DELAY_US,
 31    LORAWAN_WOR_ATTEMPTS_WO_ACK,
 32    LORAWAN_WOR_DATA_DELAY_US,
 33    LoraRelayChannel,
 34    LorawanCarrier,
 35    LorawanForwardedUplink,
 36    LorawanStateSync,
 37    LorawanSynchronization,
 38    LorawanUplinkMetadata,
 39    LorawanWor,
 40    LorawanWorKeys,
 41    LorawanWorSlot,
 42    lorawan_relay_forward_encode,
 43    lorawan_relay_forward_parse,
 44    lorawan_relay_next_wor,
 45    lorawan_relay_root_wor_s_key,
 46    lorawan_relay_second_channel,
 47    lorawan_relay_synchronization,
 48    lorawan_relay_t_offset_ms,
 49    lorawan_relay_unsynchronized_preamble,
 50    lorawan_relay_wor_ack,
 51    lorawan_relay_wor_ack_open,
 52    lorawan_relay_wor_join_request,
 53    lorawan_relay_wor_keys,
 54    lorawan_relay_wor_open,
 55    lorawan_relay_wor_parse,
 56    lorawan_relay_wor_uplink,
 57)
 58
 59__all__ = [
 60    "FORWARD_OVERHEAD",
 61    "LA_FPORT_RELAY",
 62    "MIN_WOR_PREAMBLE_SYMBOLS",
 63    "RELAY_FWD_DELAY_US",
 64    "RXR_DELAY_US",
 65    "TRUSTED_ED_NUMBER",
 66    "WOR_ACK_DELAY_US",
 67    "WOR_ATTEMPTS_WO_ACK",
 68    "WOR_DATA_DELAY_US",
 69    "CadPeriodicity",
 70    "CadToRx",
 71    "Carrier",
 72    "ForwardedUplink",
 73    "RelayForward",
 74    "StateSync",
 75    "Synchronization",
 76    "UplinkMetadata",
 77    "Wor",
 78    "WorChannel",
 79    "WorKeys",
 80    "WorKind",
 81    "WorSlot",
 82    "XtalAccuracy",
 83    "encode_forward",
 84    "next_wor",
 85    "open_wor",
 86    "open_wor_ack",
 87    "parse_forward",
 88    "parse_wor",
 89    "root_wor_s_key",
 90    "second_channel",
 91    "synchronization",
 92    "t_offset_ms",
 93    "unsynchronized_preamble",
 94    "wor_ack",
 95    "wor_join_request",
 96    "wor_keys",
 97    "wor_uplink",
 98]
 99
100#: The port every message between a relay and its network uses, TS011-1.0.1 section 9.
101LA_FPORT_RELAY = LORAWAN_LA_FPORT_RELAY
102#: How many end devices a relay verifies wake-on-radio frames for.
103TRUSTED_ED_NUMBER = LORAWAN_TRUSTED_ED_NUMBER
104#: How many WOR frames go without an acknowledgment before the uplink goes anyway.
105WOR_ATTEMPTS_WO_ACK = LORAWAN_WOR_ATTEMPTS_WO_ACK
106#: The gap between a WOR frame, or its acknowledgment, and the LoRaWAN frame after it.
107WOR_DATA_DELAY_US = LORAWAN_WOR_DATA_DELAY_US
108#: The gap between a WOR frame and its acknowledgment.
109WOR_ACK_DELAY_US = LORAWAN_WOR_ACK_DELAY_US
110#: The gap between a relay hearing an uplink and forwarding it.
111RELAY_FWD_DELAY_US = LORAWAN_RELAY_FWD_DELAY_US
112#: How long after an uplink an end device's RXR window opens at the latest.
113RXR_DELAY_US = LORAWAN_RXR_DELAY_US
114#: The bytes a forwarded uplink adds in front of the end device's frame.
115FORWARD_OVERHEAD = LORAWAN_FORWARD_OVERHEAD
116#: The shortest WOR preamble, in symbols.
117MIN_WOR_PREAMBLE_SYMBOLS = LORAWAN_MIN_WOR_PREAMBLE_SYMBOLS
118
119#: Where a frame goes and how fast.
120Carrier = LorawanCarrier
121#: The integrity and encryption keys of one end device's WOR frames.
122WorKeys = LorawanWorKeys
123#: A WOR frame, as a relay reads it.
124Wor = LorawanWor
125#: What a relay tells an end device about itself in a WOR ACK.
126StateSync = LorawanStateSync
127#: What a relay heard of an uplink it forwards.
128UplinkMetadata = LorawanUplinkMetadata
129#: An end device's uplink as a relay forwards it on port 226.
130ForwardedUplink = LorawanForwardedUplink
131#: What an end device knows of a relay's scans once a WOR ACK has arrived.
132Synchronization = LorawanSynchronization
133#: When a synchronized end device's next WOR frame goes out.
134WorSlot = LorawanWorSlot
135
136
137class WorKind(str, enum.Enum):
138    """Which WOR frame a relay heard."""
139
140    #: Ahead of a join request, which nothing protects.
141    JOIN_REQUEST = "join_request"
142    #: Ahead of a Class A uplink, sealed with the device's WOR keys.
143    UPLINK = "uplink"
144
145
146class CadPeriodicity(str, enum.Enum):
147    """How often a relay scans a channel for a WOR preamble, TS011-1.0.1 table 18."""
148
149    #: Once a second, the default.
150    MS1000 = "ms1000"
151    #: Every 500 milliseconds.
152    MS500 = "ms500"
153    #: Every 250 milliseconds.
154    MS250 = "ms250"
155    #: Every 100 milliseconds.
156    MS100 = "ms100"
157    #: Every 50 milliseconds.
158    MS50 = "ms50"
159    #: Every 20 milliseconds.
160    MS20 = "ms20"
161
162
163class CadToRx(str, enum.Enum):
164    """How many symbols a relay takes from detecting activity to receiving, table 15."""
165
166    #: Two symbols.
167    SYMBOLS2 = "symbols2"
168    #: Four symbols.
169    SYMBOLS4 = "symbols4"
170    #: Six symbols.
171    SYMBOLS6 = "symbols6"
172    #: Eight symbols, which a device assumes before it has heard from a relay.
173    SYMBOLS8 = "symbols8"
174
175
176class XtalAccuracy(str, enum.Enum):
177    """How accurate a relay's crystal is, table 17."""
178
179    #: Better than 10 parts per million.
180    PPM10 = "ppm10"
181    #: Better than 20.
182    PPM20 = "ppm20"
183    #: Better than 30.
184    PPM30 = "ppm30"
185    #: Better than 40, which a device assumes before it has heard from a relay.
186    PPM40 = "ppm40"
187
188
189class RelayForward(str, enum.Enum):
190    """Whether a relay will forward the uplink after a WOR frame, table 16."""
191
192    #: It has room to.
193    AVAILABLE = "available"
194    #: A forwarding limit is reached; try again in 30 minutes.
195    RETRY_IN_30_MINUTES = "retry_in_30_minutes"
196    #: A forwarding limit is reached; try again in 60 minutes.
197    RETRY_IN_60_MINUTES = "retry_in_60_minutes"
198    #: Forwarding is off.
199    DISABLED = "disabled"
200
201
202class WorChannel(str, enum.Enum):
203    """Which of a relay's channels a WOR frame arrived on, table 28."""
204
205    #: The default channel.
206    DEFAULT = "default"
207    #: The second channel a network configured.
208    SECOND = "second"
209
210
211def _name(value: str | enum.Enum) -> str:
212    return value.value if isinstance(value, enum.Enum) else str(value)
213
214
215def root_wor_s_key(network_key: bytes) -> bytes:
216    """Derive an end device's root relay session key from its network key, section 4.4.
217
218    :param network_key: The 16-byte network session key.
219    :returns: The 16-byte root key.
220    :raises PamojaError: If the key is not 16 bytes.
221    """
222    return lorawan_relay_root_wor_s_key(bytes(network_key))
223
224
225def wor_keys(root_key: bytes, dev_addr: int) -> LorawanWorKeys:
226    """Derive an end device's WOR keys from its root relay session key, section 4.5.
227
228    :param root_key: The 16-byte root key.
229    :param dev_addr: The device's address.
230    :returns: The integrity and encryption keys.
231    :raises PamojaError: If the key is not 16 bytes.
232    """
233    return lorawan_relay_wor_keys(bytes(root_key), dev_addr)
234
235
236def wor_join_request(uplink: LorawanCarrier) -> bytes:
237    """Build the WOR frame ahead of a join request, section 5.3.1.
238
239    :param uplink: Where and how fast the join request follows.
240    :returns: The five-byte frame.
241    :raises PamojaError: For a data rate past 15 or a frequency the field cannot carry.
242    """
243    return lorawan_relay_wor_join_request(uplink)
244
245
246def wor_uplink(
247    keys: LorawanWorKeys, dev_addr: int, wfcnt: int, uplink: LorawanCarrier, wor: LorawanCarrier
248) -> bytes:
249    """Build the WOR frame ahead of a Class A uplink, section 5.3.2.
250
251    :param keys: The device's WOR keys.
252    :param dev_addr: Its address.
253    :param wfcnt: The WOR frame counter, raised for every WOR frame.
254    :param uplink: Where and how fast the uplink follows.
255    :param wor: The carrier this WOR frame goes out on.
256    :returns: The fifteen-byte frame.
257    :raises PamojaError: For a carrier the fields cannot carry.
258    """
259    return lorawan_relay_wor_uplink(keys, dev_addr, wfcnt, uplink, wor)
260
261
262def parse_wor(frame: bytes) -> LorawanWor:
263    """Read a WOR frame, leaving an uplink's carrier sealed.
264
265    :param frame: The bytes a relay received.
266    :returns: The frame's kind, and a join request's carrier or an uplink's address and
267        counter.
268    :raises PamojaError: For a reserved or proprietary type, or a length that is not the
269        type's.
270    """
271    return lorawan_relay_wor_parse(bytes(frame))
272
273
274def open_wor(frame: bytes, keys: LorawanWorKeys, wfcnt: int, wor: LorawanCarrier) -> LorawanCarrier:
275    """Check a WOR frame ahead of a Class A uplink and read where the uplink follows.
276
277    :param frame: The frame a relay received.
278    :param keys: The keys of the device it names.
279    :param wfcnt: The full 32-bit counter the relay takes it to carry.
280    :param wor: The carrier it arrived on.
281    :returns: Where and how fast the uplink follows.
282    :raises PamojaError: When the frame is not an uplink WOR, or its integrity code does not
283        verify.
284    """
285    return lorawan_relay_wor_open(bytes(frame), keys, wfcnt, wor)
286
287
288def wor_ack(
289    keys: LorawanWorKeys,
290    dev_addr: int,
291    wfcnt: int,
292    ack: LorawanCarrier,
293    uplink: LorawanCarrier,
294    state: LorawanStateSync,
295) -> bytes:
296    """Build a relay's WOR ACK, section 6.2.
297
298    :param keys: The end device's WOR keys.
299    :param dev_addr: Its address.
300    :param wfcnt: The counter of the acknowledged WOR frame.
301    :param ack: The carrier the acknowledgment goes out on.
302    :param uplink: The carrier the WOR frame named for the uplink.
303    :param state: What the relay tells the device.
304    :returns: The seven-byte acknowledgment.
305    :raises PamojaError: For a state or carrier the fields cannot carry.
306    """
307    return lorawan_relay_wor_ack(keys, dev_addr, wfcnt, ack, uplink, state)
308
309
310def open_wor_ack(
311    frame: bytes,
312    keys: LorawanWorKeys,
313    dev_addr: int,
314    wfcnt: int,
315    ack: LorawanCarrier,
316    uplink: LorawanCarrier,
317) -> LorawanStateSync:
318    """Check and read a WOR ACK, section 6.2.
319
320    :param frame: The acknowledgment an end device received.
321    :param keys: The device's WOR keys.
322    :param dev_addr: Its address.
323    :param wfcnt: The counter of the WOR frame it sent.
324    :param ack: The carrier the acknowledgment arrived on.
325    :param uplink: The carrier the WOR frame named.
326    :returns: What the relay said about itself.
327    :raises PamojaError: When the integrity code does not verify, or the periodicity is
328        reserved.
329    """
330    return lorawan_relay_wor_ack_open(bytes(frame), keys, dev_addr, wfcnt, ack, uplink)
331
332
333def state_sync(
334    cad_to_rx: str | CadToRx,
335    forward: str | RelayForward,
336    relay_data_rate: int,
337    xtal_accuracy: str | XtalAccuracy,
338    cad_periodicity: str | CadPeriodicity,
339    t_offset_ms: int,
340) -> LorawanStateSync:
341    """Describe what a relay tells an end device, taking the enums or their names.
342
343    :param cad_to_rx: How long the relay takes to start receiving.
344    :param forward: Whether it forwards.
345    :param relay_data_rate: The data rate it forwards at.
346    :param xtal_accuracy: How accurate its crystal is.
347    :param cad_periodicity: How often it scans.
348    :param t_offset_ms: Milliseconds from the scan to the end of the WOR preamble.
349    :returns: The state.
350    :raises PamojaError: For a name that is not one of the enum's.
351    """
352    return LorawanStateSync(
353        _name(cad_to_rx),
354        _name(forward),
355        relay_data_rate,
356        _name(xtal_accuracy),
357        _name(cad_periodicity),
358        t_offset_ms,
359    )
360
361
362def encode_forward(
363    metadata: LorawanUplinkMetadata, frequency_hz: int, phy_payload: bytes
364) -> bytes:
365    """Write an uplink a relay forwards on port 226, section 9.1.
366
367    A strength or ratio past what its field carries goes out as the closest value.
368
369    :param metadata: What the relay heard of it.
370    :param frequency_hz: The frequency it arrived on.
371    :param phy_payload: The end device's frame.
372    :returns: The relay uplink's payload.
373    :raises PamojaError: For a data rate or frequency the fields cannot carry.
374    """
375    return lorawan_relay_forward_encode(metadata, frequency_hz, bytes(phy_payload))
376
377
378def parse_forward(payload: bytes) -> LorawanForwardedUplink:
379    """Read an uplink a relay forwarded, section 9.1.
380
381    :param payload: The relay uplink's payload on port 226.
382    :returns: The metadata, the frequency, and the end device's frame.
383    :raises PamojaError: For a payload too short or a reserved WOR channel.
384    """
385    return lorawan_relay_forward_parse(bytes(payload))
386
387
388def unsynchronized_preamble(
389    cad_periodicity: str | CadPeriodicity, symbol_us: int, cad_to_rx: str | CadToRx
390) -> int:
391    """The WOR preamble of an end device that does not know when the relay scans, section 5.2.
392
393    :param cad_periodicity: How often the relay scans.
394    :param symbol_us: The symbol time of the WOR frame's data rate, in microseconds.
395    :param cad_to_rx: The relay's time to start receiving.
396    :returns: The preamble length in symbols.
397
398    >>> unsynchronized_preamble(CadPeriodicity.MS500, 8_192, CadToRx.SYMBOLS4)
399    72
400    """
401    return lorawan_relay_unsynchronized_preamble(_name(cad_periodicity), symbol_us, _name(cad_to_rx))
402
403
404def t_offset_ms(scan_start_us: int, preamble_end_us: int) -> int | None:
405    """The offset a relay reports in a WOR ACK, appendix 1.
406
407    :param scan_start_us: When the scan that detected the frame started.
408    :param preamble_end_us: When the frame's preamble ended: when it finished arriving,
409        less the airtime of its sync word and payload.
410    :returns: The offset in milliseconds, or ``None`` when the preamble ended before the
411        scan or more than eleven bits of milliseconds after it.
412    """
413    return lorawan_relay_t_offset_ms(scan_start_us, preamble_end_us)
414
415
416def synchronization(
417    wor_start_us: int, preamble_symbols: int, symbol_us: int, state: LorawanStateSync
418) -> LorawanSynchronization:
419    """Work out when a relay scanned from the WOR ACK that answered a frame, appendix 1.
420
421    :param wor_start_us: When the acknowledged WOR frame started going out.
422    :param preamble_symbols: Its preamble length.
423    :param symbol_us: Its symbol time.
424    :param state: What the acknowledgment said.
425    :returns: The synchronization.
426    """
427    return lorawan_relay_synchronization(wor_start_us, preamble_symbols, symbol_us, state)
428
429
430def next_wor(
431    sync: LorawanSynchronization,
432    now_us: int,
433    device_xtal_ppm: int,
434    symbol_us: int,
435    other_channel: bool = False,
436) -> LorawanWorSlot | None:
437    """Pick the relay scan a synchronized end device aims its next WOR frame at, appendix 1.
438
439    :param sync: What the device knows of the relay.
440    :param now_us: The time.
441    :param device_xtal_ppm: The device's crystal accuracy.
442    :param symbol_us: The symbol time of the WOR frame's data rate.
443    :param other_channel: Whether the frame goes out on the relay's other channel.
444    :returns: When to send and how long a preamble, or ``None`` once the drift exceeds a
445        period.
446    """
447    return lorawan_relay_next_wor(sync, now_us, device_xtal_ppm, symbol_us, other_channel)
448
449
450def second_channel(
451    second_channel_index: int, data_rate: int, ack_offset: int, frequency_hz: int
452) -> LoraRelayChannel | None:
453    """Read the second channel a relay or end device configuration describes.
454
455    :param second_channel_index: The coded index, 1 for a second channel.
456    :param data_rate: Its data rate.
457    :param ack_offset: The coded acknowledgment offset, table 35.
458    :param frequency_hz: Its frequency.
459    :returns: The channel with its acknowledgment frequency, or ``None`` when none is named.
460    """
461    return lorawan_relay_second_channel(second_channel_index, data_rate, ack_offset, frequency_hz)
FORWARD_OVERHEAD = 6
LA_FPORT_RELAY = 226
MIN_WOR_PREAMBLE_SYMBOLS = 8
RELAY_FWD_DELAY_US = 50000
RXR_DELAY_US = 18000000
TRUSTED_ED_NUMBER = 16
WOR_ACK_DELAY_US = 50000
WOR_ATTEMPTS_WO_ACK = 8
WOR_DATA_DELAY_US = 50000
class CadPeriodicity(builtins.str, enum.Enum):
147class CadPeriodicity(str, enum.Enum):
148    """How often a relay scans a channel for a WOR preamble, TS011-1.0.1 table 18."""
149
150    #: Once a second, the default.
151    MS1000 = "ms1000"
152    #: Every 500 milliseconds.
153    MS500 = "ms500"
154    #: Every 250 milliseconds.
155    MS250 = "ms250"
156    #: Every 100 milliseconds.
157    MS100 = "ms100"
158    #: Every 50 milliseconds.
159    MS50 = "ms50"
160    #: Every 20 milliseconds.
161    MS20 = "ms20"

How often a relay scans a channel for a WOR preamble, TS011-1.0.1 table 18.

MS1000 = <CadPeriodicity.MS1000: 'ms1000'>
MS500 = <CadPeriodicity.MS500: 'ms500'>
MS250 = <CadPeriodicity.MS250: 'ms250'>
MS100 = <CadPeriodicity.MS100: 'ms100'>
MS50 = <CadPeriodicity.MS50: 'ms50'>
MS20 = <CadPeriodicity.MS20: 'ms20'>
class CadToRx(builtins.str, enum.Enum):
164class CadToRx(str, enum.Enum):
165    """How many symbols a relay takes from detecting activity to receiving, table 15."""
166
167    #: Two symbols.
168    SYMBOLS2 = "symbols2"
169    #: Four symbols.
170    SYMBOLS4 = "symbols4"
171    #: Six symbols.
172    SYMBOLS6 = "symbols6"
173    #: Eight symbols, which a device assumes before it has heard from a relay.
174    SYMBOLS8 = "symbols8"

How many symbols a relay takes from detecting activity to receiving, table 15.

SYMBOLS2 = <CadToRx.SYMBOLS2: 'symbols2'>
SYMBOLS4 = <CadToRx.SYMBOLS4: 'symbols4'>
SYMBOLS6 = <CadToRx.SYMBOLS6: 'symbols6'>
SYMBOLS8 = <CadToRx.SYMBOLS8: 'symbols8'>
Carrier = <class 'builtins.LorawanCarrier'>
class RelayForward(builtins.str, enum.Enum):
190class RelayForward(str, enum.Enum):
191    """Whether a relay will forward the uplink after a WOR frame, table 16."""
192
193    #: It has room to.
194    AVAILABLE = "available"
195    #: A forwarding limit is reached; try again in 30 minutes.
196    RETRY_IN_30_MINUTES = "retry_in_30_minutes"
197    #: A forwarding limit is reached; try again in 60 minutes.
198    RETRY_IN_60_MINUTES = "retry_in_60_minutes"
199    #: Forwarding is off.
200    DISABLED = "disabled"

Whether a relay will forward the uplink after a WOR frame, table 16.

AVAILABLE = <RelayForward.AVAILABLE: 'available'>
RETRY_IN_30_MINUTES = <RelayForward.RETRY_IN_30_MINUTES: 'retry_in_30_minutes'>
RETRY_IN_60_MINUTES = <RelayForward.RETRY_IN_60_MINUTES: 'retry_in_60_minutes'>
DISABLED = <RelayForward.DISABLED: 'disabled'>
StateSync = <class 'builtins.LorawanStateSync'>
Synchronization = <class 'builtins.LorawanSynchronization'>
UplinkMetadata = <class 'builtins.LorawanUplinkMetadata'>
Wor = <class 'builtins.LorawanWor'>
class WorChannel(builtins.str, enum.Enum):
203class WorChannel(str, enum.Enum):
204    """Which of a relay's channels a WOR frame arrived on, table 28."""
205
206    #: The default channel.
207    DEFAULT = "default"
208    #: The second channel a network configured.
209    SECOND = "second"

Which of a relay's channels a WOR frame arrived on, table 28.

DEFAULT = <WorChannel.DEFAULT: 'default'>
SECOND = <WorChannel.SECOND: 'second'>
WorKeys = <class 'builtins.LorawanWorKeys'>
class WorKind(builtins.str, enum.Enum):
138class WorKind(str, enum.Enum):
139    """Which WOR frame a relay heard."""
140
141    #: Ahead of a join request, which nothing protects.
142    JOIN_REQUEST = "join_request"
143    #: Ahead of a Class A uplink, sealed with the device's WOR keys.
144    UPLINK = "uplink"

Which WOR frame a relay heard.

JOIN_REQUEST = <WorKind.JOIN_REQUEST: 'join_request'>
WorSlot = <class 'builtins.LorawanWorSlot'>
class XtalAccuracy(builtins.str, enum.Enum):
177class XtalAccuracy(str, enum.Enum):
178    """How accurate a relay's crystal is, table 17."""
179
180    #: Better than 10 parts per million.
181    PPM10 = "ppm10"
182    #: Better than 20.
183    PPM20 = "ppm20"
184    #: Better than 30.
185    PPM30 = "ppm30"
186    #: Better than 40, which a device assumes before it has heard from a relay.
187    PPM40 = "ppm40"

How accurate a relay's crystal is, table 17.

PPM10 = <XtalAccuracy.PPM10: 'ppm10'>
PPM20 = <XtalAccuracy.PPM20: 'ppm20'>
PPM30 = <XtalAccuracy.PPM30: 'ppm30'>
PPM40 = <XtalAccuracy.PPM40: 'ppm40'>
def encode_forward( metadata: LorawanUplinkMetadata, frequency_hz: int, phy_payload: bytes) -> bytes:
363def encode_forward(
364    metadata: LorawanUplinkMetadata, frequency_hz: int, phy_payload: bytes
365) -> bytes:
366    """Write an uplink a relay forwards on port 226, section 9.1.
367
368    A strength or ratio past what its field carries goes out as the closest value.
369
370    :param metadata: What the relay heard of it.
371    :param frequency_hz: The frequency it arrived on.
372    :param phy_payload: The end device's frame.
373    :returns: The relay uplink's payload.
374    :raises PamojaError: For a data rate or frequency the fields cannot carry.
375    """
376    return lorawan_relay_forward_encode(metadata, frequency_hz, bytes(phy_payload))

Write an uplink a relay forwards on port 226, section 9.1.

A strength or ratio past what its field carries goes out as the closest value.

Parameters
  • metadata: What the relay heard of it.
  • frequency_hz: The frequency it arrived on.
  • phy_payload: The end device's frame. :returns: The relay uplink's payload.
Raises
  • PamojaError: For a data rate or frequency the fields cannot carry.
def next_wor( sync: LorawanSynchronization, now_us: int, device_xtal_ppm: int, symbol_us: int, other_channel: bool = False) -> LorawanWorSlot | None:
431def next_wor(
432    sync: LorawanSynchronization,
433    now_us: int,
434    device_xtal_ppm: int,
435    symbol_us: int,
436    other_channel: bool = False,
437) -> LorawanWorSlot | None:
438    """Pick the relay scan a synchronized end device aims its next WOR frame at, appendix 1.
439
440    :param sync: What the device knows of the relay.
441    :param now_us: The time.
442    :param device_xtal_ppm: The device's crystal accuracy.
443    :param symbol_us: The symbol time of the WOR frame's data rate.
444    :param other_channel: Whether the frame goes out on the relay's other channel.
445    :returns: When to send and how long a preamble, or ``None`` once the drift exceeds a
446        period.
447    """
448    return lorawan_relay_next_wor(sync, now_us, device_xtal_ppm, symbol_us, other_channel)

Pick the relay scan a synchronized end device aims its next WOR frame at, appendix 1.

Parameters
  • sync: What the device knows of the relay.
  • now_us: The time.
  • device_xtal_ppm: The device's crystal accuracy.
  • symbol_us: The symbol time of the WOR frame's data rate.
  • other_channel: Whether the frame goes out on the relay's other channel. :returns: When to send and how long a preamble, or None once the drift exceeds a period.
def open_wor( frame: bytes, keys: LorawanWorKeys, wfcnt: int, wor: LorawanCarrier) -> LorawanCarrier:
275def open_wor(frame: bytes, keys: LorawanWorKeys, wfcnt: int, wor: LorawanCarrier) -> LorawanCarrier:
276    """Check a WOR frame ahead of a Class A uplink and read where the uplink follows.
277
278    :param frame: The frame a relay received.
279    :param keys: The keys of the device it names.
280    :param wfcnt: The full 32-bit counter the relay takes it to carry.
281    :param wor: The carrier it arrived on.
282    :returns: Where and how fast the uplink follows.
283    :raises PamojaError: When the frame is not an uplink WOR, or its integrity code does not
284        verify.
285    """
286    return lorawan_relay_wor_open(bytes(frame), keys, wfcnt, wor)

Check a WOR frame ahead of a Class A uplink and read where the uplink follows.

Parameters
  • frame: The frame a relay received.
  • keys: The keys of the device it names.
  • wfcnt: The full 32-bit counter the relay takes it to carry.
  • wor: The carrier it arrived on. :returns: Where and how fast the uplink follows.
Raises
  • PamojaError: When the frame is not an uplink WOR, or its integrity code does not verify.
def open_wor_ack( frame: bytes, keys: LorawanWorKeys, dev_addr: int, wfcnt: int, ack: LorawanCarrier, uplink: LorawanCarrier) -> LorawanStateSync:
311def open_wor_ack(
312    frame: bytes,
313    keys: LorawanWorKeys,
314    dev_addr: int,
315    wfcnt: int,
316    ack: LorawanCarrier,
317    uplink: LorawanCarrier,
318) -> LorawanStateSync:
319    """Check and read a WOR ACK, section 6.2.
320
321    :param frame: The acknowledgment an end device received.
322    :param keys: The device's WOR keys.
323    :param dev_addr: Its address.
324    :param wfcnt: The counter of the WOR frame it sent.
325    :param ack: The carrier the acknowledgment arrived on.
326    :param uplink: The carrier the WOR frame named.
327    :returns: What the relay said about itself.
328    :raises PamojaError: When the integrity code does not verify, or the periodicity is
329        reserved.
330    """
331    return lorawan_relay_wor_ack_open(bytes(frame), keys, dev_addr, wfcnt, ack, uplink)

Check and read a WOR ACK, section 6.2.

Parameters
  • frame: The acknowledgment an end device received.
  • keys: The device's WOR keys.
  • dev_addr: Its address.
  • wfcnt: The counter of the WOR frame it sent.
  • ack: The carrier the acknowledgment arrived on.
  • uplink: The carrier the WOR frame named. :returns: What the relay said about itself.
Raises
  • PamojaError: When the integrity code does not verify, or the periodicity is reserved.
def parse_forward(payload: bytes) -> LorawanForwardedUplink:
379def parse_forward(payload: bytes) -> LorawanForwardedUplink:
380    """Read an uplink a relay forwarded, section 9.1.
381
382    :param payload: The relay uplink's payload on port 226.
383    :returns: The metadata, the frequency, and the end device's frame.
384    :raises PamojaError: For a payload too short or a reserved WOR channel.
385    """
386    return lorawan_relay_forward_parse(bytes(payload))

Read an uplink a relay forwarded, section 9.1.

Parameters
  • payload: The relay uplink's payload on port 226. :returns: The metadata, the frequency, and the end device's frame.
Raises
  • PamojaError: For a payload too short or a reserved WOR channel.
def parse_wor(frame: bytes) -> LorawanWor:
263def parse_wor(frame: bytes) -> LorawanWor:
264    """Read a WOR frame, leaving an uplink's carrier sealed.
265
266    :param frame: The bytes a relay received.
267    :returns: The frame's kind, and a join request's carrier or an uplink's address and
268        counter.
269    :raises PamojaError: For a reserved or proprietary type, or a length that is not the
270        type's.
271    """
272    return lorawan_relay_wor_parse(bytes(frame))

Read a WOR frame, leaving an uplink's carrier sealed.

Parameters
  • frame: The bytes a relay received. :returns: The frame's kind, and a join request's carrier or an uplink's address and counter.
Raises
  • PamojaError: For a reserved or proprietary type, or a length that is not the type's.
def root_wor_s_key(network_key: bytes) -> bytes:
216def root_wor_s_key(network_key: bytes) -> bytes:
217    """Derive an end device's root relay session key from its network key, section 4.4.
218
219    :param network_key: The 16-byte network session key.
220    :returns: The 16-byte root key.
221    :raises PamojaError: If the key is not 16 bytes.
222    """
223    return lorawan_relay_root_wor_s_key(bytes(network_key))

Derive an end device's root relay session key from its network key, section 4.4.

Parameters
  • network_key: The 16-byte network session key. :returns: The 16-byte root key.
Raises
  • PamojaError: If the key is not 16 bytes.
def second_channel( second_channel_index: int, data_rate: int, ack_offset: int, frequency_hz: int) -> LoraRelayChannel | None:
451def second_channel(
452    second_channel_index: int, data_rate: int, ack_offset: int, frequency_hz: int
453) -> LoraRelayChannel | None:
454    """Read the second channel a relay or end device configuration describes.
455
456    :param second_channel_index: The coded index, 1 for a second channel.
457    :param data_rate: Its data rate.
458    :param ack_offset: The coded acknowledgment offset, table 35.
459    :param frequency_hz: Its frequency.
460    :returns: The channel with its acknowledgment frequency, or ``None`` when none is named.
461    """
462    return lorawan_relay_second_channel(second_channel_index, data_rate, ack_offset, frequency_hz)

Read the second channel a relay or end device configuration describes.

Parameters
  • second_channel_index: The coded index, 1 for a second channel.
  • data_rate: Its data rate.
  • ack_offset: The coded acknowledgment offset, table 35.
  • frequency_hz: Its frequency. :returns: The channel with its acknowledgment frequency, or None when none is named.
def synchronization( wor_start_us: int, preamble_symbols: int, symbol_us: int, state: LorawanStateSync) -> LorawanSynchronization:
417def synchronization(
418    wor_start_us: int, preamble_symbols: int, symbol_us: int, state: LorawanStateSync
419) -> LorawanSynchronization:
420    """Work out when a relay scanned from the WOR ACK that answered a frame, appendix 1.
421
422    :param wor_start_us: When the acknowledged WOR frame started going out.
423    :param preamble_symbols: Its preamble length.
424    :param symbol_us: Its symbol time.
425    :param state: What the acknowledgment said.
426    :returns: The synchronization.
427    """
428    return lorawan_relay_synchronization(wor_start_us, preamble_symbols, symbol_us, state)

Work out when a relay scanned from the WOR ACK that answered a frame, appendix 1.

Parameters
  • wor_start_us: When the acknowledged WOR frame started going out.
  • preamble_symbols: Its preamble length.
  • symbol_us: Its symbol time.
  • state: What the acknowledgment said. :returns: The synchronization.
def t_offset_ms(scan_start_us: int, preamble_end_us: int) -> int | None:
405def t_offset_ms(scan_start_us: int, preamble_end_us: int) -> int | None:
406    """The offset a relay reports in a WOR ACK, appendix 1.
407
408    :param scan_start_us: When the scan that detected the frame started.
409    :param preamble_end_us: When the frame's preamble ended: when it finished arriving,
410        less the airtime of its sync word and payload.
411    :returns: The offset in milliseconds, or ``None`` when the preamble ended before the
412        scan or more than eleven bits of milliseconds after it.
413    """
414    return lorawan_relay_t_offset_ms(scan_start_us, preamble_end_us)

The offset a relay reports in a WOR ACK, appendix 1.

Parameters
  • scan_start_us: When the scan that detected the frame started.
  • preamble_end_us: When the frame's preamble ended: when it finished arriving, less the airtime of its sync word and payload. :returns: The offset in milliseconds, or None when the preamble ended before the scan or more than eleven bits of milliseconds after it.
def unsynchronized_preamble( cad_periodicity: str | CadPeriodicity, symbol_us: int, cad_to_rx: str | CadToRx) -> int:
389def unsynchronized_preamble(
390    cad_periodicity: str | CadPeriodicity, symbol_us: int, cad_to_rx: str | CadToRx
391) -> int:
392    """The WOR preamble of an end device that does not know when the relay scans, section 5.2.
393
394    :param cad_periodicity: How often the relay scans.
395    :param symbol_us: The symbol time of the WOR frame's data rate, in microseconds.
396    :param cad_to_rx: The relay's time to start receiving.
397    :returns: The preamble length in symbols.
398
399    >>> unsynchronized_preamble(CadPeriodicity.MS500, 8_192, CadToRx.SYMBOLS4)
400    72
401    """
402    return lorawan_relay_unsynchronized_preamble(_name(cad_periodicity), symbol_us, _name(cad_to_rx))

The WOR preamble of an end device that does not know when the relay scans, section 5.2.

Parameters
  • cad_periodicity: How often the relay scans.
  • symbol_us: The symbol time of the WOR frame's data rate, in microseconds.
  • cad_to_rx: The relay's time to start receiving. :returns: The preamble length in symbols.
>>> unsynchronized_preamble(CadPeriodicity.MS500, 8_192, CadToRx.SYMBOLS4)
72
def wor_ack( keys: LorawanWorKeys, dev_addr: int, wfcnt: int, ack: LorawanCarrier, uplink: LorawanCarrier, state: LorawanStateSync) -> bytes:
289def wor_ack(
290    keys: LorawanWorKeys,
291    dev_addr: int,
292    wfcnt: int,
293    ack: LorawanCarrier,
294    uplink: LorawanCarrier,
295    state: LorawanStateSync,
296) -> bytes:
297    """Build a relay's WOR ACK, section 6.2.
298
299    :param keys: The end device's WOR keys.
300    :param dev_addr: Its address.
301    :param wfcnt: The counter of the acknowledged WOR frame.
302    :param ack: The carrier the acknowledgment goes out on.
303    :param uplink: The carrier the WOR frame named for the uplink.
304    :param state: What the relay tells the device.
305    :returns: The seven-byte acknowledgment.
306    :raises PamojaError: For a state or carrier the fields cannot carry.
307    """
308    return lorawan_relay_wor_ack(keys, dev_addr, wfcnt, ack, uplink, state)

Build a relay's WOR ACK, section 6.2.

Parameters
  • keys: The end device's WOR keys.
  • dev_addr: Its address.
  • wfcnt: The counter of the acknowledged WOR frame.
  • ack: The carrier the acknowledgment goes out on.
  • uplink: The carrier the WOR frame named for the uplink.
  • state: What the relay tells the device. :returns: The seven-byte acknowledgment.
Raises
  • PamojaError: For a state or carrier the fields cannot carry.
def wor_join_request(uplink: LorawanCarrier) -> bytes:
237def wor_join_request(uplink: LorawanCarrier) -> bytes:
238    """Build the WOR frame ahead of a join request, section 5.3.1.
239
240    :param uplink: Where and how fast the join request follows.
241    :returns: The five-byte frame.
242    :raises PamojaError: For a data rate past 15 or a frequency the field cannot carry.
243    """
244    return lorawan_relay_wor_join_request(uplink)

Build the WOR frame ahead of a join request, section 5.3.1.

Parameters
  • uplink: Where and how fast the join request follows. :returns: The five-byte frame.
Raises
  • PamojaError: For a data rate past 15 or a frequency the field cannot carry.
def wor_keys(root_key: bytes, dev_addr: int) -> LorawanWorKeys:
226def wor_keys(root_key: bytes, dev_addr: int) -> LorawanWorKeys:
227    """Derive an end device's WOR keys from its root relay session key, section 4.5.
228
229    :param root_key: The 16-byte root key.
230    :param dev_addr: The device's address.
231    :returns: The integrity and encryption keys.
232    :raises PamojaError: If the key is not 16 bytes.
233    """
234    return lorawan_relay_wor_keys(bytes(root_key), dev_addr)

Derive an end device's WOR keys from its root relay session key, section 4.5.

Parameters
  • root_key: The 16-byte root key.
  • dev_addr: The device's address. :returns: The integrity and encryption keys.
Raises
  • PamojaError: If the key is not 16 bytes.