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.

Note: MQTT is configured via the HTTP interface (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

  1. 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.

  2. 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.

  3. MQTT enabled and configured on the gateway with a valid broker IP and port, then restarted. See Setting Up MQTT.

  4. 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.

Your App Broker AI-B100 | | | |-- subscribe status -->>| | | |<<-- publish join ---| |<<-- receive status:7 --| | | | | |-- publish send/1 -->> | | | |-- deliver send/1 -->| | |<<-- publish status--| |<<-- receive status:9 --| |

Neither side talks directly to the other — everything flows through the broker. This means:

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:

TopicDirectionFormat
{status_topic}/availabilityGateway → AppPlain text: online or offline
{status_topic}Gateway → AppPlain text: {code} - {description}
{status_topic}/jsonGateway → AppJSON object
{receive_topic}/{port}Gateway → AppRaw binary bytes
{receive_topic}/jsonGateway → AppJSON object
{gps_topic}Gateway → AppJSON object
{send_topic}/{port}App → GatewayRaw bytes (the uplink payload)
{send_topic}/{port}CApp → GatewayRaw bytes — confirmed uplink
{setup_topic}/joinApp → Gateway1
{setup_topic}/restartApp → Gateway1
{setup_topic}/adrApp → Gateway0 or 1
{setup_topic}/drApp → Gateway05
{setup_topic}/ipApp → Gateway(any payload)
{setup_topic}/timeApp → Gateway(any payload)
{setup_topic}/infoApp → Gateway(any payload)

Plain Text and JSON in Parallel

Every LoRaWAN status event is published twice:

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 storedTopic used at runtime
lora/$name$/statuslora/warehouse-01/status
lora/$name$/rxlora/warehouse-01/rx
Important: Topic expansion happens once when the MQTT task starts. Changing the 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:

CodeEventWhat to do
0Joined and ready (idle)No action needed
1Gateway started or all join retries exhausted — waiting for joinPublish 1 to {setup_topic}/join, or enable autojoin_enable
2Send attempted but payload was emptyCheck your publish payload
3Payload too long for current data rateReduce payload or increase data rate
4Join attempt failedRetry join or wait for automatic retry
5Auto join in progress (startup or after retry-round exhaustion)Wait for status 7
6Unknown LoRaWAN errorCheck credentials and coverage
7Joined — published once at join timeReady to send
8Downlink receivedCheck {receive_topic}/json
9Uplink sent, no downlinkNormal — unconfirmed send succeeded
10Uplink confirmed by network (ACK)Normal — confirmed send acknowledged
11Uplink not confirmed (no ACK)Retry if critical
12HeartbeatNo action needed
13Duty cycle active — uplink blockedWait next_upload_ms ms, then retry
14LoRaWAN session lostPublish 1 to {setup_topic}/join
15Invalid LoRaWAN port numberUse FPort 1–223
16Uplink failed (radio error)Retry
17Parameter validation errorCheck payload sent to {setup_topic}
18Command rejected — not joinedJoin first, then retry
19Setting savedNo 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):

ParameterDefault
status_topiclora/$name$/status
receive_topiclora/$name$/receive
send_topiclora/$name$/send
setup_topiclora/$name$/setup
gps_topiclora/$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

PayloadWhen
onlinePublished on every successful broker connection
offlineLast 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:

FieldDescription
nameGateway name — use to identify the source when multiple gateways publish to the same broker
statusStatus code — see Status Codes at a Glance
dev_addr"0" = not joined; 8-char hex = joined device address
maxUpMax payload bytes at the current data rate — check before sending
tUnixUTC Unix timestamp in seconds (0 if not yet synchronized)
next_upload_ms0 = 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.

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.

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:

FieldDescription
fcntDownDownlink frame counter — use to detect missed messages
rssiSignal strength in dBm
snrSignal-to-noise ratio in dB
marginLink margin from linkcheck — only present in linkcheck responses
gwCountGateways that heard the uplink — only present in linkcheck responses
length0 = no payload (linkcheck only); > 0 = byte count of payload
portLoRaWAN FPort — only present if length > 0
payloadPrintable 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.

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.

Important: Subtopic 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
}
FieldDescription
marginLink margin in dB as reported by the network server — higher is better
gwCountNumber 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:

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: 05 (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:

ParameterDescription
mqtt_hb_intervalInterval 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
}
FieldDescription
tamperCurrent pin value: 0 or 1
tamper_edgeDirection of change: "up" (low→high), "down" (high→low), or "none"
tUnixUTC 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

sequenceDiagram participant App as Your Application participant Broker as MQTT Broker participant GW as AI-B100 GW->>Broker: publish availability: online (retained) GW->>Broker: publish status/json: {status:1} App->>Broker: subscribe status/json Broker->>App: deliver status/json: {status:1, dev_addr:"0"} App->>Broker: publish setup/join: "1" Broker->>GW: deliver setup/join GW->>Broker: publish status/json: {status:7, dev_addr:"26011BDA"} Broker->>App: deliver status/json: {status:7} Note over App,GW: status:7 published once — gateway is now joined Note over App,GW: subsequent idle events carry status:0
  1. Gateway connects → publishes online on availability and status: 1 on status/json.
  2. Your app publishes 1 to {setup_topic}/join.
  3. Gateway attempts OTAA. Each failure publishes status: 4. Success publishes status: 7 with dev_addr set.
  4. status: 7 is published once. After this point, the steady-state idle code is status: 0.

