pamoja.gateway

Idiomatic LoRaWAN gateway facade.

A gateway hears packets from every node in range and hands them to a network server, which hands back the packets to transmit. The protocol between them is a plain exchange of UDP datagrams, and this speaks it from both sides: build a datagram, encode() it onto a socket of your own, and parse() whatever arrives.

Frequencies are in hertz, payloads are bytes rather than base64, and a reception time is a count of microseconds, so nothing has to be formatted by hand.

Example::

from pamoja.gateway import Packet, PacketKind, Rxpk, encode
from pamoja.lora import link

heard = Rxpk(868_100_000, b"hello", link=link(7, 125_000), rssi_dbm=-35.0, snr_db=5.1)
datagram = encode(Packet(PacketKind.PUSH_DATA, 0x1234, gateway="b827ebfffe010203",
                         packets=[heard]))
  1"""Idiomatic LoRaWAN gateway facade.
  2
  3A gateway hears packets from every node in range and hands them to a network server, which
  4hands back the packets to transmit. The protocol between them is a plain exchange of UDP
  5datagrams, and this speaks it from both sides: build a datagram, :func:`encode` it onto a
  6socket of your own, and :func:`parse` whatever arrives.
  7
  8Frequencies are in hertz, payloads are ``bytes`` rather than base64, and a reception time is a
  9count of microseconds, so nothing has to be formatted by hand.
 10
 11Example::
 12
 13    from pamoja.gateway import Packet, PacketKind, Rxpk, encode
 14    from pamoja.lora import link
 15
 16    heard = Rxpk(868_100_000, b"hello", link=link(7, 125_000), rssi_dbm=-35.0, snr_db=5.1)
 17    datagram = encode(Packet(PacketKind.PUSH_DATA, 0x1234, gateway="b827ebfffe010203",
 18                             packets=[heard]))
 19"""
 20
 21from __future__ import annotations
 22
 23import enum
 24
 25from pamoja._native import GatewayNetwork as Network
 26from pamoja._native import GatewayNetworkEvent as NetworkEvent
 27from pamoja._native import GatewayPacket as Packet
 28from pamoja._native import GatewayRxpk as Rxpk
 29from pamoja._native import GatewaySlot as Slot
 30from pamoja._native import GatewayStat as Stat
 31from pamoja._native import GatewayTxpk as Txpk
 32from pamoja._native import gateway_acknowledgment as acknowledgment
 33from pamoja._native import gateway_encode as encode
 34from pamoja._native import gateway_parse as parse
 35
 36__all__ = [
 37    "Crc",
 38    "DEFAULT_PORT",
 39    "Network",
 40    "NetworkEvent",
 41    "Packet",
 42    "PacketKind",
 43    "Rxpk",
 44    "Slot",
 45    "Stat",
 46    "TxStatus",
 47    "Txpk",
 48    "acknowledgment",
 49    "encode",
 50    "parse",
 51]
 52
 53#: The port a packet forwarder sends to by convention, which the protocol itself does not fix.
 54DEFAULT_PORT = 1700
 55
 56
 57class PacketKind(str, enum.Enum):
 58    """Which kind of datagram."""
 59
 60    #: The gateway forwarding what it heard.
 61    PUSH_DATA = "PushData"
 62    #: The server acknowledging a PUSH_DATA.
 63    PUSH_ACK = "PushAck"
 64    #: The gateway holding its route open through any address translation in front of it.
 65    PULL_DATA = "PullData"
 66    #: The server sending a packet to transmit.
 67    PULL_RESP = "PullResp"
 68    #: The server acknowledging a PULL_DATA.
 69    PULL_ACK = "PullAck"
 70    #: The gateway reporting what became of a PULL_RESP.
 71    TX_ACK = "TxAck"
 72
 73
 74class Crc(str, enum.Enum):
 75    """What the CRC of a received packet said."""
 76
 77    #: The CRC checked.
 78    OK = "Ok"
 79    #: The CRC failed.
 80    FAILED = "Failed"
 81    #: The packet carried no CRC.
 82    ABSENT = "Absent"
 83
 84
 85class TxStatus(str, enum.Enum):
 86    """What became of a downlink the server asked for, as the protocol names it."""
 87
 88    #: It was scheduled.
 89    NONE = "NONE"
 90    #: It arrived too late to schedule.
 91    TOO_LATE = "TOO_LATE"
 92    #: Its timestamp is too far ahead.
 93    TOO_EARLY = "TOO_EARLY"
 94    #: Another packet was already scheduled then.
 95    COLLISION_PACKET = "COLLISION_PACKET"
 96    #: A beacon was already scheduled then.
 97    COLLISION_BEACON = "COLLISION_BEACON"
 98    #: The radio chain cannot reach that frequency.
 99    TX_FREQ = "TX_FREQ"
