fix(packets): informativer Payload + eigene Telemetrie unterdrücken (fixes #3)

- bot.py: vollständige Payload-Daten für Position, Telemetry (Device+Env),
  NodeInfo (hw_model), Routing (error), Traceroute (hops), Neighborinfo (count)
- packets.js: fmtPayload() zeigt alle Felder; TELEMETRY_APP vom eigenen Node
  (my_node_id / short_name FTLW) wird unterdrückt und nicht gezählt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ppfeiffer 2026-02-19 17:20:35 +01:00
parent 407addc919
commit cc392a125a
4 changed files with 98 additions and 11 deletions

View file

@ -1,5 +1,21 @@
# Changelog
## [0.8.6] - 2026-02-19
### Added
- **Paket-Log informativer Payload** (fixes #3): `bot.py` speichert nun vollständige
Paketdaten je Typ:
- `POSITION_APP`: lat/lon + Höhe, Geschwindigkeit, Satelliten
- `TELEMETRY_APP`: Akku, Spannung, Kanalauslastung, TX-Auslastung, Temperatur,
Luftfeuchtigkeit, Luftdruck (Environment Metrics)
- `NODEINFO_APP`: long_name, short_name + Hardware-Modell
- `ROUTING_APP`: Fehlercode (errorReason)
- `TRACEROUTE_APP`: Hop-Anzahl
- `NEIGHBORINFO_APP`: Anzahl Nachbarn
- **Telemetrie-Unterdrückung eigener Node** (fixes #3): Telemetriepakete vom eigenen
Node (per `my_node_id` oder short_name `FTLW`) werden im Paket-Log nicht angezeigt
und gehen nicht in die Zählung ein.
## [0.8.5] - 2026-02-19
### Fixed

View file

@ -1,4 +1,4 @@
version: "0.8.5"
version: "0.8.6"
bot:
name: "MeshDD-Bot"

View file

@ -256,13 +256,43 @@ class MeshBot:
payload_summary = {"text": decoded.get("text", "")}
elif portnum == "POSITION_APP":
pos = decoded.get("position", {})
payload_summary = {"lat": pos.get("latitude"), "lon": pos.get("longitude")}
payload_summary = {
"lat": pos.get("latitude"),
"lon": pos.get("longitude"),
"alt": pos.get("altitude"),
"speed": pos.get("groundSpeed"),
"sats": pos.get("satsInView"),
}
elif portnum == "TELEMETRY_APP":
dm = decoded.get("telemetry", {}).get("deviceMetrics", {})
payload_summary = {"battery": dm.get("batteryLevel"), "voltage": dm.get("voltage")}
telemetry = decoded.get("telemetry", {})
dm = telemetry.get("deviceMetrics", {})
em = telemetry.get("environmentMetrics", {})
payload_summary = {
"battery": dm.get("batteryLevel"),
"voltage": dm.get("voltage"),
"ch_util": dm.get("channelUtilization"),
"air_util": dm.get("airUtilTx"),
"temp": em.get("temperature"),
"humidity": em.get("relativeHumidity"),
"pressure": em.get("barometricPressure"),
}
elif portnum == "NODEINFO_APP":
u = decoded.get("user", {})
payload_summary = {"long_name": u.get("longName"), "short_name": u.get("shortName")}
payload_summary = {
"long_name": u.get("longName"),
"short_name": u.get("shortName"),
"hw_model": u.get("hwModel"),
}
elif portnum == "ROUTING_APP":
routing = decoded.get("routing", {})
payload_summary = {"error": routing.get("errorReason")}
elif portnum == "TRACEROUTE_APP":
route = decoded.get("traceroute", {})
snodes = route.get("route", [])
payload_summary = {"hops": len(snodes), "route": snodes}
elif portnum == "NEIGHBORINFO_APP":
ni = decoded.get("neighborinfo", {})
payload_summary = {"count": len(ni.get("neighbors", []))}
pkt_record = await self.db.insert_packet(
str(from_id), str(to_id), portnum, channel,

View file

@ -6,6 +6,7 @@ const UNKNOWN_TYPE = '__unknown__';
let ws = null;
let nodes = {}; // node_id -> {long_name, short_name, ...}
let channels = {}; // ch_index -> channel name string
let myNodeId = null;
let paused = false;
let activeFilter = 'all';
let pendingRows = [];
@ -93,21 +94,56 @@ function portnumBadge(portnum) {
return `<span class="badge bg-${cfg.color} pkt-type-badge ${txtCls}">${escapeHtml(cfg.label)}</span>`;
}
function isSuppressed(pkt) {
if (pkt.portnum !== 'TELEMETRY_APP') return false;
if (myNodeId && pkt.from_id === myNodeId) return true;
const n = nodes[pkt.from_id];
return !!(n && n.short_name === 'FTLW');
}
function fmtPayload(portnum, payloadStr) {
let p = {};
try { p = JSON.parse(payloadStr || '{}'); } catch { return ''; }
if (portnum === 'TEXT_MESSAGE_APP' && p.text)
return `<span class="text-body">${escapeHtml(p.text)}</span>`;
if (portnum === 'POSITION_APP' && p.lat != null)
return `<span class="text-body-secondary">${p.lat?.toFixed(5)}, ${p.lon?.toFixed(5)}</span>`;
if (portnum === 'POSITION_APP' && p.lat != null) {
const parts = [`${p.lat.toFixed(5)}, ${p.lon.toFixed(5)}`];
if (p.alt != null && p.alt !== 0) parts.push(`${p.alt} m`);
if (p.speed != null && p.speed !== 0) parts.push(`${p.speed} km/h`);
if (p.sats != null) parts.push(`${p.sats} Sat`);
return `<span class="text-body-secondary">${parts.join(' · ')}</span>`;
}
if (portnum === 'TELEMETRY_APP') {
const parts = [];
if (p.battery != null) parts.push(`🔋 ${p.battery}%`);
if (p.voltage != null) parts.push(`${p.voltage?.toFixed(2)} V`);
if (p.voltage != null) parts.push(`${p.voltage.toFixed(2)} V`);
if (p.ch_util != null) parts.push(`CH ${p.ch_util.toFixed(1)}%`);
if (p.air_util != null) parts.push(`TX ${p.air_util.toFixed(1)}%`);
if (p.temp != null) parts.push(`🌡 ${p.temp.toFixed(1)} °C`);
if (p.humidity != null) parts.push(`💧 ${p.humidity.toFixed(0)}%`);
if (p.pressure != null) parts.push(`${p.pressure.toFixed(0)} hPa`);
return parts.length ? `<span class="text-body-secondary">${parts.join(' · ')}</span>` : '';
}
if (portnum === 'NODEINFO_APP' && (p.long_name || p.short_name))
return `<span class="text-body-secondary">${escapeHtml(p.long_name || '')}${p.short_name ? ` [${escapeHtml(p.short_name)}]` : ''}</span>`;
if (portnum === 'NODEINFO_APP' && (p.long_name || p.short_name)) {
let s = escapeHtml(p.long_name || '');
if (p.short_name) s += ` [${escapeHtml(p.short_name)}]`;
if (p.hw_model) s += ` <span class="opacity-50">${escapeHtml(p.hw_model)}</span>`;
return `<span class="text-body-secondary">${s}</span>`;
}
if (portnum === 'ROUTING_APP' && p.error && p.error !== 'NONE')
return `<span class="text-danger">${escapeHtml(p.error)}</span>`;
if (portnum === 'TRACEROUTE_APP' && p.hops != null)
return `<span class="text-body-secondary">${p.hops} Hop${p.hops !== 1 ? 's' : ''}</span>`;
if (portnum === 'NEIGHBORINFO_APP' && p.count != null)
return `<span class="text-body-secondary">${p.count} Nachbar${p.count !== 1 ? 'n' : ''}</span>`;
return '';
}
@ -187,6 +223,7 @@ function buildRow(pkt) {
}
function addRow(pkt, prepend = true) {
if (isSuppressed(pkt)) return;
knownTypes.add(typeKey(pkt.portnum));
const row = buildRow(pkt);
if (prepend) {
@ -249,9 +286,13 @@ function handleMsg(msg) {
case 'channels':
channels = msg.data || {};
break;
case 'my_node_id':
myNodeId = msg.data;
break;
case 'initial_packets':
pktBody.innerHTML = '';
(msg.data || []).forEach(p => {
if (isSuppressed(p)) return;
knownTypes.add(typeKey(p.portnum));
pktBody.appendChild(buildRow(p));
});