The LNS Protocol

Station connecting to an LNS is a two-step process which uses websockets as transport to exchange text records containing JSON-encoded objects.

First, Station queries the LNS for the URI of the actual connection end-point of the LNS. Second, it establishes a data connection with that end-point to receive setup information and henceforth exchange LoRa uplink and downlink frames through that connection. This way an LNS MAY dynamically assign gateways to different data connection end-points in order to, for example, distribute load or favor geographical proximity.

Note: The LNS service assigning data connection end-points can be separate from the LNS and run on servers separate from the LNS connection end-points, which again may be distributed across different servers.

LNS Protocol

Querying the LNS Connection End-Point (Discovery)

By default, the URI of the initial query is constructed from the contents of the file tc.uri by appending the path /router-info (see Section Credentials for other options). This forms a websocket end-point to which the following JSON object is submitted:

{
  "router": ID6 | EUI | integer
}

Station hereby simply declares its identity and asks for directions regarding the LNS connection end-point it SHALL connect to. The identity is a 64 bit EUI provided in the field router either in the form of string encoding the EUI in ID6 or EUI format, or as an integer value representing the EUI.

Depending on the authorization scheme (cf. Section Authentication Modes), the LNS SHALL verify that the declared identity matches the supplied credentials and SHALL answer with the following JSON object, immediately closing the connection afterwards:

{
  "router": ID6,
  "muxs"  : ID6,
  "uri"   : "URI",
  "error" : STRING   // only in case of error
}

The field router SHALL contain the normalized gateway identity, the field muxs SHALL contain the identity of the LNS connection end-point selected by the LNS, and the field uri SHALL contain the address of this connection end-point. Station then connects to this URI to establish a data connection with the LNS.

If the gateway cannot be identified or in case of any other problem, the response object SHALL contain the field error describing the problem. The fields muxs and uri MAY be missing.

Connecting to an LNS

After a successful query of the LNS data connection end-point, Station will connect to the obtained end-point URI using the currently configured credentials. As mentioned before, the established link is a websocket connecting where Station and LNS exchange JSON objects. Each JSON object, whether in uplink (i.e., from Station to LNS) or downlink direction (i.e., from LNS to Station), MUST have a field named msgtype which identifies the type of message and determines the structure of the JSON object.

Right after the websocket connection has been established, Station first sends a version message and the LNS SHALL respond with a router_config message. Afterwards normal operation is started with uplink/downlink messages as described below.

version Message

The first message sent by Station is a version message reporting to the LNS its version information (station, firmware, model), the protocol version (the constant 2 for now), and a list of optional features supported by the gateway:

{
  "msgtype"   : "version"
  "station"   : STRING
  "firmware"  : STRING
  "package"   : STRING
  "model"     : STRING
  "protocol"  : INT
  "features"  : STRING
}

The features string is space-separated list of some of the following keywords:

  • rmtsh: Station supports remote-shell access through the websocket link established with the LNS.
  • prod: Station has been built at production level, that is, certain test/debug features MAY be disabled.
  • gps Station has access to a GPS device.

router_config Message

The LNS SHALL respond to a version message with a router_config message to specify a channel plan for the Station and define some basic operation modes:

{
  "msgtype"    : "router_config"
  "NetID"      : [ INT, .. ]
  "JoinEui"    : [ [INT,INT], .. ]  // ranges: beg,end inclusive
  "region"     : STRING             // e.g. "EU863", "US902", ..
  "hwspec"     : STRING
  "freq_range" : [ INT, INT ]       // min, max (hz)
  "DRs"        : [ [INT,INT,INT], .. ]   // sf,bw,dnonly
  "sx1301_conf": [ SX1301CONF, .. ]
  "nocca"      : BOOL
  "nodc"       : BOOL
  "nodwell"    : BOOL
}

The fields NetID and JoinEui are used to filter LoRa frames received by Station. NetID is a list of NetID values that are accepted. Any LoRa data frame carrying a NetID other than the listed ones will be dropped. JoinEui is a list of pairs of integer values encoding ranges of join EUIs. Join request frames will be dropped by Station unless the field JoinEui in the message satisfies the condition BegEui<=JoinEui<=EndEui for at least one pair [BegEui,EndEui].

The fields nocca, nodc, and nodwell are only available in debug builds of Station to disable certain regulatory transmit constraints, namely clear channel assessment as well as duty-cycle and dwell-time limitations.

The field region specifies the region of channel plan and thereby controls certain regulatory behavior such as clear channel assessment as well as duty-cycle and dwell-time limitations. Valid names are compatible with the names of the LoRaWAN Regional Parameters specification, except the end frequency is dropped (e.g., EU863 and US902).

The field hwspec describes the what concentrator hardware is needed to operate the channel plan delivered in the router_config message. The assigned string MUST have the following form: sx1301/N where N denotes the number of SX1301 concentrator chips required to operate the channel plan. Station will check this requirement against its hardware capabilities.

The field freq_range defines the lower and upper boundaries of the available spectrum for the region set. Station will NOT allow downlink transmissions outside of this frequency range.

The field DRs defines the available data rates of the channel plan. It MUST be an array of 16 entries with each entry being an array of three (3) integers encoding the spreading factor SF, the bandwidth BW, and a DNONLY flag. SF MUST be in the range 7..12 for LoRa, or 0 for FSK. BW MUST be one of the numbers 125000, 250000, 500000, and BW is ignored for FSK. DNONLY MUST be 1 if the data rate is valid for downlink frames only, and 0 otherwise.

