# LoRaWAN

> Reference for the LoRaWAN driver: the embedded Basics Station network server, gateway certificates, the sensor and concentrator registrations, and payload addressing.

The LoRaWAN driver turns Ganter Lab into a small **LoRaWAN network server**. The
direction of the relationship is reversed from most drivers: the station does not
reach out to a sensor — the **gateway connects to Ganter Lab**, and sensors deliver
their readings as uplinks through that gateway. Ganter Lab embeds a network server
speaking the Semtech **LoRa Basics Station** protocol, so any gateway running Basics
Station firmware can use this station as its LNS.

The embedded server starts when the first enabled LoRaWAN device is loaded into the
running connector, not at app boot, and stops when the last one is removed or
disabled: with no sensor and no concentrator configured, nothing listens on the port
at all. Adding one brings it back, without restarting the application.

Each Device under the driver is one of **two registrations**, chosen in its panel:

- A **sensor**, identified by its DevEUI and admitted with its application key. Its
  tags decode the uplink payload.
- A **radio concentrator** (the gateway), identified by its Station EUI and
  registered in a **radio region** picked from a list. It carries no tags of its own.

Register the concentrator as well as the sensors. A Basics Station concentrator asks
for its radio configuration as soon as it connects and relays nothing at all until it
is answered, so a station that has never seen its Station EUI leaves every sensor
behind it without a value while the connection itself looks healthy. The answer is
built here from the region you picked; there is no configuration text to paste.

## Network server and ports {#network-server-and-ports}

| What | Value |
|---|---|
| Protocol | LoRa Basics Station (WebSocket, JSON) |
| Listener | All interfaces, TCP port **5001**, TLS only |
| Gateway address | `wss://<station-address>:5001` |
| Discovery endpoint | The Basics Station `router-info` request is served on the same listener |
| Data connection | Addressed by the gateway's Station EUI as the last part of the WebSocket path |

The listener is always TLS. There is no setting that turns the encryption off: a
plain-HTTP listener is refused anywhere except loopback, which is unreachable from
the plant network and exists only for in-process tests.

## Certificates {#certificates}

Admission is by mutual TLS: the server proves who it is with its own certificate,
and every gateway must prove who it is with a client certificate.

**Server certificate.** Generated automatically on first start and stored in the
app's data folder under `ua\pki\own\private`:

- `GanterLabOPCUAServer-with-san.pfx` — the private key, encrypted under a random
  password that is itself protected per Windows user (DPAPI), so the file pair opens
  for nobody when copied elsewhere.
- `GanterLabOPCUAServer-with-san.pem` — the public certificate. This is the file you
  install on the gateway so it trusts this station.

The certificate is self-signed and **does not expire**. You install its public half on
each concentrator once, by hand, so a certificate that ran out would take the whole
sensor network down on its anniversary, refused at the concentrator where this
station sees nothing, and cost a visit to every concentrator to bring back. There is
no expiry warning and no renewal command, because there is nothing to renew; the
driver page shows the date it carries as a readout.

It is written again only in two cases, and both say so in the journal:

- The stored file can no longer be opened, for example because it was written by
  another Windows account.
- It was issued by an earlier version of this application, which gave it 12 months.

In either case the driver page says, once, that every concentrator has to be given
the new `GanterLabOPCUAServer-with-san.pem`. Until they are, they cannot connect.

**Gateway client certificate.** Every gateway must present a client certificate
during the TLS handshake; a handshake without one is dropped before any data flows.
The certificate must:

1. Build a valid chain on this Windows machine (chain errors such as an untrusted
   root refuse the connection, and the journal records which check failed).
2. Carry the gateway's **Station EUI** in its subject common name or in a DNS
   subject alternative name. The identifier is read as a value, so `AABBCCDDEEFF0011`,
   `AA-BB-CC-DD-EE-FF-00-11` and `AA:BB:CC:DD:EE:FF:00:11` all name the same station.
3. Name the **same station** the data connection claims to be: a certificate that
   names a different gateway than the one in the connection address is refused.

## Connection fields {#connection-fields}

