ROUTING_APP packets display "error_reason: NONE" for successful ACKs #67

Closed
opened 2026-10-05 06:04:43 +02:00 by adam · 1 comment
Owner

Packets on ROUTING_APP (portnum 5) that are successful acknowledgements are rendered in the UI as:

error_reason: NONE

This reads as an error to anyone scanning the packet list or a node's packet detail page, when it is in fact the opposite — it is the implicit ACK. Since ROUTING packets are one of the highest-volume packet types on a busy mesh, the packet list ends up looking like it is full of errors.

Cause

Packet.from_model() in meshview/web.py renders any decoded protobuf via the generic branch:

if payload is None:
    text_payload = "Did not decode"
elif isinstance(payload, Message):
    text_payload = text_format.MessageToString(payload)

For Routing, the error_reason field lives inside a protobuf oneof (variant). Fields inside a oneof have explicit presence, so error_reason = NONE (value 0) is still written to the wire and is still reported as set by WhichOneof(). text_format.MessageToString() therefore prints it verbatim as error_reason: NONE, instead of omitting it the way it would for a normal scalar zero value.

In other words, NONE here is not "no data" — it is the ACK, and the current rendering gives it the exact opposite meaning to the reader.

Evidence

Captured live from MQTT (msh/EU_868/#), 41 ROUTING_APP packets:

error_reason count current render meaning
NONE 19 error_reason: NONE ACK (success)
NO_RESPONSE 22 error_reason: NO_RESPONSE NAK (failure)

All 41 had request_id set, confirming they are responses to a prior packet rather than route-discovery requests.

Sample decoded packet (routing payload 1800 = field 3 varint 0 = error_reason: NONE):

{
  "topic": "msh/EU_868/2/e/MediumFast/!f6fd6c78",
  "packet_id": 2621580621,
  "from": 1849866277,
  "to": 3932882981,
  "request_id": 2320884292,
  "routing_payload_hex": "1800",
  "variant": "error_reason",
  "error_reason": "NONE",
  "rendered_as": "error_reason: NONE"
}

Steps to reproduce

  1. Point Meshview at any reasonably active MQTT broker.
  2. Open the firehose / packet list, or any node page with routing traffic.
  3. Filter to portnum ROUTING_APP.
  4. Observe that successful ACKs are displayed as error_reason: NONE.

Expected behaviour

  • error_reason == NONE should display as ACK.
  • Any other error_reason should display as NAK: <REASON> (e.g. NAK: NO_RESPONSE), so genuine failures remain visible and distinguishable.
  • route_request / route_reply variants of Routing should keep their existing rendering.

Environment

  • Meshview 3.0.7 (31ca098)
  • Python 3.13
  • SQLite backend

Proposed fix

Special-case the error_reason variant of Routing in Packet.from_model() before falling through to the generic MessageToString() branch. Happy to open a PR for this.

Packets on `ROUTING_APP` (portnum 5) that are successful acknowledgements are rendered in the UI as: ``` error_reason: NONE ``` This reads as an error to anyone scanning the packet list or a node's packet detail page, when it is in fact the opposite — it is the implicit ACK. Since ROUTING packets are one of the highest-volume packet types on a busy mesh, the packet list ends up looking like it is full of errors. ## Cause `Packet.from_model()` in `meshview/web.py` renders any decoded protobuf via the generic branch: ```python if payload is None: text_payload = "Did not decode" elif isinstance(payload, Message): text_payload = text_format.MessageToString(payload) ``` For `Routing`, the `error_reason` field lives inside a protobuf `oneof` (`variant`). Fields inside a `oneof` have explicit presence, so `error_reason = NONE` (value `0`) is still written to the wire and is still reported as set by `WhichOneof()`. `text_format.MessageToString()` therefore prints it verbatim as `error_reason: NONE`, instead of omitting it the way it would for a normal scalar zero value. In other words, `NONE` here is not "no data" — it is the ACK, and the current rendering gives it the exact opposite meaning to the reader. ## Evidence Captured live from MQTT (`msh/EU_868/#`), 41 `ROUTING_APP` packets: | `error_reason` | count | current render | meaning | |---|---|---|---| | `NONE` | 19 | `error_reason: NONE` | ACK (success) | | `NO_RESPONSE` | 22 | `error_reason: NO_RESPONSE` | NAK (failure) | All 41 had `request_id` set, confirming they are responses to a prior packet rather than route-discovery requests. Sample decoded packet (routing payload `1800` = field 3 varint 0 = `error_reason: NONE`): ```json { "topic": "msh/EU_868/2/e/MediumFast/!f6fd6c78", "packet_id": 2621580621, "from": 1849866277, "to": 3932882981, "request_id": 2320884292, "routing_payload_hex": "1800", "variant": "error_reason", "error_reason": "NONE", "rendered_as": "error_reason: NONE" } ``` ## Steps to reproduce 1. Point Meshview at any reasonably active MQTT broker. 2. Open the firehose / packet list, or any node page with routing traffic. 3. Filter to portnum `ROUTING_APP`. 4. Observe that successful ACKs are displayed as `error_reason: NONE`. ## Expected behaviour - `error_reason == NONE` should display as `ACK`. - Any other `error_reason` should display as `NAK: <REASON>` (e.g. `NAK: NO_RESPONSE`), so genuine failures remain visible and distinguishable. - `route_request` / `route_reply` variants of `Routing` should keep their existing rendering. ## Environment - Meshview 3.0.7 (`31ca098`) - Python 3.13 - SQLite backend ## Proposed fix Special-case the `error_reason` variant of `Routing` in `Packet.from_model()` before falling through to the generic `MessageToString()` branch. Happy to open a PR for this. <!-- github-sync-id:5351665191 -->
adam closed this issue 2026-10-05 06:04:43 +02:00
Author
Owner

fix added to version 3.0.8,

fix added to version 3.0.8, <!-- github-comment-id:5606736602 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/meshview#67