A node that acts as an MQTT gateway before meshview has a Node row for it is never marked is_mqtt_gateway = True, so it permanently shows MQTT Gateway: No in the UI even though it is a fully working gateway and publishes correct ServiceEnvelopes (with its own gateway_id) to the broker meshview is reading.
Root cause
In meshview/mqtt_store.py the gateway is marked with a one-shot UPDATE guarded by a permanent in-memory cache:
node_id=int(env.gateway_id[1:],16)ifnode_idnotinMQTT_GATEWAY_CACHE:MQTT_GATEWAY_CACHE.add(node_id)# added unconditionallyawaitsession.execute(update(Node).where(Node.node_id==node_id)# UPDATE, not upsert.values(is_mqtt_gateway=True))
If, at the moment the first gateway packet from node_id is processed, no Node row exists yet (its NodeInfo hasn't been received/decoded), the UPDATE ... WHERE node_id = X affects 0 rows. But node_id has already been added to MQTT_GATEWAY_CACHE, so the block never runs again for that node. Later, when the NodeInfo finally creates the Node row, is_mqtt_gateway stays NULL forever.
The startup cache is loaded only from already-True nodes (select(Node.node_id).where(Node.is_mqtt_gateway == True)), so a restart clears the stale cache entry and lets the node get marked on its next gateway packet — which is exactly the observed workaround (see below).
Who hits this
Gateways whose own NodeInfo reaches meshview late or not at all relative to their first relayed packet, e.g.:
Gateways that start relaying traffic immediately on boot (busy mesh) while their own NodeInfo is infrequent.
Nodes with channel encryption enabled for MQTT, where meshview can't decode their NodeInfo quickly to create the row.
In my setup, 3 Ethernet gateways (publishing correctly to a local broker, verified by subscribing) all showed MQTT Gateway: No while a different node showed Yes, purely due to packet/NodeInfo ordering.
Reproduction
Point meshview at a broker.
Have a gateway start publishing relayed packets (gateway_id = itself) before meshview has decoded that node's NodeInfo.
The node never gets is_mqtt_gateway = True, even after its NodeInfo arrives and its Node row exists.
Workaround
Restart meshview (clears the in-memory cache; the node is re-marked on its next gateway packet now that its row exists), or
UPDATE node SET is_mqtt_gateway = 1 WHERE node_id = <id>; directly.
Suggested fix
Make the marking robust to ordering, e.g. one of:
Upsert the node (insert a stub row, or ON CONFLICT DO UPDATE) so the flag is set even if the row doesn't exist yet, or
Only add to MQTT_GATEWAY_CACHE when the UPDATE actually affected ≥1 row (check result.rowcount), so it retries on a later packet once the row exists, or
Set is_mqtt_gateway = True at the same place the Node row is created/updated from NodeInfo, keyed on the gateway-seen set.
The second option is the smallest change and removes the dependency on NodeInfo/gateway-packet ordering.
### Summary
A node that acts as an MQTT gateway **before meshview has a `Node` row for it** is never marked `is_mqtt_gateway = True`, so it permanently shows **MQTT Gateway: No** in the UI even though it is a fully working gateway and publishes correct `ServiceEnvelope`s (with its own `gateway_id`) to the broker meshview is reading.
### Root cause
In `meshview/mqtt_store.py` the gateway is marked with a **one-shot `UPDATE`** guarded by a **permanent in-memory cache**:
```python
node_id = int(env.gateway_id[1:], 16)
if node_id not in MQTT_GATEWAY_CACHE:
MQTT_GATEWAY_CACHE.add(node_id) # added unconditionally
await session.execute(
update(Node).where(Node.node_id == node_id) # UPDATE, not upsert
.values(is_mqtt_gateway=True)
)
```
If, at the moment the first gateway packet from `node_id` is processed, **no `Node` row exists yet** (its `NodeInfo` hasn't been received/decoded), the `UPDATE ... WHERE node_id = X` affects **0 rows**. But `node_id` has already been added to `MQTT_GATEWAY_CACHE`, so the block **never runs again** for that node. Later, when the `NodeInfo` finally creates the `Node` row, `is_mqtt_gateway` stays `NULL` forever.
The startup cache is loaded only from already-`True` nodes (`select(Node.node_id).where(Node.is_mqtt_gateway == True)`), so a restart clears the stale cache entry and lets the node get marked on its next gateway packet — which is exactly the observed workaround (see below).
### Who hits this
Gateways whose **own `NodeInfo` reaches meshview late or not at all relative to their first relayed packet**, e.g.:
- Gateways that start relaying traffic immediately on boot (busy mesh) while their own `NodeInfo` is infrequent.
- Nodes with **channel encryption enabled** for MQTT, where meshview can't decode their `NodeInfo` quickly to create the row.
In my setup, 3 Ethernet gateways (publishing correctly to a local broker, verified by subscribing) all showed **MQTT Gateway: No** while a different node showed **Yes**, purely due to packet/`NodeInfo` ordering.
### Reproduction
1. Point meshview at a broker.
2. Have a gateway start publishing relayed packets (`gateway_id = itself`) **before** meshview has decoded that node's `NodeInfo`.
3. The node never gets `is_mqtt_gateway = True`, even after its `NodeInfo` arrives and its `Node` row exists.
### Workaround
- Restart meshview (clears the in-memory cache; the node is re-marked on its next gateway packet now that its row exists), **or**
- `UPDATE node SET is_mqtt_gateway = 1 WHERE node_id = <id>;` directly.
### Suggested fix
Make the marking robust to ordering, e.g. one of:
- **Upsert** the node (insert a stub row, or `ON CONFLICT DO UPDATE`) so the flag is set even if the row doesn't exist yet, or
- **Only add to `MQTT_GATEWAY_CACHE` when the `UPDATE` actually affected ≥1 row** (check `result.rowcount`), so it retries on a later packet once the row exists, or
- Set `is_mqtt_gateway = True` at the same place the `Node` row is created/updated from `NodeInfo`, keyed on the gateway-seen set.
The second option is the smallest change and removes the dependency on `NodeInfo`/gateway-packet ordering.
<!-- github-sync-id:4634997585 -->
I came across this issue while trying to research why I was persistently seeing "MQTT Gateway: No" on Meshview even though I clearly have a functioning MQTT gateway. In my case, the node was originally deployed without MQTT function and it was later added. It appears the record is similarly not being updated in this scenario, though I lack the time and technical skill to provide much more than my own observations.
I came across this issue while trying to research why I was persistently seeing "MQTT Gateway: No" on Meshview even though I clearly have a functioning MQTT gateway. In my case, the node was originally deployed without MQTT function and it was later added. It appears the record is similarly not being updated in this scenario, though I lack the time and technical skill to provide much more than my own observations.
<!-- github-comment-id:4714408829 -->
Reproduced this today on a brand-new gateway, and I think the fix may not have
landed on develop — apologies if I am looking in the wrong place.
Reproduction, which matches the original report exactly:
A new gateway began publishing at 22:52, forwarding packets it had relayed for other nodes, each with gateway_id = !f6fb8e00.
Its own NodeInfo did not arrive until 23:19, 27 minutes later.
It has shown MQTT Gateway: No ever since, while publishing valid ServiceEnvelopes. Verified at the broker with a QoS 1 MQTT 5 publish
returning PUBACK RC:0, and verified in another meshview instance which
records packet_seen.node_id = 4143681024 for every packet.
The ordering in the report is not an edge case, it is the normal case. A gateway
starts relaying immediately on boot, while NodeInfo defaults to every 3 hours —
the Arizona community's (azmsh.net) recommended settings raise that to 12 hours, which
widens the window considerably.
On develop at time of writing, meshview/mqtt_store.py still has:
None of the three suggested fixes appears present: there is no rowcount check
anywhere in the file, and the only on_conflict uses are for daily_snapshot, packet and packet_seen, not Node. The same block is in the 3.0.8 tag and
in master.
I also checked whether the new ensure_node_exists() on develop closes it
incidentally, since it creates a Node row for an unknown node. It does not: its
only call site is after this block, and it is passed the packet's sender
rather than its gateway, so for a relayed packet it cannot create the row the UPDATE needs.
Worth reopening? Happy to send a PR for the rowcount variant if that is the
option you prefer — it is a two-line change and keeps the cache's benefit:
The documented workaround does work — restarting ingest clears the stale cache
entry and the flag is set on the next relayed packet.
Reproduced this today on a brand-new gateway, and I think the fix may not have
landed on `develop` — apologies if I am looking in the wrong place.
**Reproduction**, which matches the original report exactly:
- A new gateway began publishing at 22:52, forwarding packets it had relayed for
*other* nodes, each with `gateway_id = !f6fb8e00`.
- Its own NodeInfo did not arrive until 23:19, 27 minutes later.
- It has shown `MQTT Gateway: No` ever since, while publishing valid
`ServiceEnvelope`s. Verified at the broker with a QoS 1 MQTT 5 publish
returning `PUBACK RC:0`, and verified in another meshview instance which
records `packet_seen.node_id = 4143681024` for every packet.
The ordering in the report is not an edge case, it is the normal case. A gateway
starts relaying immediately on boot, while NodeInfo defaults to every 3 hours —
the Arizona community's (azmsh.net) recommended settings raise that to 12 hours, which
widens the window considerably.
**On `develop` at time of writing**, `meshview/mqtt_store.py` still has:
```python
if node_id not in MQTT_GATEWAY_CACHE:
MQTT_GATEWAY_CACHE.add(node_id)
await session.execute(
update(Node).where(Node.node_id == node_id).values(is_mqtt_gateway=True)
)
```
None of the three suggested fixes appears present: there is no `rowcount` check
anywhere in the file, and the only `on_conflict` uses are for `daily_snapshot`,
`packet` and `packet_seen`, not `Node`. The same block is in the `3.0.8` tag and
in `master`.
I also checked whether the new `ensure_node_exists()` on `develop` closes it
incidentally, since it creates a `Node` row for an unknown node. It does not: its
only call site is after this block, and it is passed the packet's **sender**
rather than its **gateway**, so for a relayed packet it cannot create the row the
`UPDATE` needs.
Worth reopening? Happy to send a PR for the `rowcount` variant if that is the
option you prefer — it is a two-line change and keeps the cache's benefit:
```python
if node_id not in MQTT_GATEWAY_CACHE:
result = await session.execute(
update(Node).where(Node.node_id == node_id).values(is_mqtt_gateway=True)
)
if result.rowcount:
MQTT_GATEWAY_CACHE.add(node_id)
```
The documented workaround does work — restarting ingest clears the stale cache
entry and the flag is set on the next relayed packet.
<!-- github-comment-id:5853632313 -->
Thanks @JeffreyPeacock, confirmed on my side. develop (at bb8da4c) and master both still add the node to MQTT_GATEWAY_CACHE before the UPDATE and never check whether it matched a row. Neither commit touching mqtt_store.py since this was closed changes that block, and no PR is linked to the close.
@pablorevilla-meshtastic could you reopen this? As the author I can't, since it was closed by a maintainer.
The rowcount variant is my preference too: smallest change, keeps the cache, and the only extra cost is one UPDATE per packet from that gateway until its Node row exists. Please go ahead with the PR.
Thanks @JeffreyPeacock, confirmed on my side. `develop` (at `bb8da4c`) and `master` both still add the node to `MQTT_GATEWAY_CACHE` before the `UPDATE` and never check whether it matched a row. Neither commit touching `mqtt_store.py` since this was closed changes that block, and no PR is linked to the close.
@pablorevilla-meshtastic could you reopen this? As the author I can't, since it was closed by a maintainer.
The `rowcount` variant is my preference too: smallest change, keeps the cache, and the only extra cost is one `UPDATE` per packet from that gateway until its `Node` row exists. Please go ahead with the PR.
<!-- github-comment-id:5853921145 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
A node that acts as an MQTT gateway before meshview has a
Noderow for it is never markedis_mqtt_gateway = True, so it permanently shows MQTT Gateway: No in the UI even though it is a fully working gateway and publishes correctServiceEnvelopes (with its owngateway_id) to the broker meshview is reading.Root cause
In
meshview/mqtt_store.pythe gateway is marked with a one-shotUPDATEguarded by a permanent in-memory cache:If, at the moment the first gateway packet from
node_idis processed, noNoderow exists yet (itsNodeInfohasn't been received/decoded), theUPDATE ... WHERE node_id = Xaffects 0 rows. Butnode_idhas already been added toMQTT_GATEWAY_CACHE, so the block never runs again for that node. Later, when theNodeInfofinally creates theNoderow,is_mqtt_gatewaystaysNULLforever.The startup cache is loaded only from already-
Truenodes (select(Node.node_id).where(Node.is_mqtt_gateway == True)), so a restart clears the stale cache entry and lets the node get marked on its next gateway packet — which is exactly the observed workaround (see below).Who hits this
Gateways whose own
NodeInforeaches meshview late or not at all relative to their first relayed packet, e.g.:NodeInfois infrequent.NodeInfoquickly to create the row.In my setup, 3 Ethernet gateways (publishing correctly to a local broker, verified by subscribing) all showed MQTT Gateway: No while a different node showed Yes, purely due to packet/
NodeInfoordering.Reproduction
gateway_id = itself) before meshview has decoded that node'sNodeInfo.is_mqtt_gateway = True, even after itsNodeInfoarrives and itsNoderow exists.Workaround
UPDATE node SET is_mqtt_gateway = 1 WHERE node_id = <id>;directly.Suggested fix
Make the marking robust to ordering, e.g. one of:
ON CONFLICT DO UPDATE) so the flag is set even if the row doesn't exist yet, orMQTT_GATEWAY_CACHEwhen theUPDATEactually affected ≥1 row (checkresult.rowcount), so it retries on a later packet once the row exists, oris_mqtt_gateway = Trueat the same place theNoderow is created/updated fromNodeInfo, keyed on the gateway-seen set.The second option is the smallest change and removes the dependency on
NodeInfo/gateway-packet ordering.I came across this issue while trying to research why I was persistently seeing "MQTT Gateway: No" on Meshview even though I clearly have a functioning MQTT gateway. In my case, the node was originally deployed without MQTT function and it was later added. It appears the record is similarly not being updated in this scenario, though I lack the time and technical skill to provide much more than my own observations.
I have make this changes into the develop branch, thank you for your report.
Reproduced this today on a brand-new gateway, and I think the fix may not have
landed on
develop— apologies if I am looking in the wrong place.Reproduction, which matches the original report exactly:
other nodes, each with
gateway_id = !f6fb8e00.MQTT Gateway: Noever since, while publishing validServiceEnvelopes. Verified at the broker with a QoS 1 MQTT 5 publishreturning
PUBACK RC:0, and verified in another meshview instance whichrecords
packet_seen.node_id = 4143681024for every packet.The ordering in the report is not an edge case, it is the normal case. A gateway
starts relaying immediately on boot, while NodeInfo defaults to every 3 hours —
the Arizona community's (azmsh.net) recommended settings raise that to 12 hours, which
widens the window considerably.
On
developat time of writing,meshview/mqtt_store.pystill has:None of the three suggested fixes appears present: there is no
rowcountcheckanywhere in the file, and the only
on_conflictuses are fordaily_snapshot,packetandpacket_seen, notNode. The same block is in the3.0.8tag andin
master.I also checked whether the new
ensure_node_exists()ondevelopcloses itincidentally, since it creates a
Noderow for an unknown node. It does not: itsonly call site is after this block, and it is passed the packet's sender
rather than its gateway, so for a relayed packet it cannot create the row the
UPDATEneeds.Worth reopening? Happy to send a PR for the
rowcountvariant if that is theoption you prefer — it is a two-line change and keeps the cache's benefit:
The documented workaround does work — restarting ingest clears the stale cache
entry and the flag is set on the next relayed packet.
Thanks @JeffreyPeacock, confirmed on my side.
develop(atbb8da4c) andmasterboth still add the node toMQTT_GATEWAY_CACHEbefore theUPDATEand never check whether it matched a row. Neither commit touchingmqtt_store.pysince this was closed changes that block, and no PR is linked to the close.@pablorevilla-meshtastic could you reopen this? As the author I can't, since it was closed by a maintainer.
The
rowcountvariant is my preference too: smallest change, keeps the cache, and the only extra cost is oneUPDATEper packet from that gateway until itsNoderow exists. Please go ahead with the PR.