A LoRaWAN Device has no host or port of its own: a sensor is reached through whatever
concentrator carries its radio traffic, and a concentrator reaches this station. The
panel shows the registration picker first, then the fields that registration needs.

| Field | What it is | Format | Default |
|---|---|---|---|
| Registered as | Which of the two registrations this Device is: **Sensor** or **Radio concentrator**. Switching it clears the field below, because an application key and a region are not the same thing. | Sensor / Radio concentrator | Sensor |
| DevEUI (sensor) | The sensor's DevEUI, the identity it presents in its join request. | 16 hexadecimal digits, plain (`8899AABBCCDDEEFF`), with dashes (`88-99-AA-BB-CC-DD-EE-FF`) or with colons. | empty |
| Application key (sensor) | The sensor's OTAA application key (AppKey). It authenticates the join and derives the session keys. | Exactly 32 hexadecimal digits, no separators. | empty |
| Station EUI (concentrator) | The concentrator's Station EUI, the same identifier its client certificate must carry and the one it puts in its connection address. | 16 hexadecimal digits, in any of the spellings above. | empty |
| Radio region (concentrator) | Where the concentrator stands. It decides the channels, data rates and receive windows this station hands it on connection. Picked from the list; there is no free text. | Europe 868 MHz, United States 915 MHz, Australia 915 MHz, Asia 923 MHz, China 470 MHz (revision 1), China 470 MHz (revision 2) | Europe 868 MHz |
| Maximum time without a transmission (sensor) | Seconds this sensor may stay quiet before its stored values stop counting as readings. 0 turns expiry off. See below. | 0 or greater | 3600 |

An identifier left empty falls back to the sanitized device name, which is almost
never a valid EUI, so fill it in.

The application key is a secret, and the station handles it as one. The panel hides it
behind dots, with a **Show password** box that reveals it while you check what you
typed; the configuration database keeps it protected under the Windows account that
entered it; and it appears neither in the journal nor in the address the driver reads,
which carries the DevEUI alone. Open the same configuration under another Windows
account and the key does not open with it: the field comes back empty, saying "Cannot
be read on this Windows account: it only opens on the machine and the Windows account
that entered it. Enter it again here.", and the sensor stops joining until you type
the key again. A configuration carried across in a backup keeps it, because the backup
is sealed with the password you give it.

Joining is **OTAA only**: the sensor performs an over-the-air join through the
concentrator, and the network server answers it using the configured AppKey. A join
request from a DevEUI that no configured device carries is refused and journaled.
Activation by personalization (ABP, pre-provisioned session keys) is not supported.

Disabling or deleting a device takes it off the network server immediately, without
waiting for a restart: its key leaves the register, the session any concentrator held
for it is released, and its next join is refused. A registration the server does not
accept, a mistyped DevEUI, application key, Station EUI or region, leaves the device
reading as not connected, and the journal names the field that was refused.

### What the concentrator is sent {#what-the-concentrator-is-sent}

When a registered concentrator finishes its version handshake, this station answers
with the radio configuration for its region: the frequency range, the data rates and
which of them are downlink only, and the region's default channel plan. If the
Station EUI is not registered here, nothing is sent, and the journal says which
station asked and that it is not registered. That is the entry to look for when a
concentrator connects and no sensor behind it ever reports a value.

There are no poll-interval fields. LoRaWAN is push-based: the sensor decides when it
uplinks, the server keeps the most recent decoded payloads per device, and the tag
values refresh from that cache on a fixed one-second cycle.

A sensor that has not uplinked yet therefore has **no reading**, not a failed one. The
tags stay empty, the device reports that nothing has been observed rather than going
offline, and no failure is journaled: waiting for the first uplink is the protocol
working. An address that resolves to nothing in a payload the sensor did send is a
different matter and is reported as the read failure it is.

### When a sensor goes quiet {#when-a-sensor-goes-quiet}

Because nothing polls a sensor, the payload it sent last stays in that cache until it
sends another one. Left alone, a sensor whose battery died would go on publishing the
same number as a good reading for as long as the station runs, feeding charts, history
and alarms with a value that stopped being true.

