# 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: its page keeps **Add tag** greyed, saying why, until the Device is registered as a sensor.

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.

## Prepare the gateway {#prepare-the-gateway}

The gateway dials this station through LoRa Basics Station, and out of the box it
does not know where the station is. The page of each device registered as a radio
concentrator carries a **Prepare the gateway** card with what to enter in the
gateway's Basics Station settings, written for this station:

| What to configure on the gateway | What the card gives |
|---|---|
| LNS address (Basics Station's `tc.uri`) | `wss://<address>:5001`, first with the address by this station's name (the "Network name" of the embedded OPC UA server's configuration, or the computer's name while it is empty), then once for each IPv4 address this station has on a network, each with a copy button. A gateway that checks the server's name, as Basics Station does by default (`TLS_SNI`), needs the address by name; one that does not accepts any of them, and then the one on its own network will do. |
| Station EUI | The concentrator's Station EUI, with a copy button: the identity this device registers the gateway under. Each gateway is a concentrator device of its own. While it is empty, the card says to enter it in the Connection card. |
| Client certificate (`tc.crt`, with its key in `tc.key`) | Mutual TLS: the gateway presents a client certificate that carries its Station EUI in its common name or in a DNS alternative name (see [Certificates](#certificates)). |
| Server certificate to trust (`tc.trust`) | The whole path of `GanterLabConnector-<station>.pem`, this station's root certificate, with an **Open folder** button that shows the file in Windows' file manager. The button works in the station's own window; in a browser on another device it stays greyed and says so. The file is written the first time the network server starts, and until then the card says so. |

The card is open while the gateway has not connected since the station opened, and
folds once it has: it stays on the page for the next time the gateway is set up. A
gateway whose certificate no root installed in Windows vouches for is refused at its
first connection and waits under [Equipment access](#equipment-access). A gateway
that speaks only the Semtech UDP packet forwarder protocol cannot connect: it needs
Basics Station firmware.

The file is the same for every gateway of this station, and it does not change when the
station's name or addresses do. A gateway with Basics Station's default settings accepts the
station: nothing needs to be turned off in its configuration.

**Every gateway that was given a file by an earlier version needs this one, installed once
as `tc.trust`.** On the first start of this version, a station whose gateways may hold an
earlier file shows a notice on the card, **This station's file changed**, in that run only. The
notice is a reminder, not the instruction: install the new file once on every gateway of a
station that already had gateways, whether or not the card shows the notice.

## 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.** The network server presents a certificate signed by this
station's own root, the same one the OCPP central system relies on. The root is created
once for the profile, the first time either server starts, and kept in the app's data
folder:

- `GanterLabConnector.pfx`, under `ua\pki\own\private`: 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.
- `GanterLabConnector-<station>.pem`, under `ua\pki\station`: the public root certificate.
  This is the file you install on the gateway as `tc.trust` so it trusts this station.

The root's subject is `Ganter Lab Connector (<station>)`, where `<station>` is the
network name on the day it was created, and it **does not expire**. You install its public
half on each concentrator once, by hand, so a root 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
**Station root certificate** card on the driver page shows the date it carries as a readout
(**Valid until**), with the public file's whole path and an **Open folder** button that
shows it in Windows' file manager (in the station's own window; in a browser on another
device the button stays greyed and says so).

The certificate the network server presents under that root names the station's network
name (the "Network name" of the embedded OPC UA server's configuration; the computer's name
while it is empty), `localhost` and every IPv4 address of the station, so a gateway that
checks the server's name accepts it. Renaming the computer, changing the network name or
changing the station's addresses does not replace the root: only the certificate under it is
issued again, by itself, with nothing to do on the concentrators.

The root is created again only when the stored one can no longer be opened, for example
because it was written by another Windows account. In that case, and the first time this
version starts on a station that may already have given concentrators an earlier file, the
driver page says, once, under **Concentrators need the new certificate file**, that every
concentrator has to be given the new `GanterLabConnector-<station>.pem` as `tc.trust`.
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. Be trusted in one of two ways: it builds a valid chain on this Windows machine, up
   to a root installed there; or you approved this very certificate under
   **Equipment access** on this driver's page (below). Either way it must be within its
   validity dates. A failed check refuses 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.

### Equipment access {#equipment-access}

A gateway whose certificate no root installed in Windows vouches for, because it is
self-signed or issued by the maker's own authority, does not need that root installed
on this computer. Its first connection is refused, and its certificate waits under
**Equipment access** at the end of the LoRaWAN driver's page, the same card the OPC UA
driver's page carries, until you decide:

| Group | What it lists | What you can do |
|---|---|---|
| Waiting for approval | Each refused certificate, under the Station EUI it carries: its SHA-256 fingerprint, when it last tried and how many times, and its details (subject, issuer, domain names, validity dates). | **Approve** admits the certificate from the gateway's next connection. **Block** keeps refusing it without listing it here again. **Discard all pending certificates** empties the list without approving or blocking anything. |
| Approved equipment | The certificates you approved or imported, with who decided and when. | **Withdraw approval** closes the gateway's connection and returns its certificate to the list. **Import certificate** approves a gateway's public certificate before its first connection. |
| Blocked equipment | The certificates you blocked. A blocked certificate is refused even if Windows trusts it. | **Unblock** returns the certificate to the list, without approving it. |

Each certificate is approved on its own, never the authority that signed it, so a
second gateway from the same maker waits for its own approval. Check the fingerprint
against the one the gateway reports before you approve it. Approving does not change
which gateway the certificate is: it is still admitted only under the Station EUI it
carries, and only within its validity dates. A certificate Windows already trusts is
admitted as before and never appears here; nothing is approved automatically.

The decisions are kept in the station's data folder, under `ua\pki\lorawan`, and survive
a restart. Only a role that may configure the Connector is drawn the verbs, and they act
only in the station's own window: a browser on another device reads the card without
changing it, and no agent tool approves a certificate. The list holds up to 100
certificates waiting for approval; once it is full, a new one is still refused but not
listed, and the card says so.

## 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 |

A Device whose DevEUI (sensor) or Station EUI (concentrator) is still empty, such as one
you have just added, waits for it. The station does not register it on the network
server and no connection is attempted, its row in the tree and the notice at the top
of its page say "DevEUI not entered yet" or "Station EUI not entered yet" instead of
showing an error, **Reconnect** has nothing to reopen, and its health stays Unknown.
Enter the identifier in the Connection card and the station registers the Device. A Device
saved with the identifier empty and named with its EUI (sixteen hexadecimal digits, no
separators) does not wait: it registers under its name, as it always has.

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. A
sensor's session belongs to this station, not to the concentrator that relayed its join:
with more than one concentrator in range, its uplinks arrive through whichever one hears
them, and a concentrator that drops or is removed takes no sensor with it.

Disabling or deleting a device takes it off the network server immediately, without
waiting for a restart: its key leaves the register, the session it held on this station
is released, and its next join is refused. Disabling or deleting a concentrator also
closes its connection at once, and the sensors it relayed keep their sessions.

A registration the server does not accept is not made at all: a DevEUI or Station EUI
that is written and is not 16 hexadecimal digits, an application key that is missing or is not 32
hexadecimal digits, or a region this station cannot configure. The device reads as not
connected, its row in the tree and the red text under its page's header name the field
that was refused and what it has to hold, and the journal keeps the same line. The
network server is not started for it, and the station does not try the same
registration again: correcting the field is what brings the device back, and
**Reconnect** answers with the same refusal.

### The concentrator's health {#the-concentrators-health}

A concentrator carries no tags, so no reading can say whether it is there. Its health, the
dot on its row in the tree and the **Health** column on the driver's page, is whether its
gateway is connected to this station:

| Situation | Health |
|---|---|
| Station EUI still empty | **Not known yet**: nothing is registered |
| Registered, the gateway not connected yet (its certificate still waiting for approval, for one) | **Offline** |
| The gateway connected | **Online** |
| The gateway dropped: stopped, unplugged or cut off from the network | **Offline**, until it connects again |
| Disabled | **Disabled** |

For up to ten seconds after a change to the concentrator is saved, or the Connector starts,
it reads **Not known yet**: registering it anew closes the gateway's connection, and the
gateway takes a moment to connect again. The sensors keep their own health, from their readings,
whatever the concentrator's: with more than one concentrator in range, one that drops leaves
the others delivering.

### 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 (MSB first, big-endian) | 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.