Gateway Starts — Auto Join

Prerequisite: autojoin_enable = 1

  1. Gateway connects → publishes status: 5 (auto join starting). No command needed.
  2. Each failed attempt publishes status: 4.
  3. After join_retry consecutive failures, status: 5 is published again and a fresh round of retries starts immediately. This repeats indefinitely until the join succeeds.
  4. On success, status: 7 is published once. Idle state after that is status: 0.

Normal Operation

Once joined:

sequenceDiagram participant App as Your Application participant Broker as MQTT Broker participant GW as AI-B100 App->>Broker: publish send/1: "Hello" Broker->>GW: deliver send/1 GW->>Broker: publish status/json: {status:9} or {status:10} Broker->>App: deliver status/json opt Downlink received GW->>Broker: publish rx/json: {port, payload, ...} Broker->>App: deliver rx/json end loop Every mqtt_hb_interval seconds GW->>Broker: publish status: "12 - Heartbeat: N" GW->>Broker: publish status/json: {status:0, ...} end

Session Lost and Rejoin

If the LoRaWAN session is lost (network timeout, frame counter desync):

  1. Gateway publishes status: 14 on {status_topic}/json.
  2. Your application publishes 1 to {setup_topic}/join to trigger a new join round.
  3. Or, if autojoin_enable = 1, the gateway rejoins automatically without any command.
  4. On successful rejoin, status: 7 is published once again.

Reference

All Topics

TopicDirectionRetainedTrigger / Content
{status_topic}/availabilityGW → AppYesonline on connect; offline via LWT on unexpected disconnect
{status_topic}GW → AppNoEvery LoRaWAN event — plain text {code} - {description}
{status_topic}/jsonGW → AppNoEvery LoRaWAN event + IP/time/info responses + tamper events
{receive_topic}/{port}GW → AppNoDownlink payload as raw bytes, on the port it was received on
{receive_topic}/jsonGW → AppNoDownlink + linkcheck JSON
{gps_topic}GW → AppNoGPS position JSON, published every mqtt_gps_interval seconds
{send_topic}/{port}[C]App → GWNoUplink payload; append C/c for confirmed; port 0 = LinkCheckReq
{setup_topic}/joinApp → GWNoPayload 1 triggers OTAA join
{setup_topic}/restartApp → GWNoPayload 1 restarts the gateway
{setup_topic}/adrApp → GWNoPayload 0 or 1 — disable/enable ADR
{setup_topic}/drApp → GWNoPayload 05 — set uplink data rate
{setup_topic}/ipApp → GWNoAny payload — triggers IP/DHCP publish to {status_topic}/json
{setup_topic}/timeApp → GWNoAny payload — triggers tUnix publish to {status_topic}/json
{setup_topic}/infoApp → GWNoAny 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.

ParameterTypeRangeRestart
mqtt_enableinteger0 or 1X
broker_addrIPv4valid IPv4 addressX
broker_portinteger1–65535X
mqtt_userstringmax 16 chars (empty = no auth)X
mqtt_passwordstringmax 16 chars (empty = no auth)X
status_topicstringmax 31 chars, supports $name$X
receive_topicstringmax 31 chars, supports $name$X
send_topicstringmax 31 chars, supports $name$X
setup_topicstringmax 31 chars, supports $name$X
gps_topicstringmax 31 chars, supports $name$X
mqtt_hb_intervalinteger0–65535 seconds (0 = disabled)
mqtt_gps_intervalinteger0–65535 seconds (0 = disabled)

Status Codes

CodeDescription
0Joined and ready — idle state after a successful join
1Restarted or retries exhausted — waiting for join command
2Send attempted with empty payload
3Payload too long for current data rate
4Join attempt failed
5Auto join in progress (startup or after retry-round exhaustion)
6Unknown LoRaWAN error
7Joined — published once at the moment of joining
8Downlink received
9Uplink sent, no downlink
10Uplink confirmed (ACK received)
11Uplink not confirmed (no ACK)
12Heartbeat
13Uplink blocked by duty cycle restriction
14LoRaWAN session lost
15Invalid LoRaWAN port number
16Uplink failed
17Parameter error — invalid payload on setup command
18Command rejected — gateway not joined
19Setting saved

Troubleshooting

Gateway does not appear on the broker — availability topic is empty or stays offline

  1. Check mqtt_enable = 1 and 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
  2. Confirm you restarted after the last settings change.
  3. Confirm the broker is reachable from the gateway's network. If the broker requires authentication, verify mqtt_user and mqtt_password are set correctly.
  4. 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


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:

  1. mqtt_gps_interval is non-zero:
    curl http://<gateway-ip>/get?mqtt_gps_interval
  2. gps_topic is set:
    curl http://<gateway-ip>/get?gps_topic
  3. A GPS module is connected. Query the HTTP endpoint to confirm:
    curl http://<gateway-ip>/gps
    If gps_status is 0, no module is detected. If it is 1, the module is present but has not yet acquired a fix — wait for gps_status: 2 or 3.

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.