100    #: The gateway cannot transmit at that power.
101    TX_POWER = "TX_POWER"
102    #: A GPS timestamp was asked for while the GPS is unlocked.
103    GPS_UNLOCKED = "GPS_UNLOCKED"
class Crc(builtins.str, enum.Enum):
75class Crc(str, enum.Enum):
76    """What the CRC of a received packet said."""
77
78    #: The CRC checked.
79    OK = "Ok"
80    #: The CRC failed.
81    FAILED = "Failed"
82    #: The packet carried no CRC.
83    ABSENT = "Absent"

What the CRC of a received packet said.

OK = <Crc.OK: 'Ok'>
FAILED = <Crc.FAILED: 'Failed'>
ABSENT = <Crc.ABSENT: 'Absent'>
DEFAULT_PORT = 1700
Network = <class 'builtins.GatewayNetwork'>
NetworkEvent = <class 'builtins.GatewayNetworkEvent'>
Packet = <class 'builtins.GatewayPacket'>
class PacketKind(builtins.str, enum.Enum):
58class PacketKind(str, enum.Enum):
59    """Which kind of datagram."""
60
61    #: The gateway forwarding what it heard.
62    PUSH_DATA = "PushData"
63    #: The server acknowledging a PUSH_DATA.
64    PUSH_ACK = "PushAck"
65    #: The gateway holding its route open through any address translation in front of it.
66    PULL_DATA = "PullData"
67    #: The server sending a packet to transmit.
68    PULL_RESP = "PullResp"
69    #: The server acknowledging a PULL_DATA.
70    PULL_ACK = "PullAck"
71    #: The gateway reporting what became of a PULL_RESP.
72    TX_ACK = "TxAck"

Which kind of datagram.

PUSH_DATA = <PacketKind.PUSH_DATA: 'PushData'>
PUSH_ACK = <PacketKind.PUSH_ACK: 'PushAck'>
PULL_DATA = <PacketKind.PULL_DATA: 'PullData'>
PULL_RESP = <PacketKind.PULL_RESP: 'PullResp'>
PULL_ACK = <PacketKind.PULL_ACK: 'PullAck'>
TX_ACK = <PacketKind.TX_ACK: 'TxAck'>
Rxpk = <class 'builtins.GatewayRxpk'>
Slot = <class 'builtins.GatewaySlot'>
Stat = <class 'builtins.GatewayStat'>
class TxStatus(builtins.str, enum.Enum):
 86class TxStatus(str, enum.Enum):
 87    """What became of a downlink the server asked for, as the protocol names it."""
 88
 89    #: It was scheduled.
 90    NONE = "NONE"
 91    #: It arrived too late to schedule.
 92    TOO_LATE = "TOO_LATE"
 93    #: Its timestamp is too far ahead.
 94    TOO_EARLY = "TOO_EARLY"
 95    #: Another packet was already scheduled then.
 96    COLLISION_PACKET = "COLLISION_PACKET"
 97    #: A beacon was already scheduled then.
 98    COLLISION_BEACON = "COLLISION_BEACON"
 99    #: The radio chain cannot reach that frequency.
100    TX_FREQ = "TX_FREQ"
101    #: The gateway cannot transmit at that power.
102    TX_POWER = "TX_POWER"
103    #: A GPS timestamp was asked for while the GPS is unlocked.
104    GPS_UNLOCKED = "GPS_UNLOCKED"

What became of a downlink the server asked for, as the protocol names it.

NONE = <TxStatus.NONE: 'NONE'>
TOO_LATE = <TxStatus.TOO_LATE: 'TOO_LATE'>
TOO_EARLY = <TxStatus.TOO_EARLY: 'TOO_EARLY'>
COLLISION_PACKET = <TxStatus.COLLISION_PACKET: 'COLLISION_PACKET'>
COLLISION_BEACON = <TxStatus.COLLISION_BEACON: 'COLLISION_BEACON'>
TX_FREQ = <TxStatus.TX_FREQ: 'TX_FREQ'>
TX_POWER = <TxStatus.TX_POWER: 'TX_POWER'>
GPS_UNLOCKED = <TxStatus.GPS_UNLOCKED: 'GPS_UNLOCKED'>
Txpk = <class 'builtins.GatewayTxpk'>
def acknowledgment(packet):

Returns the acknowledgment a server owes a datagram, or None for one that needs none.

def encode(packet):

Writes a datagram to send over a socket.

def parse(datagram):

Reads a datagram that arrived.