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.
The Basics Station protocol is here from both sides too. A server reads a station's discovery
request with station_discovery_parse() and answers it with
station_router_accepted(); a station reports a frame it heard with station_heard();
and any other message is a StationMessage built from the fields of its kind and
written with station_encode().
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 11The Basics Station protocol is here from both sides too. A server reads a station's discovery 12request with :func:`station_discovery_parse` and answers it with 13:func:`station_router_accepted`; a station reports a frame it heard with :func:`station_heard`; 14and any other message is a :class:`StationMessage` built from the fields of its kind and 15written with :func:`station_encode`. 16 17Example:: 18 19 from pamoja.gateway import Packet, PacketKind, Rxpk, encode 20 from pamoja.lora import link 21 22 heard = Rxpk(868_100_000, b"hello", link=link(7, 125_000), rssi_dbm=-35.0, snr_db=5.1) 23 datagram = encode(Packet(PacketKind.PUSH_DATA, 0x1234, gateway="b827ebfffe010203", 24 packets=[heard])) 25""" 26 27from __future__ import annotations 28 29import enum 30 31from pamoja._native import CHIRPSTACK_UPLINK_TOPIC 32from pamoja._native import ChirpstackReception 33from pamoja._native import ChirpstackUplinkEvent 34from pamoja._native import GatewayNetwork as Network 35from pamoja._native import GatewayNetworkEvent as NetworkEvent 36from pamoja._native import GatewayNotice as Notice 37from pamoja._native import GatewayPacket as Packet 38from pamoja._native import GatewayRelayed as Relayed 39from pamoja._native import GatewayRxpk as Rxpk 40from pamoja._native import GatewaySlot as Slot 41from pamoja._native import GatewayStat as Stat 42from pamoja._native import GatewayTxpk as Txpk 43from pamoja._native import GatewayStationBroadcast as StationBroadcast 44from pamoja._native import GatewayStationDataRate as StationDataRate 45from pamoja._native import GatewayStationJoinRange as StationJoinRange 46from pamoja._native import GatewayStationLevels as StationLevels 47from pamoja._native import GatewayStationMessage as StationMessage 48from pamoja._native import GatewayStationRouter as StationRouter 49from pamoja._native import GatewayStationWindow as StationWindow 50from pamoja._native import GatewayStationXtime as StationXtime 51from pamoja._native import gateway_acknowledgment as acknowledgment 52from pamoja._native import gateway_encode as encode 53from pamoja._native import gateway_parse as parse 54from pamoja._native import chirpstack_uplink_topic 55from pamoja._native import station_discovery 56from pamoja._native import station_discovery_parse 57from pamoja._native import station_encode 58from pamoja._native import station_eui_of 59from pamoja._native import station_heard 60from pamoja._native import station_id6 61from pamoja._native import station_parse 62from pamoja._native import station_router_accepted 63from pamoja._native import station_router_parse 64from pamoja._native import station_router_refused 65from pamoja._native import station_xtime 66from pamoja._native import station_xtime_parts 67 68__all__ = [ 69 "CHIRPSTACK_UPLINK_TOPIC", 70 "ChirpstackReception", 71 "ChirpstackUplinkEvent", 72 "DEFAULT_PORT", 73 "DISCOVERY_PATH", 74 "STATION_PROTOCOL_VERSION", 75 "Crc", 76 "Network", 77 "NetworkEvent", 78 "Notice", 79 "Packet", 80 "PacketKind", 81 "Relayed", 82 "Rxpk", 83 "Slot", 84 "Stat", 85 "StationBroadcast", 86 "StationDataRate", 87 "StationJoinRange", 88 "StationKind", 89 "StationLevels", 90 "StationMessage", 91 "StationRouter", 92 "StationWindow", 93 "StationXtime", 94 "TxStatus", 95 "Txpk", 96 "acknowledgment", 97 "chirpstack_uplink_topic", 98 "encode", 99 "parse", 100 "station_discovery", 101 "station_discovery_parse", 102 "station_encode", 103 "station_eui_of", 104 "station_heard", 105 "station_id6", 106 "station_parse", 107 "station_router_accepted", 108 "station_router_parse", 109 "station_router_refused", 110 "station_xtime", 111 "station_xtime_parts", 112] 113 114#: The port a packet forwarder sends to by convention, which the protocol itself does not fix. 115DEFAULT_PORT = 1700 116 117#: The path a station appends to its configured address to find its network server. 118DISCOVERY_PATH = "/router-info" 119 120#: The protocol version a station reports. 121STATION_PROTOCOL_VERSION = 2 122 123 124class PacketKind(str, enum.Enum): 125 """Which kind of datagram.""" 126 127 #: The gateway forwarding what it heard. 128 PUSH_DATA = "PushData" 129 #: The server acknowledging a PUSH_DATA. 130 PUSH_ACK = "PushAck" 131 #: The gateway holding its route open through any address translation in front of it. 132 PULL_DATA = "PullData" 133 #: The server sending a packet to transmit. 134 PULL_RESP = "PullResp" 135 #: The server acknowledging a PULL_DATA. 136 PULL_ACK = "PullAck" 137 #: The gateway reporting what became of a PULL_RESP. 138 TX_ACK = "TxAck" 139 140 141class Crc(str, enum.Enum): 142 """What the CRC of a received packet said.""" 143 144 #: The CRC checked. 145 OK = "Ok" 146 #: The CRC failed. 147 FAILED = "Failed" 148 #: The packet carried no CRC. 149 ABSENT = "Absent" 150 151 152class TxStatus(str, enum.Enum): 153 """What became of a downlink the server asked for, as the protocol names it.""" 154 155 #: It was scheduled. 156 NONE = "NONE" 157 #: It arrived too late to schedule. 158 TOO_LATE = "TOO_LATE" 159 #: Its timestamp is too far ahead. 160 TOO_EARLY = "TOO_EARLY" 161 #: Another packet was already scheduled then. 162 COLLISION_PACKET = "COLLISION_PACKET" 163 #: A beacon was already scheduled then. 164 COLLISION_BEACON = "COLLISION_BEACON" 165 #: The radio chain cannot reach that frequency. 166 TX_FREQ = "TX_FREQ" 167 #: The gateway cannot transmit at that power. 168 TX_POWER = "TX_POWER" 169 #: A GPS timestamp was asked for while the GPS is unlocked. 170 GPS_UNLOCKED = "GPS_UNLOCKED" 171 172 173class StationKind(str, enum.Enum): 174 """Which kind of message a session carries, as the protocol writes it.""" 175 176 #: What the station reports about itself when a session opens. 177 VERSION = "version" 178 #: How the server tells the station to configure its radios. 179 ROUTER_CONFIG = "router_config" 180 #: A join request the station heard. 181 JOIN_REQUEST = "jreq" 182 #: A data frame the station heard. 183 UPLINK = "updf" 184 #: A frame of a kind this protocol does not describe, carried whole. 185 PROPRIETARY = "propdf" 186 #: A frame the server asks the station to transmit. 187 DOWNLINK = "dnmsg" 188 #: Frames the server asks the station to transmit to a group. 189 SCHEDULE = "dnsched" 190 #: What became of a frame the station was asked to transmit. 191 TRANSMITTED = "dntxed" 192 #: The clock the two keep between them. 193 TIME_SYNC = "timesync"
One gateway that heard an uplink.
An uplink as ChirpStack reports it.
Reads an uplink event from the JSON ChirpStack published.
Fields protobuf's JSON mapping leaves out when they hold their default read as that
default: a frame counter of zero, ADR off, unconfirmed. Raises PamojaError for text
that is not a JSON object, an event with no device EUI, or a field that does not read as
what it should.
The gateway that heard it with the highest signal-to-noise ratio, or None when none
did.
142class Crc(str, enum.Enum): 143 """What the CRC of a received packet said.""" 144 145 #: The CRC checked. 146 OK = "Ok" 147 #: The CRC failed. 148 FAILED = "Failed" 149 #: The packet carried no CRC. 150 ABSENT = "Absent"
What the CRC of a received packet said.
125class PacketKind(str, enum.Enum): 126 """Which kind of datagram.""" 127 128 #: The gateway forwarding what it heard. 129 PUSH_DATA = "PushData" 130 #: The server acknowledging a PUSH_DATA. 131 PUSH_ACK = "PushAck" 132 #: The gateway holding its route open through any address translation in front of it. 133 PULL_DATA = "PullData" 134 #: The server sending a packet to transmit. 135 PULL_RESP = "PullResp" 136 #: The server acknowledging a PULL_DATA. 137 PULL_ACK = "PullAck" 138 #: The gateway reporting what became of a PULL_RESP. 139 TX_ACK = "TxAck"
Which kind of datagram.
174class StationKind(str, enum.Enum): 175 """Which kind of message a session carries, as the protocol writes it.""" 176 177 #: What the station reports about itself when a session opens. 178 VERSION = "version" 179 #: How the server tells the station to configure its radios. 180 ROUTER_CONFIG = "router_config" 181 #: A join request the station heard. 182 JOIN_REQUEST = "jreq" 183 #: A data frame the station heard. 184 UPLINK = "updf" 185 #: A frame of a kind this protocol does not describe, carried whole. 186 PROPRIETARY = "propdf" 187 #: A frame the server asks the station to transmit. 188 DOWNLINK = "dnmsg" 189 #: Frames the server asks the station to transmit to a group. 190 SCHEDULE = "dnsched" 191 #: What became of a frame the station was asked to transmit. 192 TRANSMITTED = "dntxed" 193 #: The clock the two keep between them. 194 TIME_SYNC = "timesync"
Which kind of message a session carries, as the protocol writes it.
153class TxStatus(str, enum.Enum): 154 """What became of a downlink the server asked for, as the protocol names it.""" 155 156 #: It was scheduled. 157 NONE = "NONE" 158 #: It arrived too late to schedule. 159 TOO_LATE = "TOO_LATE" 160 #: Its timestamp is too far ahead. 161 TOO_EARLY = "TOO_EARLY" 162 #: Another packet was already scheduled then. 163 COLLISION_PACKET = "COLLISION_PACKET" 164 #: A beacon was already scheduled then. 165 COLLISION_BEACON = "COLLISION_BEACON" 166 #: The radio chain cannot reach that frequency. 167 TX_FREQ = "TX_FREQ" 168 #: The gateway cannot transmit at that power. 169 TX_POWER = "TX_POWER" 170 #: A GPS timestamp was asked for while the GPS is unlocked. 171 GPS_UNLOCKED = "GPS_UNLOCKED"
What became of a downlink the server asked for, as the protocol names it.
Returns the acknowledgment a server owes a datagram, or None for one that needs none.
Builds the MQTT topic an application's uplink events are published on, with a wildcard in
place of the device: application/<id>/device/+/event/up.
Writes a datagram to send over a socket.
Reads a datagram that arrived.
Writes the request a station sends to find its network server.
Reads the request a station sent to find its network server, as the server does, and returns the station asking as sixteen hexadecimal digits.
Writes a message of any kind the way the websocket carries it.
Reads an identifier written in any form the protocol accepts.
Reads a frame the radio heard into the message that reports it.
Writes an identifier in the ID6 form the protocol prefers.
Reads a message that arrived over the websocket.
Writes the answer that sends a station to the websocket its session runs on.
Reads the answer a discovery endpoint gives.
Writes the answer that refuses a station, saying why.
Builds a station clock value from the radio it was read on, the run of the station, and the microseconds that run had counted.
Takes a station clock value apart into its radio unit, run, and microseconds.