The field sx1301_conf defines how the channel plan maps to the individual SX1301 chips. Its value is an array of SX1301CONF objects. The number of array elements MUST be in accordance with the value of the field hwspec.

The layout of a SX1301CONF object looks like this:

{
  "radio_0": { .. } // same structure as radio_1
  "radio_1": {
    "enable": BOOL,
    "freq"  : INT
  },
  "chan_FSK": {
    "enable": BOOL,
    "radio": 0|1,
    "if": INT
  },
  "chan_Lora_std": {
    "enable": BOOL,
    "radio": 0|1,
    "if": INT,
    "bandwidth": INT,
    "spread_factor": INT
  },
  "chan_multiSF_0": { .. }  // _0 .. _7 all have the same structure
  ..
  "chan_multiSF_7": {
    "enable": BOOL,
    "radio": 0|1,
    "if": INT
  }
}

Monitoring Round-trip Times

The message exchange between Station and LNS allows for monitoring round-trip times by piggybacking certain fields into normal message exchanges. The LNS MAY add a field MuxTime to every downlink sent to Station:

{
  "msgtype" : ".."
  "MuxTime" : FLOAT    // downlink
  ..
}

The field MuxTime contains a float value representing a UTC timestamp with fractional seconds and marks the time when this message was sent by the LNS. Station will respond by adding the field RefTime:

{
  "msgtype" : ".."
  "RefTime" : FLOAT  // uplink
  ..
}

The field RefTime is calculated from the last received MuxTime adjusted by the time interval on the router between the arrival of MuxTime and the sending of RefTime. Since downlink traffic is less frequent than uplink messages, it is likely that a MuxTime is reused to construct multiple RefTime fields. This means that round-trip measurements are composed of the latency of the last downlink message and the most recent uplink message.

Time Synchronization

There are two modes of operation for time synchronization: (1) With access to a PPS signal, and (2) Independent of a PPS signal.

Synchronizing PPS to GPS Time

If Station has access to a PPS signal aligned to GPS seconds, it will infer the sychronization to GPS time by running a protocol with the LNS. To infer the correct GPS time label for a given PPS pulse, Station keeps sending timesync upstream messages to the LNS which should be promptly answered with a downstream timesync message.

Format of an upstream message:

{
  "msgtype"  : "timesync"
  "txtime"   : INT64,
}

The downstream response SHOULD be sent by the LNS immediately after reception of the corresponding upstream timesync. The LNS SHALL insert the field gpstime field and pass through the field txtime received from Station, whereby txtime is some unspecified time local to Station.

The field gpstime marks the instant of time when the LNS processes the timesync message. Its value is the time in microseconds since GPS epoch.

{
  "msgtype"  : "timesync"
  "txtime"   : INT64,
  "gpstime"  : INT64
}

Transferring GPS Time

The LNS SHALL periodically send the following message to Station to transfer GPS time.

{
  "msgtype"  : "timesync"
  "xtime"    : INT64,
  "gpstime"  : INT64
}

The field xtime is particular to Station and the GPS time is inferred from overlapping upstream frames arriving at the LNS.

Remote Command

Station supports two mechanisms for running remote commands on the gateway in the following format:

{
  "msgtype"  : "runcmd"
  "command"  : STRING,
  "arguments": [ STRING, ... ]
}

The field arguments is OPTIONAL or MAY be an empty array. If present and not empty, these arguments are passed along with the command.

The field command contains the actual command to execute. Based on its content, Station behaves as follows:

  • If the executable flag is set, run the command with the specified set of arguments.
  • If it is a file but not executable, treat it as a file containing a shell script. Pass it together with the arguments to the system local shell for interpretation.
  • If none of the above is true, assume the command value represents shell statements. Run the local shell and pass the value as statements together with the arguments for execution.

Remote Shell

Station supports a remote shell session tunneling through the websocket to the LNS. The session is initiated by a command as shown below. After the session is established, the input data towards the shell and the output data generated by the shell and its commands are tunneled through the websocket as binary records. The first byte of the binary record is the session index since Station can maintain multiple sessions. The remaining bytes of a binary record are the input/output data. The encoding is transparent to the websocket transport.

To query, initiate, or stop a session, Tthe LNS SHALL send the following message:

{
  "msgtype"  : "rmtsh",
  "user"     : STRING,
  "term"     : STRING,
  "start"    : INT,
  "stop"     : INT
}

If the fields start and stop are absent, the current session state is queried. Otherwise only one of the two fields SHALL be present indicating a session start or stop, respectively.

The value of the field term is the value of the TERM environment variable. It is set before the local shell is being started.

The field user describes the user who is operating the session. For Station this is just informational context.

Station will respond with the following message describing the current state of all sessions:

{
  "msgtype"  : "rmtsh",
  "rmtsh"    : [
    {
      "user"     : STRING,
      "started"  : BOOL,
      "age"      : INT,
      "pid"      : INT
    },
    ..
  ]
}

The field age is the time in seconds since the input/output action on this remote shell session.

The field pid is the local process identifier of the started shell.

The field started indicates that the shell process is actually running.