Every sensor therefore carries a **maximum time without a transmission**, in seconds,
in its own panel. The default is **3600 seconds**, one hour, which clears the ten to
thirty minute cadence of a common sensor with room to spare; a sensor that reports
more rarely raises its own. Past that deadline:

- Its tags stop carrying a value and publish bad quality, instead of repeating the last
  uplink.
- The device panel says when the sensor last transmitted, so you can see how long it has
  been quiet.

Setting the field to **0** turns expiry off for that sensor: its last uplink keeps
reading as good indefinitely. Nothing else changes, and no stored payload is discarded.

## Tag addressing {#tag-addressing}

A LoRaWAN tag addresses **bytes inside the decoded uplink payload**. There is no
free-text address; the Source card offers typed fields in two modes, switched by the
**Address by Cayenne LPP channel/type** checkbox:

| Field | What it does | Values | Default |
|---|---|---|---|
| Address by Cayenne LPP channel/type | Off: the channel field is a plain byte offset. On: the payload is scanned for a Cayenne LPP channel/type pair. | on / off | off |
| Channel / payload offset | Offset mode: the 0-based byte offset the value starts at. LPP mode: the LPP channel number to search for. | 0 or greater | 0 |
| LPP type code | LPP mode only: the Cayenne LPP data-type byte that follows the channel byte (for example 103 for temperature). The value bytes are read from right after the matching channel/type pair, and when several payloads match, the newest one wins. | 0 or greater | 0 |
| Read length (bytes, blank = auto) | How many payload bytes make up the value. Blank derives it from the data type: 1 for Boolean and Byte, 2 for Int16, 4 for Int32 and Float, 16 for String. | 1 or greater, or blank | blank |
| Bitmask (hex, blank = none) | A hexadecimal mask ANDed onto the raw bytes before anything else (for example `0x0FFF` to drop status bits). The `0x` prefix is optional and an odd-length mask is left-padded. The mask must cover exactly as many bytes as the read length, otherwise the read errors. | hex text or blank | blank |
| Most significant byte first | Declares that the payload carries the value big-endian; the read bytes are reversed before conversion. There is no word-order box here: an arbitrary-length payload has no 16-bit words to swap. | on / off | off |
| Multiplier (blank = none) | A wire-side factor applied by the network server right after decoding, before the value enters the tag's own conversion pipeline. Applies to numeric types only. A 0 is refused where it is typed ("A multiplier of 0 would zero every reading."), because it would report every reading of the tag as 0 with good quality. | any number other than 0, or blank | blank |

The order of operations on a read is: take the addressed bytes → apply the bitmask →
reverse if most-significant-byte-first → convert to the tag's data type → apply the
multiplier (numeric types).

Examples, for a sensor whose 11-byte payload carries a big-endian Int16 temperature
at offset 2:

- Offset mode: channel/offset `2`, read length `2`, most significant byte first on.
- LPP mode, same sensor speaking Cayenne LPP on channel 1 with type 103: tick the
  LPP checkbox, channel `1`, type code `103`, read length `2`.

## Supported data types {#supported-data-types}

| Type | Bytes read (auto) | How the bytes are interpreted |
|---|---|---|
| Boolean | 1 | Zero is false, anything else true |
| Byte | 1 | Unsigned byte |
| Int16 | 2 | Signed 16-bit integer |
| Int32 | 4 | Signed 32-bit integer |
| Float | 4 | IEEE 754 single precision |
| String | 16 | UTF-8 text of the read length |

## Writes and discovery {#writes-and-discovery}

LoRaWAN tags are **read only**: values come only from sensor uplinks, the access
field offers no writable choice, and the runtime refuses a write instead of faking
success. Downlinks exist in the protocol as network traffic (join accepts, MAC
commands), never as tag value writes.

The driver has no discovery scan. Add the concentrator by hand with its Station EUI
and region, and each sensor by hand with its DevEUI and AppKey; the general
add-a-device flow is described on the [Connector](connector) page.
