AI-B100 MQTT API User Guide
Firmware version 1.9.7 | See also: HTTP API User Guide
The AI-B100 also supports a request/response HTTP API for applications that need to control the gateway directly. If your application needs to send commands and receive results synchronously, see the HTTP API User Guide instead.
Introduction
The AI-B100 MQTT integration connects the gateway to a broker of your choice. Once connected, the gateway publishes LoRaWAN events — join results, send results, downlinks, GPS position, and heartbeats — to configurable topics. Your application subscribes to those topics and receives events as they happen.
You can also send commands back to the gateway by publishing to its inbound topics: trigger a join, send a LoRa uplink, change the data rate, or restart the device.
No callback server is required. Your application only needs access to the same MQTT broker the gateway is connected to.
POST /set), not via MQTT itself. You need access to the gateway's web interface or HTTP API to enable and configure MQTT. See Setting Up MQTT.Before You Start
An MQTT broker reachable by both the gateway and your application. The gateway supports standard MQTT over TCP (no TLS). The broker can be local (e.g. Mosquitto on the same LAN) or hosted.
Gateway IP address and access to its HTTP API or web interface. MQTT settings are configured via
POST /set. See the HTTP API User Guide if you need help with this step.MQTT enabled and configured on the gateway with a valid broker IP and port, then restarted. See Setting Up MQTT.
An MQTT client on your side — any broker client works: Node-RED, Home Assistant, Mosquitto CLI tools, Python
paho-mqtt, or any application library.
Quick Start
Step 1 — Configure MQTT on the gateway
Send the broker address and topics via HTTP:
curl -X POST http://<gateway-ip>/set \
-H "Content-Type: application/json" \
-d '{
"mqtt_enable": 1,
"broker_addr": "192.168.1.10",
"broker_port": 1883,
"status_topic": "lora/gw01/status",
"receive_topic": "lora/gw01/rx",
"send_topic": "lora/gw01/send",
"setup_topic": "lora/gw01/setup"
}'
Then restart:
curl http://<gateway-ip>/restart
Step 2 — Subscribe to availability and status topics
mosquitto_sub -h 192.168.1.10 -t "lora/gw01/status/availability" &
mosquitto_sub -h 192.168.1.10 -t "lora/gw01/status/json" &
Within seconds you should see online on the availability topic. The status/json topic will carry the first status event (code 1 or 5).
Step 3 — Join the LoRaWAN network
If autojoin_enable = 0, trigger a join by publishing:
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/setup/join" -m "1"
Watch for "status": 7 on lora/gw01/status/json — this is published once when the join succeeds. After that, the idle state is "status": 0.
Step 4 — Subscribe to downlinks
mosquitto_sub -h 192.168.1.10 -t "lora/gw01/rx/json" &
Step 5 — Send a LoRa uplink
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/send/1" -m "Hello"
The send result arrives on lora/gw01/status/json. If the network returns a downlink, it arrives on lora/gw01/rx/json.
Core Concepts
The Publish/Subscribe Model
The gateway acts as an MQTT client — it connects to your broker, subscribes to inbound topics, and publishes to outbound topics. Your application does the same from the other side.
Neither side talks directly to the other — everything flows through the broker. This means:
- No open port is needed on your application server.
- Multiple subscribers can receive the same gateway events simultaneously.
- Events published while your application is offline are delivered when it reconnects (if the broker retains them).
Topic Structure
All topics are built from a configurable base name. You set four base topics in settings; the gateway adds fixed suffixes for specific event types:
| Topic | Direction | Format |
|---|---|---|
{status_topic}/availability | Gateway → App | Plain text: online or offline |
{status_topic} | Gateway → App | Plain text: {code} - {description} |
{status_topic}/json | Gateway → App | JSON object |
{receive_topic}/{port} | Gateway → App | Raw binary bytes |
{receive_topic}/json | Gateway → App | JSON object |
{gps_topic} | Gateway → App | JSON object |
{send_topic}/{port} | App → Gateway | Raw bytes (the uplink payload) |
{send_topic}/{port}C | App → Gateway | Raw bytes — confirmed uplink |
{setup_topic}/join | App → Gateway | 1 |
{setup_topic}/restart | App → Gateway | 1 |
{setup_topic}/adr | App → Gateway | 0 or 1 |
{setup_topic}/dr | App → Gateway | 0–5 |
{setup_topic}/ip | App → Gateway | (any payload) |
{setup_topic}/time | App → Gateway | (any payload) |
{setup_topic}/info | App → Gateway | (any payload) |
Plain Text and JSON in Parallel
Every LoRaWAN status event is published twice:
{status_topic}— a short plain-text string:9 - LoRaWAN message sent{status_topic}/json— a full JSON object with all fields
Use plain text for simple dashboards, log tailing, or quick debugging with mosquitto_sub. Use JSON for anything your application processes programmatically.
Gateway Name and the $name$ Tag
Each gateway has a configurable name (up to 16 characters). The default is AI-B100.
You can embed $name$ directly inside any topic string in settings. The gateway replaces it with the configured name once at startup before connecting to the broker:
| Setting stored | Topic used at runtime |
|---|---|
lora/$name$/status | lora/warehouse-01/status |
lora/$name$/rx | lora/warehouse-01/rx |
name setting requires a restart before the new name appears in topics.The gateway name also appears as the "name" field in every JSON payload, so you can identify which gateway sent an event when multiple units publish to the same broker.
Status Codes at a Glance
The status field in every JSON payload and the leading number in every plain-text message carries a numeric code.
Status 7 and status 0 — joining:
- Status
7is published exactly once when the gateway joins the LoRaWAN network. It is an event notification, not a persistent state. If you subscribe after the gateway has already joined, you will not see7. - Status
0is the ongoing "joined and ready" state. It appears on heartbeats, after a/statuspoll, and after any LoRa operation that completes without a specific result. Astatus: 0message with a non-zerodev_addrconfirms the gateway is joined and ready to send.
| Code | Event | What to do |
|---|---|---|
| 0 | Joined and ready (idle) | No action needed |
| 1 | Gateway started or all join retries exhausted — waiting for join | Publish 1 to {setup_topic}/join, or enable autojoin_enable |
| 2 | Send attempted but payload was empty | Check your publish payload |
| 3 | Payload too long for current data rate | Reduce payload or increase data rate |
| 4 | Join attempt failed | Retry join or wait for automatic retry |
| 5 | Auto join in progress (startup or after retry-round exhaustion) | Wait for status 7 |
| 6 | Unknown LoRaWAN error | Check credentials and coverage |
| 7 | Joined — published once at join time | Ready to send |
| 8 | Downlink received | Check {receive_topic}/json |
| 9 | Uplink sent, no downlink | Normal — unconfirmed send succeeded |
| 10 | Uplink confirmed by network (ACK) | Normal — confirmed send acknowledged |
| 11 | Uplink not confirmed (no ACK) | Retry if critical |
| 12 | Heartbeat | No action needed |
| 13 | Duty cycle active — uplink blocked | Wait next_upload_ms ms, then retry |
| 14 | LoRaWAN session lost | Publish 1 to {setup_topic}/join |
| 15 | Invalid LoRaWAN port number | Use FPort 1–223 |
| 16 | Uplink failed (radio error) | Retry |
| 17 | Parameter validation error | Check payload sent to {setup_topic} |
| 18 | Command rejected — not joined | Join first, then retry |
| 19 | Setting saved | No action needed |
Setting Up MQTT
MQTT settings are changed via the HTTP /set endpoint and require a restart to take effect. You cannot configure MQTT topics through MQTT itself.
Configure Broker and Topics
curl -X POST http://<gateway-ip>/set \
-H "Content-Type: application/json" \
-d '{
"mqtt_enable": 1,
"broker_addr": "192.168.1.10",
"broker_port": 1883,
"status_topic": "lora/gw01/status",
"receive_topic": "lora/gw01/rx",
"send_topic": "lora/gw01/send",
"setup_topic": "lora/gw01/setup",
"gps_topic": "lora/gw01/gps",
"mqtt_hb_interval": 60
}'
Then restart:
curl http://<gateway-ip>/restart
Default topic values (used if you do not set them):
| Parameter | Default |
|---|---|
status_topic | lora/$name$/status |
receive_topic | lora/$name$/receive |
send_topic | lora/$name$/send |
setup_topic | lora/$name$/setup |
gps_topic | lora/$name$/gps |
With the default name of AI-B100, the topics resolve to lora/AI-B100/status, etc.
Using the $name$ Tag in Topics
If you manage multiple gateways on the same broker, store $name$ in every topic field and set a unique name per unit. The gateway expands the tag at startup:
curl -X POST http://<gateway-ip>/set \
-H "Content-Type: application/json" \
-d '{
"name": "warehouse-01",
"status_topic": "lora/$name$/status",
"receive_topic": "lora/$name$/rx",
"send_topic": "lora/$name$/send",
"setup_topic": "lora/$name$/setup",
"gps_topic": "lora/$name$/gps"
}'
After restart, all topics resolve to lora/warehouse-01/.... Change only the name parameter on each unit — topics do not need to be re-entered individually.
Authentication
If your broker requires a username and password:
curl -X POST http://<gateway-ip>/set \
-H "Content-Type: application/json" \
-d '{
"mqtt_user": "myuser",
"mqtt_password": "mypassword"
}'
Both fields must be non-empty for credentials to be sent. To disable authentication, set both to empty strings. Requires a restart.
Verify the Connection
Subscribe to the availability topic before restarting. After restart, the gateway connects to the broker and publishes online with retain = true. If this message does not appear within 10–15 seconds, check the broker address, port, and credentials.
mosquitto_sub -h 192.168.1.10 -t "lora/gw01/status/availability" -v
Expected output:
lora/gw01/status/availability online
The online message is retained — any subscriber connecting later will receive it immediately, even if the gateway published it minutes ago.
Subscribing to Gateway Events
Availability
Topic: {status_topic}/availability
Retained: Yes
Format: Plain text
| Payload | When |
|---|---|
online | Published on every successful broker connection |
offline | Last Will and Testament — published by the broker automatically if the gateway disconnects unexpectedly |
Because both messages are retained, a subscriber always sees the current state immediately on subscribe, even if the event happened before the subscriber connected.
Use this topic to detect whether the gateway is alive and connected to the broker.
Status — Plain Text
Topic: {status_topic}
Retained: No
Format: {code} - {description}
Published after every LoRaWAN event. Some events include extra context appended to the description:
1 - Restarted - Ready to Join
7 - LoRaWAN Joined devAddr: 0x26011BDA
9 - LoRaWAN message sent
13 - Uplink unavailable due to duty cycle or other restrictions - 1
12 - Heartbeat: 14
Use this topic for quick shell debugging (mosquitto_sub -v) or simple alerting where you only need the code number. For structured processing, use the JSON topic instead.
Status — JSON
Topic: {status_topic}/json
Retained: No
Format: JSON object
Published in parallel with every plain-text status event. Also used for several auxiliary responses (see below).
Standard status JSON (every LoRaWAN event):
{
"name": "warehouse-01",
"status": 9,
"dev_addr": "26011BDA",
"confirmed": 1,
"fcntUp": 592,
"data_rate": 3,
"maxUp": 115,
"tUnix": 1775036333,
"next_upload_ms": 0
}
Key fields:
| Field | Description |
|---|---|
name | Gateway name — use to identify the source when multiple gateways publish to the same broker |
status | Status code — see Status Codes at a Glance |
dev_addr | "0" = not joined; 8-char hex = joined device address |
maxUp | Max payload bytes at the current data rate — check before sending |
tUnix | UTC Unix timestamp in seconds (0 if not yet synchronized) |
next_upload_ms | 0 = no duty cycle restriction; > 0 = milliseconds to wait before next uplink |
Special messages also published to this topic:
On startup or when {setup_topic}/ip is received:
{"ip": "192.168.1.131", "dhcp": 0}
When {setup_topic}/time is received:
{"tUnix": 1775036333}
When {setup_topic}/info is received:
Full system info JSON — same content as the HTTP /info endpoint, including firmware version, hardware revision, device EUI, and join state.
On tamper input event (HW 1.3 only):
{"name": "warehouse-01", "tamper": 1, "tamper_edge": "up", "tUnix": 1775036333}
See Tamper Input Events for details.
Downlink — Raw Bytes
Topic: {receive_topic}/{port}
Retained: No
Format: Raw binary bytes
Published when a LoRaWAN downlink with a payload is received on {port} (FPort 1–223). The payload is the raw bytes exactly as received — no encoding or wrapping.
Example topic: lora/gw01/rx/21
Use this topic if your application needs the raw bytes directly without parsing JSON. For most use cases, the JSON topic below is more convenient.
Downlink — JSON
Topic: {receive_topic}/json
Retained: No
Format: JSON object
Published for every receive event: a downlink with payload, a linkcheck response, or both together. This is the primary topic for application-level receive processing.
Example — downlink with payload:
{
"name": "warehouse-01",
"confirmed": 1,
"fcntDown": 38,
"rssi": -70,
"snr": 10.0,
"tUnix": 1775036333,
"next_upload_ms": 0,
"port": 21,
"length": 4,
"payload": [1, 2, 3, 255]
}
Example — linkcheck response (no payload):
{
"name": "warehouse-01",
"confirmed": 1,
"fcntDown": 5,
"rssi": -70,
"snr": 10.0,
"tUnix": 1775036333,
"margin": 22,
"gwCount": 1,
"next_upload_ms": 3842,
"length": 0
}
Key fields:
| Field | Description |
|---|---|
fcntDown | Downlink frame counter — use to detect missed messages |
rssi | Signal strength in dBm |
snr | Signal-to-noise ratio in dB |
margin | Link margin from linkcheck — only present in linkcheck responses |
gwCount | Gateways that heard the uplink — only present in linkcheck responses |
length | 0 = no payload (linkcheck only); > 0 = byte count of payload |
port | LoRaWAN FPort — only present if length > 0 |
payload | Printable ASCII → returned as a string. Any non-printable byte → returned as an integer array. Only present if length > 0. |
GPS Position
Topic: {gps_topic}
Retained: No
Format: JSON object
Published at the interval set by mqtt_gps_interval (in seconds). Publishing is disabled when mqtt_gps_interval = 0 or no GPS module is detected.
{
"name": "warehouse-01",
"ns": "N",
"lat": 55.6761,
"ew": "E",
"lon": 12.5683,
"alt": 12.5,
"nosv": 8,
"pdop": 1.8,
"hdop": 1.2,
"vdop": 1.4,
"utc": "103045.00",
"date": "090526",
"sog": 0.0,
"cog": 0.0,
"gps_status": 3
}
lat/lon are decimal degrees; ns/ew give the hemisphere.
utc format is HHMMSS.SS; date format is DDMMYY.
gps_status: 0 = no module, 1 = no fix yet, 2 = 2D fix, 3 = 3D fix.
Sending Commands to the Gateway
The gateway subscribes to {send_topic}/# and {setup_topic}/#. You send commands by publishing to subtopics of these.
All payloads must be plain text (not JSON). Unrecognised subtopics are silently ignored.
Send a LoRa Uplink
Topic: {send_topic}/{port} or {send_topic}/{port}C
Payload: The raw bytes to transmit (up to 222 bytes)
{port} is the LoRaWAN FPort (1–223). Append C or c to request a confirmed uplink.
# Unconfirmed uplink on FPort 1
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/send/1" -m "Hello"
# Confirmed uplink on FPort 1
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/send/1C" -m "Hello"
# Binary payload (example using mosquitto_pub -l or raw bytes)
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/send/5" -n # empty payload test
# Port 0 — trigger a LinkCheckReq (no payload transmitted)
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/send/0" -m ""
The send result arrives on {status_topic}/json — codes 9, 10, 11, 13, or 16. If the network returned a downlink, it also arrives on {receive_topic}/json.
json is reserved and ignored. Publishing to {send_topic}/json does nothing.Before sending, check maxUp — the maximum payload size at the current data rate. A payload larger than maxUp is rejected with status code 3. The maxUp value is included in every status JSON message.
The gateway must be joined before a send command is accepted. If not joined, the send is rejected with status code 18 published to {status_topic}/json.
Request Linkcheck
Topic: {send_topic}/0
Payload: (any — ignored)
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/send/0" -m ""
Sends a LoRaWAN LinkCheckReq MAC command. The gateway transmits an uplink on port 0; the network server responds in the next downlink window with signal quality information.
The result arrives on {receive_topic}/json:
{
"name": "warehouse-01",
"confirmed": 1,
"fcntDown": 5,
"rssi": -70,
"snr": 10.0,
"tUnix": 1775036333,
"margin": 22,
"gwCount": 1,
"next_upload_ms": 3842,
"length": 0
}
| Field | Description |
|---|---|
margin | Link margin in dB as reported by the network server — higher is better |
gwCount | Number of gateways that received the uplink |
The gateway must be joined before a linkcheck is accepted. If not joined, the request is rejected with status: 18.
Trigger a Join
Topic: {setup_topic}/join
Payload: 1
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/setup/join" -m "1"
The gateway starts an OTAA join. Progress arrives on {status_topic}/json:
status: 4on each failed attemptstatus: 7on success — published once; after this the idle state isstatus: 0- After
join_retryconsecutive failures,status: 1is published and the gateway returns to the waiting-for-join state
If autojoin_enable is changed to 1 while the gateway is in the waiting-for-join state (including after retries are exhausted), autojoin starts immediately without a restart.
Any payload other than 1 is ignored. A non-integer payload publishes status: 17 (parameter error).
Restart the Gateway
Topic: {setup_topic}/restart
Payload: 1
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/setup/restart" -m "1"
The gateway restarts within a few seconds. The broker's LWT publishes offline on the availability topic, then online reappears when the gateway reconnects.
Set Adaptive Data Rate
Topic: {setup_topic}/adr
Payload: 0 (disable) or 1 (enable)
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/setup/adr" -m "1"
ADR lets the LoRaWAN network server automatically adjust the data rate to optimise range and airtime. Enable it when the gateway is stationary; disable it for mobile deployments.
Set Data Rate
Topic: {setup_topic}/dr
Payload: 0–5 (0 = SF12 longest range, 5 = SF7 shortest range / largest payload)
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/setup/dr" -m "3"
Takes effect on the next uplink. Also updates the stored data_rate setting.
Request IP Info
Topic: {setup_topic}/ip
Payload: (any — ignored)
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/setup/ip" -m ""
The gateway publishes the current IP and DHCP state to {status_topic}/json:
{"ip": "192.168.1.131", "dhcp": 0}
Request Network Time
Topic: {setup_topic}/time
Payload: (any — ignored)
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/setup/time" -m ""
The gateway publishes the current network time to {status_topic}/json:
{"tUnix": 1775036333}
tUnix is 0 if the gateway has not yet received time from the LoRaWAN network (time is synchronized during a LinkCheck response).
Request System Info
Topic: {setup_topic}/info
Payload: (any — ignored)
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/setup/info" -m ""
The gateway publishes the full system info JSON to {status_topic}/json — identical to the HTTP /info response. Includes hardware version, firmware version, device EUI, device address (or "0" if not joined), restart counter, and board temperature (HW 1.3).
Use this to check join state when you subscribe after the gateway has already joined and will not see status: 7 again.
Heartbeat
The heartbeat publishes a regular "still alive" message to {status_topic} so you know the gateway is running even when there is no LoRaWAN traffic.
Topic: {status_topic}
Format: 12 - Heartbeat: {count}
The counter starts at 1 and increments with each heartbeat, resetting on restart.
12 - Heartbeat: 14
A parallel JSON message is also published to {status_topic}/json with "status": 12.
Configuration:
| Parameter | Description |
|---|---|
mqtt_hb_interval | Interval in seconds. 0 = disabled. No restart required. |
# Set 60-second heartbeat
curl -X POST http://<gateway-ip>/set \
-H "Content-Type: application/json" \
-d '{"mqtt_hb_interval": 60}'
# Disable heartbeat
curl -X POST http://<gateway-ip>/set \
-H "Content-Type: application/json" \
-d '{"mqtt_hb_interval": 0}'
A heartbeat with "status": 0 in the JSON confirms the gateway is joined and idle. A heartbeat arriving shortly after status: 7 is your confirmation that the join state is stable.
Tamper Input Events
Applies to hardware revision 1.3 only.
The AI-B100 HW 1.3 has an auxiliary digital input (AUX_INPUT). When the input changes state, a tamper event is published to {status_topic}/json.
{
"name": "warehouse-01",
"tamper": 1,
"tamper_edge": "up",
"tUnix": 1775036333
}
| Field | Description |
|---|---|
tamper | Current pin value: 0 or 1 |
tamper_edge | Direction of change: "up" (low→high), "down" (high→low), or "none" |
tUnix | UTC Unix timestamp of the event (0 if time not synchronized) |
Tamper events are published to {status_topic}/json (not a dedicated topic). To distinguish tamper events from LoRaWAN status events, check for the presence of the tamper field — it is only present in tamper payloads.
On HW 1.3, the same event is also sent via the HTTP tamper callback if callback_status_uri is configured.
Lifecycle: Typical MQTT Flow
Gateway Starts — Manual Join
Prerequisite: autojoin_enable = 0
- Gateway connects → publishes
onlineon availability andstatus: 1on status/json. - Your app publishes
1to{setup_topic}/join. - Gateway attempts OTAA. Each failure publishes
status: 4. Success publishesstatus: 7withdev_addrset. status: 7is published once. After this point, the steady-state idle code isstatus: 0.
Gateway Starts — Auto Join
Prerequisite: autojoin_enable = 1
- Gateway connects → publishes
status: 5(auto join starting). No command needed. - Each failed attempt publishes
status: 4. - After
join_retryconsecutive failures,status: 5is published again and a fresh round of retries starts immediately. This repeats indefinitely until the join succeeds. - On success,
status: 7is published once. Idle state after that isstatus: 0.
Normal Operation
Once joined:
- Send result:
status: 9(sent),10(confirmed),11(not confirmed),13(duty cycle blocked). - If blocked by duty cycle: wait
next_upload_msmilliseconds, then publish the uplink again. - Heartbeats carry
status: 0when the gateway is joined and idle.
Session Lost and Rejoin
If the LoRaWAN session is lost (network timeout, frame counter desync):
- Gateway publishes
status: 14on{status_topic}/json. - Your application publishes
1to{setup_topic}/jointo trigger a new join round. - Or, if
autojoin_enable = 1, the gateway rejoins automatically without any command. - On successful rejoin,
status: 7is published once again.
Reference
All Topics
| Topic | Direction | Retained | Trigger / Content |
|---|---|---|---|
{status_topic}/availability | GW → App | Yes | online on connect; offline via LWT on unexpected disconnect |
{status_topic} | GW → App | No | Every LoRaWAN event — plain text {code} - {description} |
{status_topic}/json | GW → App | No | Every LoRaWAN event + IP/time/info responses + tamper events |
{receive_topic}/{port} | GW → App | No | Downlink payload as raw bytes, on the port it was received on |
{receive_topic}/json | GW → App | No | Downlink + linkcheck JSON |
{gps_topic} | GW → App | No | GPS position JSON, published every mqtt_gps_interval seconds |
{send_topic}/{port}[C] | App → GW | No | Uplink payload; append C/c for confirmed; port 0 = LinkCheckReq |
{setup_topic}/join | App → GW | No | Payload 1 triggers OTAA join |
{setup_topic}/restart | App → GW | No | Payload 1 restarts the gateway |
{setup_topic}/adr | App → GW | No | Payload 0 or 1 — disable/enable ADR |
{setup_topic}/dr | App → GW | No | Payload 0–5 — set uplink data rate |
{setup_topic}/ip | App → GW | No | Any payload — triggers IP/DHCP publish to {status_topic}/json |
{setup_topic}/time | App → GW | No | Any payload — triggers tUnix publish to {status_topic}/json |
{setup_topic}/info | App → GW | No | Any payload — triggers full system info publish to {status_topic}/json |
MQTT Parameters
These are set via POST /set. All except mqtt_hb_interval and mqtt_gps_interval require a restart.
| Parameter | Type | Range | Restart |
|---|---|---|---|
mqtt_enable | integer | 0 or 1 | X |
broker_addr | IPv4 | valid IPv4 address | X |
broker_port | integer | 1–65535 | X |
mqtt_user | string | max 16 chars (empty = no auth) | X |
mqtt_password | string | max 16 chars (empty = no auth) | X |
status_topic | string | max 31 chars, supports $name$ | X |
receive_topic | string | max 31 chars, supports $name$ | X |
send_topic | string | max 31 chars, supports $name$ | X |
setup_topic | string | max 31 chars, supports $name$ | X |
gps_topic | string | max 31 chars, supports $name$ | X |
mqtt_hb_interval | integer | 0–65535 seconds (0 = disabled) | |
mqtt_gps_interval | integer | 0–65535 seconds (0 = disabled) |
Status Codes
| Code | Description |
|---|---|
| 0 | Joined and ready — idle state after a successful join |
| 1 | Restarted or retries exhausted — waiting for join command |
| 2 | Send attempted with empty payload |
| 3 | Payload too long for current data rate |
| 4 | Join attempt failed |
| 5 | Auto join in progress (startup or after retry-round exhaustion) |
| 6 | Unknown LoRaWAN error |
| 7 | Joined — published once at the moment of joining |
| 8 | Downlink received |
| 9 | Uplink sent, no downlink |
| 10 | Uplink confirmed (ACK received) |
| 11 | Uplink not confirmed (no ACK) |
| 12 | Heartbeat |
| 13 | Uplink blocked by duty cycle restriction |
| 14 | LoRaWAN session lost |
| 15 | Invalid LoRaWAN port number |
| 16 | Uplink failed |
| 17 | Parameter error — invalid payload on setup command |
| 18 | Command rejected — gateway not joined |
| 19 | Setting saved |
Troubleshooting
Gateway does not appear on the broker — availability topic is empty or stays offline
- Check
mqtt_enable = 1and the broker settings are correct:curl http://<gateway-ip>/get?mqtt_enable curl http://<gateway-ip>/get?broker_addr curl http://<gateway-ip>/get?broker_port - Confirm you restarted after the last settings change.
- Confirm the broker is reachable from the gateway's network. If the broker requires authentication, verify
mqtt_userandmqtt_passwordare set correctly. - Check the serial log (115200 baud) for
[MQTT]error lines at startup.
Topics have the wrong name after changing name
Topic expansion happens once at MQTT task startup. A settings change to name or any topic field takes effect only after a restart:
curl http://<gateway-ip>/restart
Published to {setup_topic}/join but no response on {status_topic}/json
- Confirm the gateway is subscribed to the correct setup topic. Read it back:
curl http://<gateway-ip>/get?setup_topic - Confirm the published topic matches exactly (case-sensitive) including any
$name$expansion. - Confirm the payload is the ASCII string
1, not the integer1or JSON{"value":1}. - Watch the availability topic — if it is
offline, the gateway has disconnected.
status: 7 never received — unsure if gateway is joined
If you subscribe after the gateway has already joined, status: 7 will not be seen again until the next join event. To check the current join state:
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/setup/info" -m ""
The response on {status_topic}/json includes dev_addr — "0" means not joined, an 8-character hex string means joined.
Alternatively, wait for the next heartbeat: a heartbeat with "status": 0 and a non-zero dev_addr confirms the gateway is joined.
Send uplink rejected with status: 18 (not joined)
The gateway must join the LoRaWAN network before sending. Publish to {setup_topic}/join:
mosquitto_pub -h 192.168.1.10 -t "lora/gw01/setup/join" -m "1"
Wait for status: 7 before sending.
Send blocked with status: 13 (duty cycle)
The next_upload_ms field in the status JSON tells you exactly how long to wait:
{"status": 13, "next_upload_ms": 45000, ...}
Wait 45 seconds (in this example) before publishing to the send topic again. Do not repeatedly publish — each rejected attempt still consumes duty cycle budget on some implementations.
GPS topic not publishing
Check the following in order:
mqtt_gps_intervalis non-zero:curl http://<gateway-ip>/get?mqtt_gps_intervalgps_topicis set:curl http://<gateway-ip>/get?gps_topic- A GPS module is connected. Query the HTTP endpoint to confirm:
Ifcurl http://<gateway-ip>/gpsgps_statusis0, no module is detected. If it is1, the module is present but has not yet acquired a fix — wait forgps_status: 2or3.
Binary payload on {receive_topic}/{port} looks garbled
The raw port topic carries raw bytes, not text. If your MQTT client interprets them as UTF-8 text, non-printable bytes will appear corrupted. Use {receive_topic}/json instead — the payload field is either a clean ASCII string or an integer array, both of which are safe to handle in any language.