Usage
This build exposes no OpenAPI resource CRUD routes — only the custom /data/lorawan/* REST routes documented below, alongside the system.lorawan.* scripting API. For the Ignition 8.3 build, see the current documentation.
Device Lifecycle
OTAA Join Process
- Registration: Register the device with its DevEUI and AppKey
- Device Power On: Device sends JoinRequest
- Server Validation: Module verifies the MIC and checks DevNonce (replay protection)
- Session Creation: Server derives session keys and assigns a DevAddr
- JoinAccept: Server sends the encrypted JoinAccept via a gateway
- Ready: Device is joined and can send data
system.lorawan.addDevice(
devEui="A358C4B192543F75",
appKey="925742558798BFB46E938D7A5A1B161F",
name="TempSensor_01",
deviceClass="A")
ABP Activation
- Registration: Register the device with DevEUI, DevAddr, NwkSKey, AppSKey
- Device Power On: Device immediately sends data frames
- Ready: No join procedure needed
system.lorawan.addDevice(
devEui="0102030405060708",
mode="ABP",
name="ABP_Sensor",
devAddr="26011234",
nwkSKey="2B7E151628AED2A6ABF7158809CF4F3C",
appSKey="3C4F9C09885817FBF6A2D2AE28161E7B")
Uplink Data
When a device sends an uplink:
- Gateway receives the LoRa transmission
- Gateway forwards it to Ignition via UDP
- Module processes the frame (MIC verification, frame-counter validation, decryption, and payload decoding if a decoder is assigned)
- Tags are updated in real time under
Uplink/and, for decoded fields, at the device root - Device state is persisted
Multi-Gateway Deduplication
When multiple gateways receive the same uplink (keyed by DevAddr:FCnt within a 5-second window):
- The frame is processed once
- The best gateway is selected for downlink routing by RSSI, then SNR as a tiebreaker
- Duplicate receptions are otherwise dropped
Downlink Messages
Downlinks can be enqueued via tags or the REST API. The per-device queue holds up to 10 messages. Confirmed downlinks are retried (up to 8 attempts), and the module falls back to RX2 when RX1 cannot be used.
When the module trial has expired, the downlink endpoint returns HTTP 503 and no frames are transmitted. See Licensing.
Queue via Tags
Set the writable downlink tags on the device, then trigger the send:
- Write
[LoRaWAN]/Devices/{Name}/Downlink/Payload(hex) - Write
[LoRaWAN]/Devices/{Name}/Downlink/Port - Optionally write
[LoRaWAN]/Devices/{Name}/Downlink/Confirmed - Write
trueto[LoRaWAN]/Devices/{Name}/Downlink/Send(edge trigger)
The current queue is visible as a dataset at [LoRaWAN]/Devices/{Name}/Downlink/Queue.
system.tag.writeBlocking([
"[LoRaWAN]/Devices/TempSensor_01/Downlink/Payload",
"[LoRaWAN]/Devices/TempSensor_01/Downlink/Port",
"[LoRaWAN]/Devices/TempSensor_01/Downlink/Send",
], ["48656C6C6F", 10, True])
Queue via REST API
Endpoint: POST /data/lorawan/devices/:devEui/downlink
curl -X POST "http://localhost:8088/data/lorawan/devices/A358C4B192543F75/downlink" \
-H "Content-Type: application/json" \
-d '{
"fPort": 10,
"payload": "48656C6C6F",
"confirmed": false
}'
| Field | Type | Required | Description |
|---|---|---|---|
fPort | integer | Yes | LoRaWAN port |
payload | string | Yes | Hex-encoded payload |
confirmed | boolean | No | Request device acknowledgment |
Returns HTTP 503 when the module trial has expired.
View / Cancel Queued Downlinks
# List queued downlinks
curl "http://localhost:8088/data/lorawan/devices/A358C4B192543F75/downlinks"
# Cancel a queued downlink (only if still QUEUED)
curl -X DELETE "http://localhost:8088/data/lorawan/devices/A358C4B192543F75/downlinks/{id}"
Transmission Timing
| Device Class | Timing |
|---|---|
| Class A | RX1 window after uplink, with RX2 fallback |
| Class C | Scheduled by the Class C scheduler; a prior uplink is required so the module knows how to route the frame |
Class C downlinks are routed through the last gateway that heard the device, so the device must have sent at least one uplink before a downlink can be scheduled.
RX2 Fallback
When RX1 cannot be used, the module falls back to the region's RX2 default frequency and data rate. Confirmed downlinks are retried up to 8 times.
Device Status
Automatic Status Requests
The module issues a DevStatusReq periodically — by default every 100 uplinks. (This interval is a fixed internal default on the 8.1 build and is not exposed on the Settings page.)
On-Demand Status Request
Via REST API:
curl -X POST "http://localhost:8088/data/lorawan/devices/A358C4B192543F75/status-request"
Via Tag: Write true to [LoRaWAN]/Devices/{Name}/Status/Request
Status Information
| Field | Description |
|---|---|
| Battery | Raw battery byte reported in DevStatusAns (per TS001-1.0.4 §5.3) |
| Margin | Link margin in dB |
| Timestamp | When the status was received |
Adaptive Data Rate (ADR)
The module implements ADR to optimize device transmission parameters by sending LinkADRReq MAC commands, adjusting the device's spreading factor and TX power based on observed link margin.
REST API Reference
Only custom routes under /data/lorawan/* are exposed on the 8.1 build (there are no OpenAPI resource CRUD routes — those are an 8.3-only feature). Device, gateway, and decoder management is done via the scripting API or CSV import/export.
| Method | Path | Description |
|---|---|---|
| GET | /data/lorawan/gateways/status | Runtime status of all gateways (connected, address, lastSeen) |
| POST | /data/lorawan/gateways/:eui/block | Block a gateway (ignore its traffic) |
| POST | /data/lorawan/gateways/:eui/unblock | Unblock a gateway |
| GET | /data/lorawan/devices/status | Runtime status of all devices (joined, devAddr, fCntUp, fCntDown, lastSeen, rssi, snr, battery, margin) |
| POST | /data/lorawan/devices/:devEui/status-request | Queue a DevStatusReq for the next downlink opportunity |
| POST | /data/lorawan/devices/:devEui/downlink | Queue a downlink (503 if trial expired) |
| GET | /data/lorawan/devices/:devEui/downlinks | List queued downlinks for a device |
| DELETE | /data/lorawan/devices/:devEui/downlinks/:id | Cancel a queued downlink (only if QUEUED) |
| GET | /data/lorawan/edition | Report the Ignition edition and any device-count cap |
| GET | /data/lorawan/devices/export | Download all devices as CSV |
| GET | /data/lorawan/devices/template | Download a device CSV template (example OTAA + ABP rows) |
| POST | /data/lorawan/devices/import | Upsert devices from a CSV body; returns a JSON import report |
| GET | /data/lorawan/gateways/export | Download all gateways as CSV |
| GET | /data/lorawan/gateways/template | Download a gateway CSV template |
| POST | /data/lorawan/gateways/import | Upsert gateways from a CSV body; returns a JSON import report |
Status Examples
curl "http://localhost:8088/data/lorawan/devices/status"
curl "http://localhost:8088/data/lorawan/gateways/status"
Edition / Device Cap
curl "http://localhost:8088/data/lorawan/edition"
On Ignition Edge this reports {"edition": "EDGE", "deviceLimit": 50}; on other editions deviceLimit is null.
Scripting API
The system.lorawan.* namespace provides 13 functions (all accept keyword arguments). This is the primary way to manage resources on the 8.1 build. It works from Perspective gateway-scope scripts and the Designer script console. Reads, listings, and downlink queueing are intentionally not scripted — use the [LoRaWAN] tag provider for those.
| Function | Returns | Description |
|---|---|---|
system.lorawan.addDevice(devEui, appKey, name, mode, deviceClass, decoder, devAddr, nwkSKey, appSKey, fCntUp, fCntDown) | DevEUI | Register a device (OTAA by default; mode='ABP' uses the session kwargs) |
system.lorawan.updateDevice(devEui, name, appKey, deviceClass, decoder) | — | Update a device |
system.lorawan.removeDevice(devEui) | — | Remove a device |
system.lorawan.addGateway(eui, name, description, location) | EUI | Register a gateway |
system.lorawan.updateGateway(eui, name, description, location) | — | Update a gateway |
system.lorawan.removeGateway(eui) | — | Remove a gateway |
system.lorawan.addDecoder(name, file) | decoder name | Add a decoder from a JS file |
system.lorawan.updateDecoder(name, file) | — | Update a decoder from a JS file |
system.lorawan.removeDecoder(name) | — | Remove a decoder |
system.lorawan.exportDevices(file) | row count | Export devices to CSV |
system.lorawan.importDevices(file) | JSON report | Import devices from CSV (upsert, best-effort) |
system.lorawan.exportGateways(file) | row count | Export gateways to CSV |
system.lorawan.importGateways(file) | JSON report | Import gateways from CSV (upsert, best-effort) |
CRUD functions raise IllegalArgumentException on bad input or an unknown id.
For decoder, import, and export functions, file is a path on the machine running the script — the gateway filesystem for a Perspective gateway-scope script, or the Designer machine for the script console (the contents are round-tripped to the gateway over RPC).
# Register an OTAA device with a decoder
system.lorawan.addDevice(
devEui="A358C4B192543F75",
appKey="925742558798BFB46E938D7A5A1B161F",
name="TempSensor_01",
mode="OTAA",
deviceClass="A",
decoder="EnvSensor")
# Promote a device to Class C
system.lorawan.updateDevice(devEui="A358C4B192543F75", deviceClass="C")
# Bulk export/import
count = system.lorawan.exportDevices(file="/tmp/devices.csv")
report = system.lorawan.importDevices(file="/tmp/devices.csv") # JSON summary string
Ignition Integration
Tag Bindings
Bind device data to components using the [LoRaWAN] tag provider:
[LoRaWAN]/Devices/TempSensor_01/Temperature
[LoRaWAN]/Devices/TempSensor_01/Uplink/RSSI
[LoRaWAN]/Devices/TempSensor_01/Status/Battery
Historian
Enable history on LoRaWAN tags to store time-series data:
- Right-click a device tag and select Edit Tag
- Enable History
- Configure the sample mode (typically "On Change")
Alarming
Create alarms on LoRaWAN device data, for example a high-temperature alarm on a decoded field or a low-battery alarm on Status/Battery.
Scripting with Tags
# Read a decoded value
temp = system.tag.readBlocking(["[LoRaWAN]/Devices/TempSensor_01/Temperature"])[0].value
# Request device status
system.tag.writeBlocking(["[LoRaWAN]/Devices/TempSensor_01/Status/Request"], [True])
# Enqueue a downlink via tags
system.tag.writeBlocking([
"[LoRaWAN]/Devices/TempSensor_01/Downlink/Payload",
"[LoRaWAN]/Devices/TempSensor_01/Downlink/Port",
"[LoRaWAN]/Devices/TempSensor_01/Downlink/Send",
], ["48656C6C6F", 10, True])
Troubleshooting
| Issue | Possible Cause | Solution |
|---|---|---|
| Gateway not connecting | UDP blocked | Check firewall for the configured port (default 1700) |
| Gateway not connecting | Wrong server address | Verify gateway configuration |
| Device not joining | Wrong AppKey | Verify the 32-character hex key |
| Device not joining | DevNonce replay | Device may need a reset |
| Downlinks not received | Class A timing | Device must uplink first |
| Class C downlink not sent | No prior uplink | Class C needs a prior uplink to establish routing |
| Registering a device fails on Edge | 50-device cap reached | Ignition Edge is capped at 50 devices |
Class B is not supported error | Class B not implemented | Use Class A or Class C |
Tags report Bad_TrialExpired | Trial expired | Apply a license to restart the network layer |
Downlink returns 503 | Trial expired | Apply a license |
Best Practices
- Use OTAA - Provides better security than ABP through dynamic session keys
- Assign decoders - Convert raw payloads into meaningful tags
- Enable historian - Store time-series data for analysis
- Monitor battery - Set up alarms on device status
- Use Class A - Unless minimal-latency downlinks are required
- Check signal quality - Low RSSI/SNR indicates poor coverage
- Back up device keys - Store AppKeys securely outside Ignition; exported CSVs contain keys in plaintext
- Keep a license current - Trial expiry disables the network layer