███╗ ███╗███████╗███████╗██╗ ██╗ ██████╗ ██████╗ ███╗ ███╗
████╗ ████║██╔════╝██╔════╝██║ ██║██╔════╝██╔═══██╗████╗ ████║
██╔████╔██║█████╗ ███████╗███████║██║ ██║ ██║██╔████╔██║
██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║██║ ██║ ██║██║╚██╔╝██║
██║ ╚═╝ ██║███████╗███████║██║ ██║╚██████╗╚██████╔╝██║ ╚═╝ ██║
╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
██╗ ██╗███████╗██████╗ ██████╗ ███████╗███████╗██╗ ██╗
██║ ██║██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝██║ ██╔╝
██║ █╗ ██║█████╗ ██████╔╝██║ ██║█████╗ ███████╗█████╔╝
██║███╗██║██╔══╝ ██╔══██╗██║ ██║██╔══╝ ╚════██║██╔═██╗
╚███╔███╔╝███████╗██████╔╝██████╔╝███████╗███████║██║ ██╗
╚══╝╚══╝ ╚══════╝╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝
A Blazor Server web application for communicating with a MeshCom 4.0 node via UDP (EXTUDP JSON protocol).
Built with .NET 10 and Blazor Interactive Server.
MeshCom Firmware: Compatible with icssw-org/MeshCom-Firmware v4.35+
Hardware IDs 1–53 supported (TLORA,T-BEAM,T-ECHO,T-DECK,T-DECK-PLUS,T-DECK-PRO,T-ETH-ELITE,HELTEC-V1–V4,RAK4631,EBYTE-E22,T5-EPAPER, …)
💾 Ready-to-run binaries (Windows & Linux) – no build required:
👉 Download latest release
☕ Do you like my work? Then buy me a coffee! ☕
MeshCom always reminds me a little of the good old Packet Radio days – digital text communication over radio, simple and direct.
However, I could not find any suitable software that provides a web server interface for MeshCom accessible from any device (PC, tablet, smartphone) within the local network. That is why I created this MeshCom WebDesk.
The application runs on Windows or Linux and makes a full web client for MeshCom available via a simple URL – no installation required on the end device, everything runs directly in the browser.
- Multi-tab conversations – each partner (callsign, group, broadcast) gets its own tab
- Broadcast tab "All" for
*/CQCQCQmessages - Direct messages – each callsign gets its own tab automatically
- Group messages – group destinations appear as
#<group>tabs with optional whitelist filter - Group Labels – configurable display names for group numbers (e.g.
#262→DL); short label in the tab, full name as tooltip; pre-filled with the official MeshCom GRC group list - Smart routing: broadcast replies from a known callsign appear in their direct tab
- Auto-scroll to the latest message when a tab is opened or a new message arrives
- Unread badge – inactive tabs show a yellow counter badge for new messages
- ACK delivery indicator on every outgoing message:
⏳grey – waiting for node echo (message queued)✓blue – node has transmitted over LoRa (sequence number assigned)✓✓green – recipient confirmed delivery (LoRa ACK received)☁️– sent to group or broadcast (no ACK expected)☁️✓– delivered via gateway (Gateway ACK received)
- Clickable callsigns in the monitor – click any sender or recipient to open a chat tab instantly; hover over a callsign to reveal a small
@button – clicking it inserts@CALLSIGNat the current cursor position in the message input field (useful for addressing someone in a group or broadcast message without switching tabs) @-Mention in chat messages – the same@button appears next to every incoming callsign in the group/broadcast message list; clicking it inserts@CALLSIGNat the cursor position in the send bar- QRZ.com tooltips – when enabled, hovering over any callsign (tab buttons, chat messages, monitor From/To) shows the operator's first name and home QTH (e.g.
Chat mit DH1FR-2 öffnen · Max, Berlin); concurrent lookups for the same callsign share a single HTTP request (in-flight deduplication) - Audio notification 🔔 when a new direct message to your own callsign arrives (Web Audio API, no audio file required); mute toggle in the status bar
- 🔊 Voice announcements – incoming direct messages are read aloud using the browser's built-in Web Speech API (no plugin or audio file required); toggle with the 🔊/🔇 button in the status bar; state is saved in
localStorageand restored on reload; language passed to the speech synthesiser matches the current UI language - ⚡ Quick Texts – configurable one-click text buttons in the send bar; clicking a button loads the predefined text into the input field for review before sending; supports all
{variable}placeholders ({mycall},{mylocator},{callsign},{locator},{rssi},{time},{date},{telemetry}, …); buttons can be reordered by drag & drop in the flyout; new order is saved immediately; configured in Settings → ⚡ Quick Texts - Variable expansion in the send bar – type any
{variable}directly in the message input field (e.g.{telemetry},{mycall},{date}); press Tab to expand as a live preview; variables are always expanded automatically on send - Browser spell-check – the message input field has
spellcheck="true"; the browser's built-in spell checker underlines misspelled words when spell-checking is enabled in the browser settings - Draggable tabs – chat tabs can be reordered by drag & drop; order is saved in
localStorageand restored on every visit - Timestamps – time is always shown as
HH:mm:ss; for messages not from today a compact date (dd.MM.yy) is shown below the time without increasing row height - 🔎 QSO dialog – every direct-chat tab shows a 🔎 button once the MySQL database is active; opens a four-tab modal with KI-Zusammenfassung (AI summary), Verlauf (history), Suche (text search) and KI-Suche (AI search); History and Search work without an AI API key – MySQL only required; KI-Suche supports an optional „Search all direct QSOs" mode to search across all 1:1 conversations at once
- 📋 Recent QSO partners – a 📋 button next to the + tab button opens a flyout listing the most recently contacted callsigns (sorted by last contact time); clicking a row opens the chat tab directly; populated from the MySQL database
- 📤 Export / 📥 Import of Quick Texts – export the quick-text list as
MeshComWebDesk-quick-texts.json; import to replace the list; filename editable before export; share presets with other operators - 📋 Copy message to clipboard – hovering over a message in a direct chat tab reveals a 📋 button; clicking it copies the message text to the clipboard (JS Clipboard API)
- {last-qso} variable – expands to the date and time of the previous direct QSO with the current callsign (the contact that triggered the expansion is excluded);
kein QSO/no QSOif no prior QSO exists; database-first with in-memory fallback; available in Auto-Reply, Bot commands, Beacon and Quick Texts
- Live table of all heard stations with last message, timestamp and message count
- GPS position parsed from EXTUDP position packets (
lat_dir/long_dirAPRS format) - QTH Locator – Maidenhead locator (e.g.
JN48qn) calculated from GPS coordinates; shown below the GPS position in the MH table and in the station card popup; also available as{locator}placeholder in Auto-Reply, Bot commands and Quick Texts - Station card popup – hovering (desktop) or tapping (mobile) a callsign shows a rich card with QRZ name/QTH, RSSI/SNR, battery, distance, QTH locator, GPS coordinates (as OSM link) and firmware; buttons: 💬 Open Chat and 🔗 aprs.fi
- Distance calculation (Haversine) from own position to each heard station
- Battery level 🔋 column parsed from
battfield in position/telemetry packets, colour-coded (🟢 >60% / 🟡 >30% / 🔴 ≤30%) - Search / filter 🔍 – search field in the header; type callsign, message text or QRZ name; results debounced (250 ms); Enter applies immediately; clearing the field resets instantly
- Firmware tooltip – hover the callsign to see firmware version, hardware ID and first-heard time
- QRZ.com callsign data – when the QRZ.com integration is enabled, a dedicated Name / Location column shows each operator's first name and home QTH; the same data also appears as a hover tooltip on every callsign
- RSSI / SNR signal quality with colour coding (green / yellow / red)
- Altitude correctly converted from APRS feet to metres
- 🗺️ OpenStreetMap link per station
- Own position extracted automatically from the node's
type:"pos"UDP beacon - Browser GPS button to use device geolocation as own position
- Click 💬 to open a chat tab with any station
- Structured display with type badge (
MSG/POS/TEL/ACK/SYS), direction (RX/TX), routing and signal - Full relay path shown inline for relayed messages:
OE1XAR-62 ⟶ DL0VBK-12 ⟶ DB0KH-11 → all - Telemetry rows (
type:"tele") display temperature 🌡️, humidity 💧, pressure 🧭 and battery 🔋 - Colour-coded rows: green for TX, cyan for position beacons, purple for telemetry, gold for ACKs
- UDP registration packet (
{"type":"info",...}) sent on startup is shown as aSYSTX entry - Newest entry always at the top; configurable history limit (
MonitorMaxMessages) - Resizable: drag the divider bar between chat and monitor to adjust the split – last position is saved in
localStorageand restored on the next visit - Collapsible on mobile (toggle button)
- Live filter 🔍 in the monitor title bar – type any callsign or text fragment to instantly show only matching rows; press Enter to apply immediately; the entry counter switches to
X / Y Entrieswhile a filter is active; clear with the × button or delete all text; filter results are cached and only recomputed on change (debounced 250 ms)
- Three-state UDP indicator: 🔴 No socket (bind failed) · 🟡 Waiting for signal (socket open, no packet yet) · 🟢 Receiving (at least one packet received) – semantically correct for connectionless UDP
- Last RX timestamp, sender callsign, RSSI / SNR with colour coding
- TX counter, own callsign, device IP:Port
- Own GPS position with source label (Node / Browser GPS)
- 🔔 / 🔕 Sound notification toggle
- Incoming messages are deduplicated using the
msg_idfield (unique hex ID from the node) - Fallback chain:
msg_id→{NNN}sequence number → message text - Duplicate suppression window: 10 minutes (rolling cache, auto-pruned)
- Chat tabs, MH list, monitor history and own GPS position are saved to disk on shutdown
- State is restored automatically on startup – no waiting for the first position beacon
- Auto-save every 5 minutes; data stored in
DataPath(configurable)
- ASCII banner with version number printed to the console/terminal on every start
- Browser auto-open: when launched directly as an executable the default browser opens automatically at
http://localhost:5162 - Skipped when running in Docker, as a Windows service, or under systemd
- Portable data paths: all data (
data/), logs (logs/) and keys (data/keys/) are stored next to the executable by default – no hard-codedC:\Temppaths
- Displays assembly version (e.g.
v1.4.1), build timestamp and links - Version is also shown in the navigation bar next to the app title
- Author contact: dh1fr@darc.de
- Web-based configuration editor at
/settings– edit all settings directly in the browser - Changes are written to
appsettings.override.jsoninDataPath(Docker-safe read-only mount supported) - Most settings apply immediately without restart
- Settings that still require a restart: Listen-IP / Listen-Port (socket binding) and Log-Path / Log-Retention (Serilog)
- Collapsible sections – all 14 setting sections can be individually expanded/collapsed; all start collapsed so the page is compact by default; state is saved in
localStorageand restored on every visit - Encrypted sensitive fields –
MySqlConnectionString,InfluxToken,Qrz.PasswordandTelemetryApiKeyare encrypted with the ASP.NET Core Data Protection API before being written toappsettings.override.json(prefixdp:); existing plain-text values continue to work and are encrypted on the next save
- Full bilingual interface: Deutsch 🇩🇪 and English 🇬🇧
- Language is selected in Settings → Language and persisted in
appsettings.override.json - Switching applies instantly across all pages without any page reload or restart
- Periodic beacon – sends a configurable text to a configurable group at a fixed interval
- Interval is configurable in whole hours (minimum 1 h); first transmission after one full interval (no send on every restart)
- Enabled / disabled via
BeaconEnabledflag – applies live without restart - Supported placeholders in
BeaconText:{version},{mycall},{mylocator},{date},{time},{telemetry}– shown as inline hint in Settings - Status indicator in the status bar: pulsing
●dot with next scheduled send time; turns yellow when < 10 min away - Beacon appears in the monitor feed and in the corresponding group chat tab
- "Send Beacon Now" test button in Settings – sends the beacon immediately without waiting for the interval
- Sends a configurable reply text automatically when a brand-new direct chat tab is opened by an incoming message (first contact from a callsign)
- Enabled / disabled via
AutoReplyEnabled– applies live without restart - Supported placeholders in AutoReplyText: {mycall}, {mylocator}, {callsign}, {locator}, {dest-name}, {dest-loc}, {rssi}, {snr}, {hw}, {route}, {hops}, {srctype}, {srctype-label}, {date}, {time}, {version}, {telemetry}, {last-qso}, {my-tx-power}, {my-eirp}, {my-antenna}, {my-antenna-type}, {my-antenna-height}, {my-freq}
Example:MeshCom WebDesk V{version} – QTH: {mylocator}→MeshCom WebDesk V1.8.0 – QTH: JN48qn - Test button in Settings – send the auto-reply text immediately to any callsign without waiting for an incoming message
-
Command-based auto-responses for incoming direct messages
-
Trigger: any direct message starting with
--(two hyphens) or—(em dash, as typed by many MeshCom clients and mobile keyboards) immediately followed by a letter – decoration strings like---===are intentionally ignored -
Built-in commands:
Command Response --helpList of all registered commands --versionMeshcomWebDesk vX.Y.Z--timeCurrent date and time --mhCount and callsigns of recently heard stations --pingPong! 👋 <callsign> | RSSI: -87 dBm, SNR: 6.5 dB | Route (2 hops): OE1XAR-62,DB0TAW-13 | Received: 14:32:07--echo <text>Echoes <text>back to the sender -
Bare
pingkeyword: a direct message containing onlyping(case-insensitive, with optional surrounding whitespace) is treated identically to--ping -
User-defined commands fully configurable in Settings → 🤖 Bot – no code changes required:
Name– command name without--(e.g.info)- Response – reply text; supports all {variable} placeholders ({mycall}, {mylocator}, {callsign}, {locator}, {version}, {rssi}, {snr}, {hw}, {route}, {hops}, {srctype}, {srctype-label}, {date}, {time}, {telemetry}, {last-qso}, {my-tx-power}, {my-eirp}, {my-antenna}, {my-antenna-type}, {my-antenna-height}, {my-freq})
Description– optional short text shown in--helpoutput
-
Test button in Settings – enter any command (e.g.
--ping) and an optional sender callsign; the bot executes the command locally (dry-run, no UDP send) and shows the exact reply including all expanded{variable}placeholders -
📤 Export / 📥 Import – export all user-defined bot commands as
MeshComWebDesk-bot-commands.json(browser download); import a previously exported or hand-edited file to replace the current list; filename editable before export -
Developer extension: implement
IBotCommandand register viaservices.AddSingleton<IBotCommand, MyCommand>()inProgram.cs -
Auto-split: replies longer than 149 characters are automatically split into consecutive packets (2 s pause between parts) – same strategy as multi-bucket telemetry
-
Enabled / disabled via
BotEnabled– applies live without restart
- Configurable callsign list – specify any number of callsigns to watch
- Flexible matching: entry without SSID (e.g.
DH1FR) matches all SSIDs (DH1FR,DH1FR-1,DH1FR-11, …); entry with SSID (e.g.DH1FR-1) matches only that exact callsign - Alert tone 🔔 (ascending three-tone beep, distinct from the normal message beep) when a watched callsign is heard
- Toast notification in the top-right corner showing the callsign, packet type badge (
MSG/POS/TEL/ACK) and relative age; configurable auto-dismiss (default 5 min, adjustable in Settings); multiple hits are stacked in the same toast; manual close button ✕ - Per-type filter – independently enable/disable alerts for: chat messages (MSG), position beacons (POS), telemetry (TEL, default off to avoid noise from periodic data), and ACKs (ACK, default off)
- Respects the global 🔕 mute toggle in the status bar – no sound when muted
- Configured in Settings → 📻 Watchlist; changes apply live without restart
- Incoming group messages are scanned for CQ calls (case-insensitive regex; no false positives on words like "FREQUENCY")
- Own callsign is suppressed; only groups matching the active group filter are evaluated
- Toast notification (yellow, top-right) showing callsign, group and relative age; auto-dismissed after 60 seconds; manual close ✕
- CQ Beep – Morse pattern CQ CQ in CW style (700 Hz, 80 ms unit, correct dit/dah timing with inter-element and inter-character pauses, word pause between the two CQ sequences); only when sound is enabled
- Voice announcement – e.g. “C Q C Q from Delta Hotel One Foxtrot Romeo in group 2 6 2“ – only when TTS is active; follows the app language (DE/EN)
- Watchlist and CQ toasts are stacked vertically in a shared container so both are visible simultaneously
-
Configurable in Settings → 📡 Station / HF (new section)
-
TX power (dBm), cable type (15 preset 50-Ω types in three attenuation groups), cable length, antenna gain (dBi), antenna type (free text, e.g.
Dipol,Yagi 3-El.), antenna height, operating frequency and system margin (dB) -
Manual attenuation input – selecting "Manuell eingeben …" reveals an extra input field for custom cable loss in dB/10 m
-
Live display of calculated EIRP (
P_TX − cable loss + antenna gain) and theoretical free-space range (using configured system margin, default 30 dB) -
All parameters are included in the settings backup and are backwards compatible
-
FSPL coverage circle on the map – when the coverage overlay is active a yellow circle shows the theoretical free-space range derived from EIRP, frequency and system margin; legend distinguishes "Gemessen (Convex Hull)" and "FSPL-Reichweite (theor.)"
-
Station template variables – available in Auto-Reply, Bot commands, Beacon and Quick Texts:
Variable Example {my-tx-power}22 dBm{my-eirp}23.50 dBm{my-antenna}2.5 dBi{my-antenna-type}Dipol{my-antenna-height}10 m{my-freq}433.175 MHz
- Periodic telemetry messages
- Source-agnostic: any system can write the JSON file – Home Assistant, Node-RED, MQTT bridge, shell script, etc.
- HTTP POST endpoint
POST /api/telemetry– external sources (e.g. Home Assistant) can push JSON directly; no shared filesystem needed; protected by optionalX-Api-Keyheader - Flexible mapping – unlimited key → label / unit / decimal-places pairs, fully configurable in the Settings UI without touching source code
- Auto-split: if all values exceed 150 chars, messages are automatically split into
TM1:/TM2:/ … with a 2-second pause between packets - Destination – group (e.g.
#262), broadcast (*) or direct callsign (e.g.OE1KBC-1) - Status indicator in the status bar analogue to the beacon
- Live preview in Settings: shows current file values, formatted output per entry, and exact LoRa message(s)
- Instant send button in Settings for immediate test send without waiting for the interval
- Example messages:
TM: 🌡=10.7C 🧭=1022hPa 💧=86% 🌬=0.0m/sor split intoTM1:/TM2:when needed - 📖 Home Assistant integration guide – complete example with weather station sensors,
rest_commandand automation
- Rolling daily log files with configurable retention
- Optional UDP traffic log (
LogUdpTraffic) for offline analysis
- Optional persistent storage of all monitor data to an external database
- MySQL / MariaDB: writes each monitor entry as a row via parametrised
INSERT(usesMySqlConnector) - InfluxDB 2: writes each monitor entry as a point via HTTP Line Protocol (
/api/v2/write) - Provider selection in Settings → 🗄️ Datenbank:
- "Test connection" button: detects missing database, table or bucket and offers automatic creation with a single click
- Optional insert logging – every successful write is logged at
Informationlevel; privacy notice shown in Settings - Provider and connection settings change live without restart
- MeshCom LoRa packets are limited to 149 characters of message text
- Character counter
X/149next to the input field: grey → yellow (≥ 130) → red bold (≥ 145) maxlength="149"prevents over-long input in the browser- Server-side guard in
SendMessageAsync: logs a warning and aborts send if text exceeds 149 characters - Bot replies and beacon texts that exceed 149 characters are automatically split into multiple packets (2 s pause between parts) rather than being dropped
- Interactive map at
/mappowered by Leaflet.js + OpenStreetMap - APRS-style markers: filled circle colour-coded by RSSI (🟢 > −90 / 🟡 > −105 / 🔴 ≤ −105 dBm) + callsign label below
- Own position shown as gold diamond ◆ (APRS convention)
- Popup on click: callsign, QRZ operator name / QTH (when enabled), last message, RSSI, battery, altitude, QTH locator, GPS coordinates (as clickable OSM link) and a direct 🔗 aprs.fi link (opens station info page in new tab)
- Callsign search 🔍 – search field in the control bar; press Enter or click the button to jump directly to the station and open its popup; multiple matches show a dropdown; shows „Not found" if no match
- First open: map automatically zooms to a 50 km radius around own position (once own GPS is known)
- View persistence: last map position and zoom level are saved in
localStorageand restored on every subsequent visit - Compact info bar at the bottom:
📡 N Station(en) · 📍 MyCallsign– clean one-liner regardless of station count - 🤖 KI-Stationsbeschreibung (AI popup) – every station marker popup has a 🤖 button; clicking it sends the station's data (callsign, RSSI, SNR, battery, firmware, relay path, QRZ data) as context to the configured AI and shows a concise German-language station description directly inside the popup; requires AI integration to be configured and enabled
- Updates in real-time as new position beacons arrive
- Nav link 🗺️ added to the navigation bar
- Coverage overlay on the map – toggled with the 📶 button in the map control bar
- Measured convex hull (blue polygon) – shows the actual coverage area based on all directly heard stations (HopCount = 0); calculated as the convex hull of the GPS positions of all direct-receive stations plus own position (Haversine / gift-wrapping algorithm, implemented in JavaScript)
- Fill opacity 35 %, solid border (weight 3, opacity 95 %) for good contrast against the map tiles
- Tooltip:
📡 Gemessene Reichweite - Coverage is recalculated every time the button is toggled; no background polling
- FSPL radius (yellow circle) – shown alongside the convex hull when station/HF parameters are configured; represents the theoretical free-space range based on EIRP, frequency and system margin; legend shows both layers
Requires MySQL database to be configured for all features in this section.
A 🔎 icon appears on every direct-chat tab as soon as the MySQL database is active.
Clicking it opens a modal dialog with four tabs:
| Tab | Requires AI | Description |
|---|---|---|
| 📋 KI-Zusammenfassung | ✅ Yes | AI-generated summary of recent QSO messages |
| 📜 Verlauf | ❌ No | Paginated message history with date & text filter |
| 🔎 Suche | ❌ No | Full-text search across all messages in the conversation |
| 🔎 KI-Suche | ✅ Yes | Ask a natural-language question; AI cites exact timestamps |
- Without AI configured: tabs 📋 and 🔎 KI-Suche are disabled (greyed out); History and Search work with MySQL only
- With AI configured: all four tabs available; the dialog opens on the Summary tab by default
-
Automatic QSO summaries generated by an external AI API (OpenAI, Grok or Azure OpenAI)
-
A 🔎 icon appears on the chat tab of every direct conversation once the last contact is older than the configurable threshold (
ThresholdDays, default 7 days) – or immediately when a summary already exists -
Generate / Regenerate button – sends the last N messages (configurable
MaxMessages, default 50) to the AI and stores the result in theqso_summariestable -
Supported AI providers:
Provider ProvidervalueDefault model OpenAI (default) openaigpt-4o-miniGrok (xAI) grokgrok-3-miniAzure OpenAI azuredeployment name -
Token usage statistics – current session prompt / completion / total tokens and request count shown in Settings → 🤖 KI
-
OpenAI balance check – queries the OpenAI billing API and shows remaining credit / monthly usage in Settings; falls back to a dashboard link when the billing endpoint is restricted (project keys)
-
Azure OpenAI: configurable resource endpoint and API version
- Paginated chronological message history for the current conversation (oldest first)
- Date range filter (from / to) and free-text filter – all combinable
- Page size: 50 messages; previous/next pagination with total count
- Works with MySQL only – no AI API key required
- Full-text search across all stored messages in the direct conversation
- Date range filter (from / to) combinable with the search term
- Results shown in a table with matching text highlighted
- Works with MySQL only – no AI API key required
- Ask any natural-language question about the conversation (e.g. "What did Jürgen recommend?")
- The AI receives all relevant messages and responds with a precise answer citing exact timestamps
- Optional date range to narrow the search window
- „Alle Direkt-QSOs durchsuchen" / „Search all direct QSOs" – optional checkbox to search across all direct 1:1 QSOs instead of just the current conversation; group messages (
#...) and broadcasts are excluded; the AI lists the callsigns found and cites the exact timestamp and original text for each match - Requires AI to be enabled and an API key to be configured
- Set up MySQL – configure
Database.Provider = "mysql"and a validMySqlConnectionStringin Settings; use the „Anlegen" button to create the schema automatically - Choose a provider –
openai(default),grokorazure - Enter your API key:
- OpenAI: create a key at platform.openai.com/api-keys (project key is sufficient for summaries)
- Grok / xAI: create a key at console.x.ai
- Azure OpenAI: enter resource endpoint + deployment name + API version
- Enable AI – tick „Aktiviert" and click 💾 Save; takes effect immediately without restart
- Test – enter a callsign in the test field and click „DB + API testen"; the test checks DB connectivity and makes a live API call
- The 🔎 icon appears on chat tabs once messages are stored in MySQL
API keys are stored encrypted in
appsettings.override.json(ASP.NET Core Data Protection,dp:prefix).
- HTTP POST to a configurable URL on incoming events
- Configurable triggers: chat messages / position beacons / telemetry (each individually)
- JSON payload:
event,timestamp,from,to,text,rssi,snr,latitude,longitude,altitude,battery,firmware,relay_path,src_type; telemetry events additionally includetemp1,temp2,humidity,pressure;nullfields are omitted - Fire-and-forget (10 s timeout); errors logged and swallowed – never blocks reception
- Configured in Settings → 🔗 Webhook; changes apply live without restart
- Compatible with Home Assistant webhooks, Node-RED, n8n, IFTTT, custom endpoints
-
Optional connection to an external MQTT broker (e.g. Mosquitto, Home Assistant Mosquitto add-on, EMQX)
-
Publisher – forwards incoming MeshCom events to typed MQTT topics:
Event Topic Broadcast chat {prefix}/broadcastGroup chat {prefix}/group/{group}e.g.meshcom/group/262Direct message {prefix}/dm/{callsign}e.g.meshcom/dm/DH1FR-1Position beacon {prefix}/position/{callsign}Telemetry {prefix}/telemetry/{callsign} -
Subscriber (optional) – subscribes to send-topics and forwards them as outgoing UDP messages to the MeshCom node:
Topic Action {prefix}/send/broadcastSend broadcast message {prefix}/send/group/{group}Send group message {prefix}/send/dm/{callsign}Send direct message Payload: JSON
{ "text": "message text" }– all{variable}placeholders ({mycall},{callsign},{date},{time}, …) are expanded before sending. -
MQTT payload is identical to the Webhook JSON payload for consistency
-
Supports authentication (username / password stored encrypted), TLS and a configurable topic prefix
-
Auto-reconnect on connection loss
-
Configurable per-event publish flags (messages / positions / telemetry separately)
-
Configured in Settings → 📡 MQTT; password is stored encrypted
- User-defined display names for MeshCom group numbers shown in the chat tab below the group number
- The short label (e.g.
DL,DACH,OE1) appears inside the tab; the full name (e.g.DL – Deutschland) appears as a tooltip on hover - Pre-filled with the official MeshCom GRC group list from icssw.org/meshcom-grc-gruppen covering all European and international groups (EU, DACH, country codes, Italian regions, German regional groups, …)
- Fully editable in Settings → 🏷️ Group Labels – add, remove or rename entries
- Restore Defaults button resets the list to the official MeshCom group table
- 📤 Export / 📥 Import – export labels as
MeshComWebDesk-group-labels.json; share with other users or import a community list; filename editable before export - Labels are saved in
appsettings.override.jsonand apply without restart
- Full encrypted backup of all settings including passwords and API keys – exported as
MeshComWebDesk-settings.enc(binary, browser download) - AES-256-CBC encryption with a user-supplied password; key derived via PBKDF2 / SHA-256 / 100,000 iterations
- File format:
[MCWD magic 4B][Salt 16B][IV 16B][AES ciphertext]– portable across machines and operating systems - Password is never stored – if lost, the file cannot be recovered
- Import / Restore: select the
.encfile and enter the password; settings are decrypted, validated and saved; the page reloads automatically with a success confirmation - Error handling: wrong password, corrupted file, invalid JSON and missing fields all produce clear error messages
- Filename editable before export (default:
MeshComWebDesk-settings.enc) - Found in Settings → 🔐 Datensicherung at the bottom of the settings page
- Installable on any device via the browser's "Add to Home Screen" / "Install" prompt
manifest.webmanifestwith name, icon,display: standalone, shortcuts (Chat + Map)- Minimal service worker – enables install prompt; full offline not possible (Blazor Server requires live connection)
- Apple meta tags for iOS Safari Add-to-Home-Screen
- Custom antenna SVG icon in the app colour scheme
- Optional integration with the QRZ.com XML API for operator name and home location
- Requires a free QRZ.com account (username + password – no separate API key)
- Login flow: the app fetches a session key automatically on first lookup and refreshes it transparently on expiry
- Free account returns first name + city/QTH – sufficient for all MeshCom WebDesk displays
- XML subscription (~$30/year) unlocks full profile data
- SSID stripping:
DH1FR-2is looked up asDH1FRautomatically - Results are cached in memory per app session – each callsign is only queried once regardless of how many pages display it
- Shown everywhere a callsign appears:
- 📻 MH list – dedicated Name / Location column + hover tooltip on the callsign cell
- 💬 Chat – hover tooltip on tab buttons (direct chats) and on every callsign in messages and monitor rows
- 🗺️ Map – callsign popup shows name and QTH below the bold callsign
- Configured in Settings → 🔍 QRZ.com; can be enabled/disabled live without restart
- Test button in Settings: performs a live lookup of your own callsign and shows the result immediately
- Cache-clear button in Settings: forces fresh lookups after account upgrade or data change
- All errors (wrong credentials, network failure, unknown callsign) are logged as Warning in the Serilog log file
scripts/create-lan-cert.ps1– one-click self-signed certificate generator for Windows PowerShell- Auto-detects LAN IP; can also be set manually with
-LanIp - Creates cert with IP SAN (Subject Alternative Name) so browsers accept it without warnings
- Exports
certs/meshcom-lan.pfxfor Kestrel +certs/meshcom-lan.crtfor mobile device trust - Trusts the cert in Windows
CurrentUser\Rootautomatically
- Auto-detects LAN IP; can also be set manually with
- New launch profile
lan-https– binds HTTP:5162and HTTPS:5163simultaneously appsettings.LanHttps.json– Kestrel HTTPS endpoint configuration (loaded viaASPNETCORE_ENVIRONMENT=LanHttps)- HTTP on port 5162 stays active – existing bookmarks and Docker deployments are unaffected
- HTTPS is only needed for PWA installation on Android / iPad / iPhone over LAN
MeshcomWebDesk/ ← Blazor Server (ASP.NET Core host)
│ Program.cs ← DI setup, Serilog, hosted services
│ appsettings.json ← All configuration
│ appsettings.LanHttps.json ← Kestrel HTTPS endpoint on :5163 (for PWA on mobile)
│
├─ Components/
│ ├─ App.razor ← HTML shell + JS helpers + Leaflet CDN + SW registration
│ ├─ Layout/
│ │ MainLayout.razor ← Top navigation bar
│ └─ Pages/
│ Chat.razor ← Chat tabs + monitor pane + status bar
│ Mh.razor ← Most Recently Heard table + own position
│ Map.razor ← Live Leaflet map with APRS-style markers
│ Settings.razor ← Web-based configuration editor
│ About.razor ← Version / copyright / build info + PayPal donation link
│ Clear.razor ← Data reset page
│
├─ Helpers/
│ GeoHelper.cs ← Haversine, coordinate formatting, OSM links
│ MeshcomLookup.cs ← hw_id → hardware name table, firmware formatter
│
├─ Models/
│ MeshcomMessage.cs ← Message model (from/to/text/GPS/RSSI/ACK/relay/telemetry)
│ MeshcomSettings.cs ← Strongly-typed config (IOptions)
│ AiSettings.cs ← AI provider + API key + model + summary parameters
│ TelemetryMappingEntry.cs ← Telemetry mapping entry (JSON key → label + unit + decimals)
│ QuickTextEntry.cs ← Quick-text button entry (label + text, supports {variables})
│ DatabaseSettings.cs ← DB provider + connection settings + LogInserts
│ WebhookSettings.cs ← Webhook URL + trigger flags
│ QrzSettings.cs ← QRZ.com credentials + enabled flag
│ ChatTab.cs ← Tab model with UnreadCount
│ HeardStation.cs ← MH list entry (GPS, signal, battery, hardware, firmware)
│ ConnectionStatus.cs ← Live UDP status + own GPS position
│ PersistenceSnapshot.cs ← Serialisable state snapshot (tabs, MH, monitor, own GPS)
│
├─ wwwroot/
│ map.js ← Leaflet JS helpers (init, updateMarkers, APRS icons)
│ manifest.webmanifest ← PWA manifest (name, icon, display:standalone, shortcuts)
│ service-worker.js ← Minimal SW – enables install prompt
│ icons/icon.svg ← Antenna icon in app colour scheme
│ certs/ ← LAN certificate directory (not in repo – .gitignore)
│
├─ scripts/
│ create-lan-cert.ps1 ← PowerShell: generates self-signed cert for HTTPS LAN access (Windows)
│ create-lan-cert.sh ← Bash: generates self-signed cert for HTTPS LAN access (Linux / Docker)
│ start-https.sh ← Bash: starts Docker Compose with HTTPS overlay (checks cert first)
│
├─ docker-compose.yml ← Standard deployment: HTTP only (always works)
├─ docker-compose.https.yml ← HTTPS overlay: adds port 5163 + cert volume (requires cert)
│
└─ Services/
MeshcomUdpService.cs ← BackgroundService: UDP RX/TX, JSON parsing, ACK matching, beacon timer, bot reply sender
ChatService.cs ← Singleton: routing, tabs, MH list, monitor, deduplication, webhook trigger, OnBotCommand event
DataPersistenceService.cs← BackgroundService: load/save state to JSON on disk
SettingsService.cs ← Writes appsettings.override.json in DataPath (Docker-safe); changes applied live via IOptionsMonitor
LanguageService.cs ← Singleton: UI language switching (de/en); T(de,en) helper; OnChange event for instant re-render
WebhookService.cs ← HTTP POST fire-and-forget on message / position / telemetry events
QrzService.cs ← QRZ.com XML API: session login, callsign lookup, in-memory cache
QsoSummaryService.cs ← AI-based QSO summary: reads messages from MySQL, calls AI API, stores result in qso_summaries table; token usage tracking; balance check
Bot/
IBotCommand.cs ← Interface for all bot commands (Name, Description, ExecuteAsync)
BotCommandService.cs ← Dispatcher: parses --name [args], builds --help, hot-reloads user commands from config; normalises bare "ping" keyword
VersionCommand.cs ← --version: returns app version
TimeCommand.cs ← --time: returns current date/time
MhCommand.cs ← --mh: returns MH list count + callsigns
PingCommand.cs ← --ping / bare "ping": pong with RSSI, SNR, relay route and receive timestamp
EchoCommand.cs ← --echo <text>: echoes arguments back to the sender
ConfiguredBotCommand.cs← Wrapper for BotCommands entries from appsettings.json
Database/
IMonitorDataSink.cs ← Interface: WriteAsync(MeshcomMessage)
MySqlMonitorSink.cs ← MySQL / MariaDB write sink (MySqlConnector)
InfluxDbMonitorSink.cs ← InfluxDB 2 write sink (HTTP Line Protocol)
MonitorSinkService.cs ← Routes each write to the active provider; IOptionsMonitor-aware
DatabaseSetupService.cs← Connection test + automatic schema creation (DB, table, bucket)
All settings in MeshcomWebDesk/appsettings.json:
"Meshcom": {
"ListenIp": "0.0.0.0", // bind address (0.0.0.0 = all interfaces)
"ListenPort": 1799, // local UDP port
"DeviceIp": "192.168.1.60", // MeshCom node IP
"DevicePort": 1799, // MeshCom node UDP port
"MyCallsign": "NOCALL-1", // own callsign
"LogPath": "C:\\Temp\\Logs",// log file directory
"LogRetainDays": 30, // log file retention in days
"LogUdpTraffic": false, // log every UDP packet to file
"MonitorMaxMessages": 1000, // max monitor history (oldest dropped)
"GroupFilterEnabled": true, // only show whitelisted group tabs
"Groups": ["#20","#262"], // whitelisted groups (GroupFilterEnabled=true)
"WatchCallsigns": ["DH1FR","OE1KBC-1"], // watched callsigns (without SSID = match all SSIDs)
"WatchOnMessage": true, // alert on chat messages from watched callsigns
"WatchOnPosition": true, // alert on position beacons
"WatchOnTelemetry": false, // alert on telemetry packets (periodic – off by default)
"WatchOnAck": false, // alert on ACK packets
"WatchAlertMinutes": 5, // watchlist toast auto-dismiss in minutes (min 1)
"DataPath": "C:\\Temp\\MeshcomData", // persistent state directory
"AutoReplyEnabled": false, // send auto-reply on first contact
"AutoReplyText": "...", // auto-reply text; {version} → app version
"BotEnabled": false, // enable remote command bot (-- prefix)
"BotCommands": [ // user-defined bot commands (built-in: --help/--version/--time/--mh)
{ "Name": "info", "Response": "QTH: Wien, HW: {hw}, 73 de {mycall}!", "Description": "Stationsinfo" }
],
"BeaconEnabled": false, // send periodic beacon (Bake)
"BeaconGroup": "#262", // target group for beacon
"BeaconText": "...", // beacon text; {version} → app version
"BeaconIntervalHours": 1, // beacon interval in hours (minimum 1)
"TelemetryEnabled": false, // send periodic telemetry message
"TelemetryFilePath": "/data/telemetry.json", // source JSON file (written by HA, script etc.)
"TelemetryGroup": "#262", // destination: group (#262), broadcast (*), or callsign
"TelemetryScheduleHours": "11,15", // send at 11:00 and 15:00 (comma-separated hours 0–23)
"TelemetryApiEnabled": false, // enable POST /api/telemetry HTTP endpoint
"TelemetryApiKey": "", // optional X-Api-Key for the endpoint (empty = no auth)
"Language": "de", // UI language: "de" (German) or "en" (English)
"Database": { // optional database sink
"Provider": "none", // "none" | "mysql" | "influxdb2"
"MySqlConnectionString": "", // e.g. "Server=localhost;Database=meshcom;User=mc;Password=secret;"
"MySqlTableName": "meshcom_monitor", // created automatically via Settings → Anlegen
"InfluxUrl": "http://localhost:8086",
"InfluxToken": "",
"InfluxOrg": "meshcom",
"InfluxBucket": "meshcom",
"LogInserts": false // log every successful write at Information level
},
"Webhook": {
"Enabled": false, // send HTTP POST on events
"Url": "", // target URL (HTTP POST, JSON body)
"OnMessage": true, // fire on incoming chat messages
"OnPosition": false, // fire on incoming position beacons
"OnTelemetry": false // fire on incoming telemetry
},
"Mqtt": { // optional MQTT broker integration
"Enabled": false, // enable MQTT connection
"Host": "localhost", // MQTT broker hostname or IP
"Port": 1883, // MQTT broker port (default 1883, TLS usually 8883)
"ClientId": "meshcom-webdesk",
"Username": "", // optional MQTT username
"Password": "", // optional MQTT password (stored encrypted after first UI save)
"UseTls": false, // enable TLS/SSL
"TopicPrefix": "meshcom", // prefix for all topics
"PublishMessage": true, // publish incoming chat messages
"PublishPosition": false, // publish incoming position beacons
"PublishTelemetry": false, // publish incoming telemetry packets
"SubscribeEnabled": false // subscribe to send-topics and forward to mesh
},
"Qrz": {
"Enabled": false, // enable QRZ.com XML API callsign lookups
"Username": "", // QRZ.com login username (usually your callsign)
"Password": "" // QRZ.com login password (stored encrypted after first UI save)
},
"Ai": {
"Enabled": false, // enable AI-based QSO summary feature
"Provider": "openai", // "openai" | "grok" | "azure"
"ApiKey": "", // API key (stored encrypted after first UI save)
"Model": "gpt-4o-mini", // OpenAI: "gpt-4o-mini"/"gpt-4o"; Grok: "grok-3-mini"/"grok-3"; Azure: deployment name
"AzureEndpoint": "", // Azure OpenAI resource endpoint (only for Provider="azure")
"AzureApiVersion":"2024-08-01-preview", // Azure OpenAI API version (only for Provider="azure")
"ThresholdDays": 7, // days since last QSO before the 🤖 icon appears (0 = always)
"SummaryDays": 365, // how many days back to look for messages
"MaxMessages": 50, // max messages sent to AI per request (limits token usage)
"LogRequests": false // log every AI API request at Information level
},
"TelemetryMapping": [ // any number of entries; configure in Settings UI
{ "JsonKey": "aussentemp", "Label": "🌡", "Unit": "C", "Decimals": 1 },
{ "JsonKey": "luftdruck", "Label": "🧭", "Unit": "hPa", "Decimals": 1 },
{ "JsonKey": "pv_leistung", "Label": "☀", "Unit": "kW", "Decimals": 2 }
]
}The lan launch profile binds to all network interfaces:
# In Visual Studio: select profile "lan" next to the Run button
# Then open in browser on any device in the same network:
http://192.168.x.x:5162Set "LogUdpTraffic": true to write every packet to the log file:
[INF] [UDP-RX] 192.168.1.60:1799 {"src_type":"lora","type":"msg","src":"DH1FR-1",...}
[INF] [UDP-TX] 192.168.1.60:1799 {"type":"msg","dst":"DH1FR-1","msg":"Hello"}
Filter the log file:
Select-String "\[UDP-RX\]" C:\Temp\Logs\MeshcomWebDesk-*.log
Select-String "\[UDP-TX\]" C:\Temp\Logs\MeshcomWebDesk-*.logThis client communicates with the MeshCom node using the EXTUDP JSON protocol defined in the MeshCom firmware.
type |
Description | Handled as |
|---|---|---|
msg |
Chat message (direct, broadcast, group, ACK) | Chat tab + monitor |
pos |
Position beacon with GPS coordinates | MH list + monitor |
tele |
Telemetry (temperature, humidity, pressure, battery) | MH list + monitor |
| Direction | Example |
|---|---|
| Registration | {"type":"info","src":"NOCALL-2"} |
| Chat RX (direct) | {"src_type":"lora","type":"msg","src":"NOCALL-1","dst":"NOCALL-2","msg":"Hello{034","msg_id":"5DFC7187","rssi":-95,"snr":12,"firmware":35,"fw_sub":"p"} |
| Chat RX (relayed) | {"src_type":"lora","type":"msg","src":"OE1XAR-62,DL0VBK-12,DB0KH-11","dst":"*","msg":"...","rssi":-109,"snr":5} |
| Position RX | {"src_type":"lora","type":"pos","src":"DB0MGN-1,...","lat":50.57,"lat_dir":"N","long":10.42,"long_dir":"E","alt":1243,"batt":100,"hw_id":42,"firmware":35,"fw_sub":"p","rssi":-108,"snr":5} |
| Telemetry RX | {"src_type":"lora","type":"tele","src":"DB0MGN-1,...","batt":100,"temp1":20.6,"hum":0,"qnh":1031.4} |
| Chat TX | {"type":"msg","dst":"NOCALL-1","msg":"Hello"} |
| ACK RX | {"src_type":"udp","type":"msg","src":"NOCALL-1","dst":"NOCALL-2","msg":"NOCALL-2 :ack034","msg_id":"A177E139"} |
- Outgoing message sent →
⏳pending (waiting for node echo) - Node echo arrives with sequence marker
{034}→ indicator changes to✓(transmitted over LoRa) - Recipient sends ACK
:ack034→ message marked as delivered✓✓ - Group / broadcast messages: no ACK expected →
☁️after node echo - Gateway delivery confirmed →
☁️✓
| ID | Short name | Hardware |
|---|---|---|
| 1–3 | TLORA-V1/V2 | TTGO LoRa32 |
| 4–6, 12 | T-BEAM | TTGO T-Beam |
| 7 | T-ECHO | LilyGO T-Echo |
| 8 | T-DECK | LilyGO T-Deck |
| 9 | RAK4631 | Wisblock RAK4631 |
| 10–11, 43 | HELTEC-V1/V2/V3 | Heltec WiFi LoRa 32 |
| 39 | EBYTE-E22 | Ebyte LoRa E22 (ESP32) |
| 41 | HELTEC-TRACK | Heltec Wireless Tracker |
| 42 | HELTEC-STICK | Heltec Wireless Stick v3 |
| 44 | HELTEC-E290 | Heltec E-Ink E290 |
| 45 | T-BEAM-1262 | TTGO T-Beam 1.2 SX1262 |
| 46 | T-DECK-PLUS | LilyGO T-Deck Plus |
| 47 | TBEAM-SUP | TTGO T-Beam Supreme |
| 48 | EBYTE-E22-S3 | Ebyte LoRa E22 (ESP32-S3) |
| 49 | T-LORA-PAGER | LilyGO T-Lora Pager |
| 50 | T-DECK-PRO | LilyGO T-Deck Pro |
| 51 | T-BEAM-1W | LilyGO T-Beam 1W |
| 52 | HELTEC-V4 | Heltec WiFi LoRa 32 v4 |
Note: Altitude in position packets follows APRS convention (feet). The client converts to metres automatically.
💡 No build required: Ready-to-run binaries for Windows and Linux are available under Releases.
- .NET 10 Runtime (ASP.NET Core Runtime, required to run the Windows binary)
- .NET 10 SDK (only required for build from source)
- A reachable MeshCom node running firmware v4.35+ with EXTUDP enabled
- UDP port 1799 open (Windows Firewall / router)
When running the .exe for the first time, Windows may show "Windows protected your PC".
This happens because the binary is not code-signed.
To run it anyway:
- Click "More info" in the SmartScreen dialog
- Click "Run anyway"
Alternative: Right-click the .exe → Properties → check "Unblock" → OK
cd MeshcomWebDesk
dotnet run --launch-profile lan # HTTP only, accessible from all LAN devices
# or
dotnet run --launch-profile lan-https # HTTP :5162 + HTTPS :5163 (required for PWA on mobile)
# or
dotnet run # localhost onlyThen open http://localhost:5162 (or http://<your-ip>:5162 for LAN access).
# Step 1 – Generate self-signed certificate (once, run as admin)
cd C:\SRC\RA\MeshcomWebDesk
.\scripts\create-lan-cert.ps1 # auto-detects LAN IP
# or: .\scripts\create-lan-cert.ps1 -LanIp 192.168.1.100
# Step 2 – Start app with HTTPS
cd MeshcomWebDesk
dotnet run --launch-profile lan-httpsMobile trust: Copy
certs/meshcom-lan.crtto your phone and install it as a trusted root CA. Then openhttps://<your-ip>:5163in the browser and install the PWA.
Das selbstsignierte Zertifikat ermöglicht verschlüsselten HTTPS-Zugriff im Heimnetz. Ohne HTTPS lässt sich die App nicht als PWA auf Mobilgeräten installieren.
# PowerShell als Administrator öffnen
cd C:\SRC\RA\MeshcomWebDesk
# IP automatisch erkennen:
.\scripts\create-lan-cert.ps1
# oder IP manuell angeben:
.\scripts\create-lan-cert.ps1 -LanIp 192.168.1.100Das Skript erstellt:
certs/meshcom-lan.pfx– für Kestrel (wird von der App verwendet)certs/meshcom-lan.crt– für Mobilgeräte (dort installieren)- Trägt das Zertifikat in Windows
CurrentUser\Rootein → kein Browser-Warning mehr auf dem PC
Voraussetzung: openssl installiert (sudo apt-get install -y openssl).
# Skript ausführbar machen (einmalig)
chmod +x scripts/create-lan-cert.sh
# IP automatisch erkennen:
./scripts/create-lan-cert.sh
# oder IP manuell angeben:
./scripts/create-lan-cert.sh 192.168.1.100Das Skript erstellt:
MeshcomWebDesk/certs/meshcom-lan.pfx– für Kestrel im ContainerMeshcomWebDesk/certs/meshcom-lan.crt– für Mobilgeräte
Windows (dotnet run):
cd MeshcomWebDesk
dotnet run --launch-profile lan-httpsLinux / Docker Compose:
Zwei separate Compose-Dateien – HTTP läuft immer, HTTPS nur wenn Zertifikat vorhanden:
# HTTP only (Standard – immer funktionsfähig):
docker compose up -d --build
# HTTP + HTTPS (mit Zertifikat-Prüfung):
./scripts/start-https.sh # prüft ob certs/meshcom-lan.pfx existiert
# oder manuell:
docker compose -f docker-compose.yml -f docker-compose.https.yml up -d --build
docker-compose.https.ymlsetztASPNETCORE_ENVIRONMENT=LanHttpsund mountet./certs:/app/certs:ro– wenn das Verzeichnis leer ist, startet der Container nicht. Das Skriptscripts/start-https.shprüft das vorab und gibt eine klare Fehlermeldung.
Beide Ports laufen gleichzeitig:
HTTP → http://192.168.x.x:5162 ← wie bisher, bleibt aktiv
HTTPS → https://192.168.x.x:5163 ← neu, für PWA
Die Datei certs/meshcom-lan.crt auf das Gerät übertragen (z. B. per E-Mail oder USB).
| Gerät | Pfad |
|---|---|
| iPad / iPhone (iOS) | Einstellungen → Allgemein → VPN & Geräteverwaltung → Profil installieren → danach: Einstellungen → Allgemein → Info → Zertifikatsvertrauenseinstellungen → Zertifikat aktivieren |
| Android | Einstellungen → Sicherheit → Verschlüsselung & Anmeldedaten → Zertifikat installieren → CA-Zertifikat |
Noetig wenn der Container auf einem Linux-Server laeuft und du von Windows darauf zugreifst.
Vorbereitung: meshcom-lan.crt vom Linux-Server kopieren (SCP / WinSCP / USB-Stick):
scp user@192.168.1.x:/opt/meshcom/certs/meshcom-lan.crt C:\Temp\meshcom-lan.crtVariante A - Doppelklick (empfohlen):
meshcom-lan.crtper Doppelklick oeffnen- Klick auf Zertifikat installieren
- Speicherort: Lokaler Computer -> Weiter (Adminrechte erforderlich)
- Alle Zertifikate in folgendem Speicher speichern -> Durchsuchen
- Vertrauenswuerdige Stammzertifizierungsstellen -> OK -> Weiter -> Fertig stellen
- Sicherheitswarnung: Ja
Variante B - PowerShell als Administrator:
$cert = New-Object Security.Cryptography.X509Certificates.X509Certificate2("C:\Temp\meshcom-lan.crt")
$store = New-Object Security.Cryptography.X509Certificates.X509Store("Root","LocalMachine")
$store.Open("ReadWrite"); $store.Add($cert); $store.Close()
Write-Host "Installiert: $($cert.Thumbprint)"Danach Browser neu starten (Strg+Shift+Del empfohlen).
Firefox nutzt einen eigenen Zertifikatspeicher. Empfehlung:
about:config->security.enterprise_roots.enabledauftruesetzen -> neu starten.
Ergebnis: https://192.168.x.x:5163 zeigt das Schloss-Symbol ohne Warnung.
Eine Progressive Web App ist eine normale Webseite, die sich wie eine native App verhält. Der Browser bietet an, sie auf dem Gerät zu installieren – ohne App Store, ohne Download.
Nach der Installation:
- Eigenes Icon auf dem Home-Screen / Desktop
- Öffnet sich ohne Adressleiste (wie eine native App)
- Shortcut-Kacheln für Chat und Karte im Startmenü (Windows/Android)
⚠️ Offline-Betrieb ist nicht möglich – MeshCom WebDesk ist Blazor Server und benötigt immer eine Verbindung zum Server.
https://192.168.x.x:5163im Safari öffnen- Zertifikat muss vorab installiert sein (siehe oben)
- Teilen-Symbol ⤵ antippen → "Zum Home-Bildschirm"
- Namen bestätigen → Hinzufügen
https://192.168.x.x:5163in Chrome öffnen- Zertifikat muss vorab installiert sein
- ⋮ Menü → "App installieren" oder automatischer Banner am unteren Rand
https://192.168.x.x:5163öffnen (Zertifikat wird automatisch vertraut)- In der Adressleiste rechts: ⊕ Installieren
- Oder: ⋮ Menü → "MeshCom WebDesk installieren"
Die App bekommt ein eigenes Fenster ohne Browser-UI und erscheint im Startmenü / Taskbar.
# Install Docker + Docker Compose plugin (Debian / Ubuntu / Raspberry Pi OS)
sudo apt-get update
sudo apt-get install -y docker.io docker-compose-plugin
# Add current user to the docker group (no sudo needed)
sudo usermod -aG docker $USER
newgrp docker# Clone repository
git clone https://github.com/DH1FR/MeshcomWebDesk.git
cd MeshcomWebDesk
# Create optional config file (overrides embedded defaults)
cp deploy/appsettings.linux.json appsettings.json
nano appsettings.json # set DeviceIp, MyCallsign, Groups etc.
# Build image and start container
docker compose up -d --buildThe container runs in the background and restarts automatically (restart: unless-stopped).
Web interface: http://<Linux-IP>:5162
Note:
network_mode: hostis required so the container can receive UDP packets from the MeshCom device.
Either edit appsettings.json (next to docker-compose.yml) or use environment variables in docker-compose.yml:
environment:
- Meshcom__DeviceIp=192.168.1.60
- Meshcom__MyCallsign=NOCALL-1
- Meshcom__GroupFilterEnabled=true
- Meshcom__Groups__0=#OE
- Meshcom__Groups__1=#TestSettings saved via the UI are written to
DataPath/appsettings.override.json(inside the./datavolume).
Theappsettings.jsonmount stays read-only (:ro) – no container rebuild needed after UI changes.
After any change to docker-compose.yml or appsettings.json:
docker compose up -dPull the latest changes, rebuild the image and replace the container:
cd MeshcomWebDesk
# Fetch latest changes
git pull origin master
# Rebuild image and replace container (brief downtime)
docker compose up -d --build
# Remove unused old image (optional)
docker image prune -f# Check container status
docker compose ps
# Follow live logs (Ctrl+C to exit)
docker compose logs -f
# Stop container
docker compose stop
# Stop and remove container (config & logs are preserved)
docker compose down
# Stop, remove container and delete image (full reset)
docker compose down --rmi localDocker is the recommended deployment method. If you prefer not to use Docker, download the binary directly – it is framework-dependent, meaning the .NET 10 Runtime must be installed on the target machine (no SDK needed).
📦 Download: GitHub Releases
Prerequisites:
# Unzip to e.g. C:\meshcom
Expand-Archive MeshcomWebDesk-vX.Y.Z-win-x64.zip -DestinationPath C:\meshcom
# Edit configuration
notepad C:\meshcom\appsettings.json # set DeviceIp, MyCallsign
# Start
cd C:\meshcom
.\MeshcomWebDesk.exeOpen browser: http://localhost:5162
To run automatically at Windows startup, register as a Windows service:
sc.exe create MeshcomWebDesk binPath="C:\meshcom\MeshcomWebDesk.exe" start=auto sc.exe start MeshcomWebDesk
Prerequisites:
# Install .NET 10 Runtime (Debian / Ubuntu / Raspberry Pi OS)
sudo apt-get update && sudo apt-get install -y aspnetcore-runtime-10.0
# Extract archive
mkdir meshcom && tar -xzf MeshcomWebDesk-vX.Y.Z-linux-x64.tar.gz -C meshcom
cd meshcom
# Edit configuration (MyCallsign, DeviceIp etc.)
nano appsettings.json
# Install as systemd service (starts automatically at boot)
sudo bash install.shWeb interface: http://<Linux-IP>:5162
Useful commands after installation:
journalctl -u meshcom-webclient -f # live log
systemctl status meshcom-webclient # status
systemctl restart meshcom-webclient # restart after config changePrerequisites:
- .NET 10 ASP.NET Core Runtime for macOS
# Extract the archive (choose the right binary for your CPU)
# Apple Silicon (M1/M2/M3):
tar -xzf MeshcomWebDesk-vX.Y.Z-osx-arm64.tar.gz -C ~/meshcom
# Intel Mac:
tar -xzf MeshcomWebDesk-vX.Y.Z-osx-x64.tar.gz -C ~/meshcom
cd ~/meshcom
# Edit configuration
nano appsettings.json # set DeviceIp, MyCallsign
# Allow execution (macOS Gatekeeper)
xattr -d com.apple.quarantine MeshcomWebDesk
# Start
./MeshcomWebDeskOpen browser: http://localhost:5162
macOS Gatekeeper: If you see "cannot be opened because it is from an unidentified developer",
runxattr -d com.apple.quarantine ./MeshcomWebDeskonce before starting.
The shipped appsettings.json contains placeholder values – the following must be set before first start:
| Key | Description | Example |
|---|---|---|
MyCallsign |
Your own callsign | NOCALL-1 |
DeviceIp |
IP address of the MeshCom node | 192.168.1.60 |
LogPath |
Directory for log files | ./logs / /var/log/meshcom |
DataPath |
Directory for persistent state | ./data / /opt/meshcom/data |
© 2025–2026 Ralf Altenbrand (DH1FR) · All rights reserved.
This software is made available for licensed radio amateurs for private, non-commercial use.
Commercial use is not permitted without explicit written consent from the author.
Use at your own risk.
The author accepts no liability for damages of any kind – including but not limited to damage to hardware, network infrastructure, radio equipment or data loss – caused by the use of this software.
The software is provided without any warranty.
See LICENSE
🇩🇪 Deutsch | 🇬🇧 English below
MeshCom WebDesk verarbeitet Funkamateure-Daten – Rufzeichen und Nachrichtentexte, die über das MeshCom-Netz übertragen werden.
Diese Daten sind per se öffentlich (LoRa-Funk ist für jeden empfangbar), können aber personenbezogen im Sinne der DSGVO sein.
| Funktion | Gespeicherte Daten | Wo |
|---|---|---|
Log-Datei (LogUdpTraffic: true) |
Rufzeichen, Nachrichtentexte, GPS-Koordinaten, RSSI/SNR – als Rohdaten jedes UDP-Pakets | LogPath auf dem Server |
Datenbank (Database.Provider != "none") |
Alle Monitor-Einträge: Rufzeichen, Nachrichtentexte, Zeitstempel, GPS, RSSI, Batterie, Firmware | Externer Datenbankserver |
DB-Insert-Log (LogInserts: true) |
Vollständige SQL-INSERT-Anweisungen mit allen Feldinhalten |
LogPath auf dem Server |
Persistenz (DataPath) |
Chat-Verlauf, MH-Liste, Monitor-History, eigene GPS-Position | DataPath auf dem Server |
- UDP-Traffic-Log (
LogUdpTraffic) nur aktivieren, wenn zur Fehlersuche nötig; danach wieder deaktivieren. - DB-Insert-Log (
LogInserts) nur kurzfristig zur Fehlersuche aktivieren; enthält vollständige Nachrichtentexte und Rufzeichen. - Die Datenbankanbindung ist für den Betrieb im eigenen, abgesicherten Netz vorgesehen. Externe Datenbankserver sollten verschlüsselte Verbindungen verwenden (
SslMode=Requiredim Connection String). - Log-Aufbewahrung (
LogRetainDays) auf den minimal notwendigen Zeitraum setzen. - Der Betrieb dieser Software unterliegt den für Funkamateure geltenden datenschutzrechtlichen Regelungen (DSGVO, BDSG, ggf. nationale Amateurfunkgesetze). Der Betreiber ist selbst verantwortlich für die rechtskonforme Nutzung.
MeshCom WebDesk processes amateur radio data – callsigns and message texts transmitted over the MeshCom mesh network.
This data is inherently public (LoRa radio is receivable by anyone), but may constitute personal data under GDPR.
| Feature | Data stored | Location |
|---|---|---|
Log file (LogUdpTraffic: true) |
Callsigns, message texts, GPS coordinates, RSSI/SNR – raw content of every UDP packet | LogPath on the server |
Database (Database.Provider != "none") |
All monitor entries: callsigns, message texts, timestamps, GPS, RSSI, battery, firmware | External database server |
DB insert log (LogInserts: true) |
Full SQL INSERT statements with all field values |
LogPath on the server |
Persistence (DataPath) |
Chat history, MH list, monitor history, own GPS position | DataPath on the server |
- Enable UDP traffic logging (
LogUdpTraffic) only for troubleshooting; disable it afterwards. - Enable DB insert logging (
LogInserts) only briefly for debugging; it contains full message texts and callsigns. - The database sink is intended for use within your own secured network. External database servers should use encrypted connections (
SslMode=Requiredin the connection string). - Set log retention (
LogRetainDays) to the minimum period necessary. - Operation of this software is subject to the data protection regulations applicable to amateur radio operators (GDPR, national regulations). The operator is solely responsible for lawful use.
© by Ralf Altenbrand (DH1FR) 2025–2026
- feat: 💬 MsgId im Monitor – eingehende Nachrichten zeigen die Nachrichten-ID (msg_id) im Monitor an; erleichtert Diagnose und ACK-Zuordnung
- feat: 🟡 Toast-Bezeichnung – Toast-Anzeige für Watchlist- und CQ-Treffer zeigt jetzt „empfangen" statt „gehört"
- feat:
↔️ Eigene Nachrichten linksbündig – neue Option OwnMessagesAlignLeft in den Einstellungen; eigene gesendete Nachrichten können optional linksbündig dargestellt werden (Standard: rechtsbündig) - feat: 🔊 Watchlist-TTS Drosselung – Sprachansagen für Watchlist-Treffer werden pro Rufzeichen maximal einmal alle 5 Minuten ausgelöst
- fix: 🕔 {last-qso} zeigt vorheriges QSO – Variable liefert jetzt den Zeitpunkt des vorherigen direkten QSOs (aktuelles Paket wird ausgeschlossen); kein vorheriges QSO → „kein QSO"
- fix: 🤖 Bot: ---=== nicht mehr als Befehl erkannt – Dekorations-Strings wie ---=== (WebDesk-Ident-Auto-Reply) werden nicht mehr als Bot-Befehl interpretiert
- fix: 💬 Direkt-Tab ohne Voice sofort sichtbar – neu angelegte Direkt-Tabs erscheinen jetzt auch dann sofort in der UI wenn Sprachansagen deaktiviert sind
- fix: 🔍 QRZ Race Condition – In-Flight-Deduplizierung für QRZ-Abfragen verhindert parallele Mehrfach-Requests für dasselbe Rufzeichen
- feat: 📻 Station / HF-Parameter – neue Einstellungssektion mit TX-Leistung (dBm), Kabeltyp (15 vordefinierte 50-Ω-Typen), Kabeldämpfung (manuell eingebbar), Antennengewinn (dBi), Antennentyp (Freitext), Antennengehöhe (m) und Frequenz (MHz); EIRP und theoretische Freiraumreichweite werden live berechnet und angezeigt
- feat: 🗺️ FSPL-Reichweiten-Kreis auf der Karte – beim Aktivieren der Reichweitenanzeige wird neben der Convex-Hull-Wolke ein gelber Kreis mit der theoretischen Freiraumreichweite (EIRP + Frequenz + Systemreserve) eingeblendet; Legende unterscheidet „Gemessen (Convex Hull)“ und „FSPL-Reichweite (theor.)“
- feat: 📡 Stationsvariablen –
{my-tx-power},{my-eirp},{my-antenna},{my-antenna-type},{my-antenna-height},{my-freq}stehen in Auto-Reply, Bot-Befehlen, Bake-Text und Quick Texts als Platzhalter zur Verfügung - feat: 🕔 Neue Variable
{last-qso}– Zeitstempel des letzten direkten QSOs mit dem aktuellen Rufzeichen (Formatdd.MM.yyyy HH:mm); DB-first + In-Memory-Fallback; verfügbar in Auto-Reply, Bot-Befehlen, Beacon und Quick Texts - feat: 📋 Nachricht in Zwischenablage kopieren – beim Hovern über eine Nachricht im Direkt-Tab erscheint ein 📋-Button zum Kopieren des Nachrichtentexts (JS Clipboard API)
- feat: 📢 CQ-Erkennung – eingehende Gruppen-Nachrichten werden automatisch auf CQ-Rufe geprüft (case-insensitiver Regex, kein False-Positive bei Wörtern wie „FREQUENCY“); eigenes Rufzeichen wird unterdrückt; nur Gruppen aus dem aktiven Gruppenfilter
- feat: 📢 CQ-Toast – Toast-Anzeige (gelb) mit Rufzeichen, Gruppe und Alter; automatisches Verschwinden nach 60 Sekunden; Watchlist- und CQ-Toast werden vertikal gestapelt, sodass beide gleichzeitig sichtbar sind
- feat: 📢 CQ-Beep – Morse-Muster CQ CQ in CW-Stil (700 Hz, 80 ms Einheit, korrektes dit/dah/Wortpausen-Timing); nur wenn Ton aktiv
- feat: 📢 CQ-TTS-Ansage – z. B. „C Q C Q von Delta Hotel Eins Foxtrot Romeo in Gruppe 2 6 2“; nur wenn Sprachansagen aktiv; folgt der App-Sprache (DE/EN)
- fix: 🤖 OpenAI Guthaben-Link – migriert von veralteten Dashboard-Endpunkten auf
/v1/organization/costs; zusätzlicher Link zuhttps://platform.openai.com/usageergänzt - fix: 📢 CQ-Beep Timing – korrektes Morse-Timing (Inter-Element 1u, Inter-Zeichen 3u, Wortpause 7u) ohne doppelte Lücken
- feat: 🔧 Variable {telemetry} – Telemetrie-String aus der JSON-Datei steht überall als {telemetry}-Platzhalter zur Verfügung (Auto-Reply, Bot, Bake-Text, Quick Texts, Nachrichteneingabe); JSON wird bei jedem Abruf neu eingelesen; dokumentiert in der Variablen-Referenztabelle in den Einstellungen und im README
- feat: 🗺️ Locator-Format korrigiert – Maidenhead-Locator wird jetzt überall korrekt dargestellt (letzte zwei Buchstaben kleingeschrieben, z. B. JO40nu statt JO40NU); zentral in GeoHelper.ToMaidenhead() geändert
- feat: @-Mention-Button – neben jedem fremden Rufzeichen in der Nachrichtenliste (Gruppen/Alle) und im Monitor erscheint beim Hover ein kleiner @-Button; Klick fügt @RUFZEICHEN an der aktuellen Cursorposition im Nachrichteneingabefeld ein, ohne den bestehenden Chat-Tab-Klick zu beeinflussen
- feat: 🔍 Globaler Such-Button in der Tab-Leiste – neuer 🔍-Button neben 📋 öffnet den QSO-Dialog im globalen Modus: Text-Suche und KI-Suche durchsuchen alle Direkt-QSOs gleichzeitig; Checkbox „Alle Direkt-QSOs durchsuchen" ist automatisch aktiviert und gesperrt; Tabs KI-Zusammenfassung und Verlauf sind im globalen Modus ausgeblendet
- feat: 🔍 KI-Suche über alle Direkt-QSOs – in der KI-Suche des QSO-Dialogs ist optional das Durchsuchen aller 1:1-QSOs möglich (Checkbox „Alle Direkt-QSOs durchsuchen"); Gruppen- und Broadcast-Nachrichten werden dabei ausgeschlossen
- feat: ⚙️ Node-Firmware & Hardware in Statusleiste – Firmware-Version und Hardware-Name des eigenen Nodes werden automatisch aus eingehenden
src_type:"node"-Paketen gelesen und in der Statusleiste angezeigt (⚙️ 4.35 · T-BEAM); Wert erscheint sobald das erste Node-Paket empfangen wird - feat: 🔧 Neue Variablen
{node-firmware}und{node-hw}– Firmware-Version und Hardware-Name des eigenen Nodes stehen in Auto-Reply, Bot-Befehlen, Bake-Text und Quick Texts als Platzhalter zur Verfügung; ergänzt in der Variablen-Referenztabelle in den Einstellungen - feat: 💬 Sequenznummer im Monitor – sobald der Node die Sequenznummer per Echo zurückmeldet, wird sie nachträglich neben der gesendeten Nachricht im Monitor angezeigt (
{411}); erleichtert die Zuordnung von ACK zu TX - fix: 💬 ACK-Zuordnung – Fallback auf 10 Minuten begrenzt – der Fallback-Match (älteste unbestätigte Nachricht an Sender) wurde auf Nachrichten der letzten 10 Minuten beschränkt; verhindert dass beim Neustart geladene alte Nachrichten fälschlicherweise neue ACKs konsumieren
- fix: 💬
AssignOutgoingSequence–SequenceNumber == "TX"wird berücksichtigt – beim Senden wird sofort"TX"als Sequenznummer gesetzt; die Suche nach dem passenden Echo-Paket schlug dadurch fehl (suchte nurnull); beide Werte werden jetzt abgeglichen - fix: 📱 iPad – Statusleiste nutzt volle Breite –
width:100%; box-sizing:border-boxverhindert dass Safari/iPad die Statusleiste schmaler als den Container rendert; 8px Toleranzpuffer im JS-Overflow-Check verhindert vorzeitiges Ausblenden von Elementen - fix: 🔊 iOS/iPad – Beep-Töne –
AudioContext-Entsperrung auftouchstart(statt nurclick) umgestellt; Kontext wird beim ersten Touch sofort erzeugt und entsperrt - fix: 🔊 iOS/iPad – Sprachausgabe –
speechSynthesisaus Hintergrund-Callbacks ist auf iOS durch Apple blockiert; TTS wird auf iOS/iPadOS übersprungen; Beep-Töne werden als Benachrichtigung verwendet
- feat: 🗺️ MH-Liste – Automatisches Löschen alter Einträge – neue Einstellung „MH-Liste: Max. Alter (Stunden)“; Einträge älter als die konfigurierte Anzahl Stunden werden automatisch gelöscht; bestehende Werte (Tage) werden in Stunden migriert
- perf: ⚙️ Einstellungen – Lazy Rendering – Section-Inhalte werden erst beim Öffnen gerendert; deutlich schnelleres Laden der Einstellungsseite
- perf: 📻 MH-Liste & Karte – neues
OnMhChange-Event; nur noch MH-relevante Updates; Karte 400 ms Debounce; QRZ-Abfragen nur für neue Rufzeichen - feat: 📊 Statusleiste responsive – passt sich dynamisch der verfügbaren Breite an; Elemente werden stufenweise ausgeblendet wenn der Platz nicht ausreicht (
ResizeObserver+MutationObserver) - fix: ⚙️ MH Max. Alter – Persistenz –
MhMaxAgeHourswurde nach Seitenwechsel auf 0 zurückgesetzt; Fehler inSettingsServicebehoben - fix: 🗺️ Karte – Reichweiten-Wolke – Convex Hull wurde nach MH-Update oder MH-Purge nicht neu berechnet;
UpdateMarkersAsync()ruft jetztsetCoverageautomatisch neu auf wenn die Anzeige aktiv ist
- feat: Bug fixes and optimizations.
- feat: 🔎 QSO dialog – History & Search without AI – the 🔎 icon on direct-chat tabs is now always shown when MySQL is active (not only when an AI summary exists); the modal opens a four-tab dialog: KI-Zusammenfassung, Verlauf, Suche, KI-Suche; History and Text Search work with MySQL only (no AI API key required); AI tabs are disabled and greyed out when AI is not configured
- feat: 🤖 KI-Zusammenfassung / KI-Suche locked when AI inactive – Summary and AI Search tabs are
disabledwith tooltip hint whenAi.Enabled = falseor no API key is set; guard added inSwitchModalTaband template to prevent bypassing; reopens on the History tab when AI is off - feat: 📋 Recent QSO partners – 📋 button next to the + tab button opens a flyout with the most recently contacted callsigns; populated from MySQL; clicking a row opens the chat tab directly
- feat: ✍️ Browser spell-check – message input field has
spellcheck="true"; browser underlines misspelled words when spell-check is enabled in browser settings - feat: 📤 Import / Export – Bot Commands, Group Labels, Quick Texts – each section in Settings has 📤 Export (browser download as
.json) and 📥 Import (replaces existing entries, with full error handling); filenames are editable before export; files can be shared between users - feat: 🔐 Backup & Restore – full encrypted backup of all settings (including passwords and API keys) as
MeshComWebDesk-settings.enc; AES-256-CBC + PBKDF2/SHA-256/100k iterations; wrong password and corrupted files produce clear error messages; page reloads automatically after successful restore; filename editable before export - fix: ⚙️ Settings – „Einstellungen gespeichert" moved – success message now appears directly above the Save button instead of at the top of the page; full width display corrected
- fix: 🤖 IsAiActive reads live settings –
IOptions<MeshcomSettings>replaced withIOptionsMonitorin Chat.razor;IsAiActivealways readsCurrentValueso re-enabling AI in Settings is reflected immediately without page reload - fix: 💾 QsoSummaryService – History/Search without AI –
GetHistoryAsyncandTextSearchAsyncnow use a newIsDbOnlyAvailable()check (MySQL configured, noai.Enabledrequirement) so Verlauf and Suche work even when KI is disabled - fix: 🔐 Backup – PBKDF2 on thread-pool – CPU-intensive key derivation moved to
Task.Run()to prevent blocking the SignalR heartbeat and disconnecting the browser - fix: 🔐 Backup Import – Blazor InputFile – replaced JS-interop
int[]file read (which exceeded the SignalR 32 KB message limit) with Blazor'sInputFilecomponent; connection no longer drops on file selection - docs: 📖 README – QSO dialog section fully rewritten; Import/Export and Backup & Restore documented; History, Text Search, AI Search and AI Summary documented as separate tabs; step-by-step AI setup guide added
- feat: 📡 Reichweite (Coverage) – Kontrast der gemessenen Reichweiten-Fläche erhöht (
fillOpacity0.12 → 0.35, Randlinie weight 2 → 3, opacity 0.65 → 0.95); Topo-Prognose (ungenau) vollständig entfernt - docs: 📖 README – Abschnitte für 🤖 KI / QSO-Zusammenfassung, 📡 Reichweiteberechnung (Convex Hull), 🗺️ KI-Stationsbeschreibung im Karten-Popup und Callsign-Suche ergänzt;
Ai-Konfigurationsblock und Architektur aktualisiert
- feat: 🟢 MQTT/DB Verbindungsstatus in der Statusleiste – MQTT- und Datenbankverbindung werden als farbige Status-Badges in der Statusleiste angezeigt; MQTT-Verbindung kann direkt in den Einstellungen über einen „Verbindung testen"-Button überprüft werden
- feat: 🔔 Statusleiste – einheitliche Badge-Darstellung – alle Statusleisten-Elemente (UDP, Ton, MQTT, DB) werden einheitlich als farbige Badges dargestellt; Glocke-/Ton-aus-Button ebenfalls als Badge; UDP-Badge zeigt nur Icon+Label, vollständiger Text als Tooltip
- feat: ☁️ Gateway-ACK mit Wolken-Symbol – ACK-Nachrichten vom Gateway werden mit einem Wolken-Symbol (☁️) dargestellt; ACK-Matching korrigiert (FirstOrDefault)
- feat: 📡 MQTT integration (Beta) – optionale Verbindung zu einem externen MQTT-Broker; publiziert eingehende Chat-Nachrichten, Positions-Baken und Telemetrie auf typisierten Topics (
{prefix}/broadcast,{prefix}/group/{group},{prefix}/dm/{callsign},{prefix}/position/{callsign},{prefix}/telemetry/{callsign}); optionaler Subscriber leitet{prefix}/send/#-Topics als ausgehende UDP-Nachrichten weiter; unterstützt Authentifizierung, TLS und Auto-Reconnect; alle{variable}-Platzhalter werden in Subscriber-Payloads expandiert - feat: 📝 MQTT Logging – aktivierbares/deaktivierbares MQTT-Diagnose-Logging in den Einstellungen
- feat: 🏷️ Group Labels – konfigurierbare Kurz- und Vollnamen für MeshCom-Gruppennummern; Kurzname wird im Monitor-Tab neben der Gruppe angezeigt; Gruppen einheitlich mit
#dargestellt; vorbelegt mit der offiziellen MeshCom-GRC-Gruppenliste (icssw.org); in den Einstellungen editierbar mit Zurücksetzen-Button - feat: 🚀 Automatische Update-Prüfung – beim Start wird die GitHub-Releases-API abgefragt; bei verfügbarer neuer Version erscheint ein wegklickbares Banner
- fix: 💬 QRZ-Vorabladen für Auto-Reply – QRZ-Daten werden vor dem Senden der Auto-Reply vorgeladen, damit
{dest-name}bei neuen Kontakten korrekt befüllt ist - fix: 📡 Broadcast-Routing – ausgehende Broadcast-Nachrichten und Baken werden korrekt im
*-Tab angezeigt (nicht mehr in#alle) - fix: 🔤 Tab-Key Normalisierung –
ResolveTabKeynormalisiert Callsigns einheitlich, damit Baken-Nachrichten im richtigen Tab erscheinen - fix: 🌐 Browser-Tab-Titel korrigiert auf „MeshCom WebDesk"
- fix: 🔧 MQTT ClientId – zufälliger Suffix verhindert Instanz-Konflikte beim Broker; Stack-Trace bei Connect-Fehler unterdrückt
- feat: 📡 MQTT integration – optional connection to an external MQTT broker;
- feat: 🏷️ Group Labels – configurable short and full names for MeshCom group numbers; shown in chat tab (short label below group number, full name as tooltip); pre-filled with official MeshCom GRC group list (icssw.org); editable in Settings with restore-defaults button
- feat: 🚀 Automatic update check – on startup the app queries the GitHub Releases API and shows a dismissible banner when a newer version is available
- fix: 🌐 Browser tab title corrected to "MeshCom WebDesk"
- feat: ⚡ Quick Texts – drag & drop reorder – buttons in the flyout can be reordered by drag & drop; new order saved immediately to
appsettings.override.json - feat: 🔧
{mylocator}variable – own QTH Maidenhead locator (from Node GPS) available in all template texts: Auto-Reply, Bot commands, Quick Texts and Beacon - feat: 💬 Variable expansion in send bar –
{variables}can be typed directly in the message input; Tab expands inline as preview; Send always expands automatically - perf: 📻 MH search debounce – same optimisation as monitor filter: 250 ms debounce, Enter applies immediately, clearing resets instantly
- fix: 📡 Broadcast TX uses
dst *– outgoing messages in the ALL channel now use*as destination (notCQCQCQ) matching the MeshCom protocol spec - fix:
⚠️ Remove::deepfrom global CSS –::deepis only valid in scoped.razor.cssfiles; removed fromapp.css
- feat: 💬 Draggable chat tabs – tabs can be reordered by drag & drop; order persisted in
localStorage - feat: 📻 MH – QTH Locator – Maidenhead locator (
JN48qn) shown below GPS coordinates in the MH table; also in the station card popup (with clickable OSM link for GPS coordinates) - feat: 🗺️ Map popup – GPS & QTH Locator – station popup now shows QTH locator and GPS coordinates as a clickable OSM link
- feat: 🔧
{locator}variable – QTH locator of the chat partner available in Auto-Reply, Bot commands and Quick Texts - feat: ⚙️ Beacon variables hint – all supported placeholders (
{version},{mycall},{date},{time}) shown inline below the beacon text field - feat: 💬 Timestamps – date (
dd.MM.yy) shown below time for messages not from today; row height unchanged - perf: 📡 Monitor filter debounce – filter input debounced (250 ms); Enter applies immediately; clearing the field resets instantly; results cached
- fix: 🌍 InvariantCulture for coordinates and InfluxDB –
OsmUrl,FormatCoordand all InfluxDB Line Protocol float fields now use.as decimal separator regardless of system locale
- feat: ⚡ Quick Texts – configurable one-click text buttons in the send bar; a ⚡ icon opens a flyout panel above the input; clicking a button loads the predefined text into the input field for review before sending; supports all
{variable}placeholders ({mycall},{callsign},{rssi},{snr},{hw},{route},{hops},{date},{time}, …); variable reference table with live examples in Settings → ⚡ Quick Texts; fully multilingual (DE / EN / IT / ES) - feat: 📻 MH – Station Card popup – hovering (desktop) or tapping (mobile) a callsign in the MH list shows a rich popup card with QRZ name/QTH, RSSI/SNR, battery, distance, firmware, last message preview, a 💬 Open Chat button and a 🔗 aprs.fi link
- feat: 🔧 MeshcomLookup – new hardware IDs – added hw_id
40(T5-EPAPER / LilyGo T5 E-Paper) and53(T-ETH-ELITE / LilyGo T-ETH-Elite S3 + SX1262 Ethernet) from MeshCom-Firmware v4.35p - fix: 📱 Send bar overflow on iOS Safari – added
width:100%,max-width:100%,min-width:0through the full flex chain (app-body→meshcom-container→upper-pane→tab-content);tab-baroverflow-x container no longer breaks parentoverflow:hiddenon iOS - fix: 📻 MH Station Card hover gap – removed 6 px gap between callsign and card (replaced with
::beforebridge element); elevatedz-indexso card appears above subsequent table rows
- feat: 🤖 Bot –
{srctype}and{srctype-label}variables – connection type (LoRa / UDP/Gateway / Node) available in all template texts - feat: 🤖 Bot –
--pingcommand – replies with RSSI, SNR, relay route and receive timestamp; barepingkeyword also accepted (case-insensitive) - feat: 🤖 Bot –
--echocommand – echoes back any text after the command - feat: ⚙️ Settings – Bot command test – dry-run preview of any command with optional sender callsign; all
{variable}placeholders expanded live - fix: 🤖 Bot –
{srctype}partial substitution –{srctype-label}now replaced before{srctype}to avoid partial match - fix: 🤖 Bot –
LastSrcTypenull from older persisted snapshots – graceful fallback to"lora"for stations loaded before the field was introduced
- feat: 📻 MH – per-row delete button – individual stations can be removed from the MH list with a ✕ button without clearing the whole list
- feat: 📻 MH – navigate to chat on tab open – clicking 💬 in the MH list now automatically navigates to the Chat page and activates the newly opened tab
- feat: 🔐 Encrypted sensitive settings –
MySqlConnectionString,InfluxToken,Qrz.PasswordandTelemetryApiKeyare now encrypted inappsettings.override.jsonusing the ASP.NET Core Data Protection API (dp:prefix);IPostConfigureOptions<MeshcomSettings>decrypts them transparently on load; plain-text values in existing files pass through unchanged (backward compatible); keys stored inDataPath/keys - feat: 📻 Watchlist – callsign alert system – configurable list of callsigns to watch; matching supports base-callsign wildcards (
DH1FRmatches all SSIDs) or exact SSID matching (DH1FR-1); triggers an ascending three-tone alert beep and a self-dismissing toast notification; per-type filters (MSG / POS / TEL / ACK); telemetry alerts off by default; respects the global mute toggle - feat: 📻 Watchlist – configurable toast duration – new
WatchAlertMinutessetting (default 5, min 1) configurable in Settings → 📻 Watchlist - feat: ⚙️ Settings – collapsible sections – all 13 sections individually collapsible; all start collapsed by default; expanded/collapsed state saved to
localStorageand restored on every visit - feat: 🔗 Webhook – telemetry fields in payload –
temp1,temp2,humidity,pressureadded toeventType="telemetry"payload - fix: ⚙️ Settings – Watchlist persistence –
WatchCallsignsandWatchOn*flags were missing fromSettingsService
- feat: 🤖 Bot command system – incoming direct messages starting with
--(or—em dash) are interpreted as bot commands; built-in:--help,--version,--time,--mh; fully configurable user-defined commands with{variable}placeholder support in Settings → 🤖 Bot;IBotCommandinterface for developer extensions - feat: ↩️ Auto-Reply –
{route}and{hops}template variables – relay path and hop count from the sender's last message are now available inAutoReplyText - feat: ⚙️ Auto-Reply – collapsible variable reference table in Settings; all supported
{variable}placeholders with live example values shown inline - fix: ↩️ Auto-Reply ordering – auto-reply is now sent after the incoming message is stored in the tab so the reply always appears below the trigger message in the conversation
- fix: ↩️ Auto-Reply first-contact data –
UpdateMhListis called beforeGetOrCreateTabso RSSI, relay path and hardware data from the first message are immediately available toExpandVariables - fix: 🤖 Bot – em dash support – MeshCom clients and mobile keyboards often convert
--to—(U+2014); both variants now accepted as command prefix - fix: 🤖 Bot – silent exception handling –
HandleBotCommandAsyncwrapped intry/catchso errors are always logged and never swallowed silently - fix: 🤖 Bot / 📡 Beacon – auto-split – replies and beacon texts longer than 149 characters are automatically split into multiple packets (2 s pause between parts); previously they were silently dropped
- fix: 💾 Settings –
TimeOffsetHourspersistence – this field was missing fromSettingsService.SaveMeshcomSettingsAsyncand was reset to 0 on every UI save - fix: ⚙️ Settings –
WeatherRolepersistence –WeatherRolewas not written to the override file when saving telemetry mapping via the UI - fix: 🗺️ Map – direct-link lines – corrected detection logic so direct (0-hop) connections are drawn reliably; broadcast replies and ACKs also count as confirmed direct links
- perf: 💬 Chat –
SendBarextracted – input bar moved to its own component (SendBar.razor) to isolate re-renders and prevent the entire chat from re-rendering on every keystroke
- feat: 🗺️ Map – relay path lines – lines between relaying stations drawn from incoming relay paths and ACK return paths; colour-coded by hop count
- feat: 🗺️ Map – own station popup – own position marker now shows callsign, own GPS source, and own telemetry values (temperature, humidity, pressure) when available
- feat: 🗺️ Map – thermometer icon on own station marker when telemetry has been sent recently
- feat: 📊 Explicit
WeatherRoleper telemetry mapping entry – each entry can declare"temp","humidity"or"pressure"role explicitly; unit-based auto-detection is the fallback for older configs - fix: 💬 Chat – monitor visibility persisted across navigation – monitor collapsed/expanded state is now preserved when navigating away and back
- fix: 💬 Chat – splitscreen jump on navigation return eliminated
- fix: 💬 Chat – batch
OnAfterRenderAsyncre-renders; snapshot tabs filtered by group whitelist on load - fix: ⚙️ Settings – privacy warning labels cleaned up; DB-insert notice kept only in the database section
- perf: 📻 MH – QRZ data loaded from cache synchronously on first render – no more delayed flicker when returning to the MH page
- feat: 🔗 Clickable URLs in chat & monitor –
http://andhttps://URLs in incoming messages are automatically rendered as clickable links; clicking shows a confirmation dialog before opening the external page in a new tab (works correctly in Blazor Server via capture-phase JS handler) - fix: 🗺️ Map – Leaflet self-hosted – Leaflet 1.9.4 is now served from
wwwroot/lib/leaflet/instead of theunpkg.comCDN; eliminates map init errors when the server has no internet access or the CDN is slow to respond (e.g. Docker / LAN-only setups) - fix: 🐳 Docker – browser auto-open warning suppressed –
ENV DOTNET_RUNNING_IN_CONTAINER=trueadded toDockerfile;[WRN] Could not open browserno longer appears in Docker logs - perf: ⚡ QRZ cache – no redundant async calls on navigation – new
QrzService.TryGetCached()distinguishes "never queried" from "queried, not found"; components skip the asyncLookupAsyncpath for all already-known callsigns on every re-mount - docs: 📖 Multilingual node-connection guides – new
docs/node-connection-{de,en,it,es}.mdwith step-by-step EXTUDP setup; in-app help banner added to the Connection section in Settings
- feat: 📱 QRZ-Name im Tab-Button – der Vorname des Operators erscheint als kleiner Untertitel direkt im Chat-Tab-Button (immer sichtbar, kein Hover nötig)
- feat: 🎟️ Touch-Display-Support – QRZ-Vorname wird als festes
· Name-Badge inline bei jedem Rufzeichen angezeigt (Chat-Nachrichten und Monitor-Zeilen), ohne Hover-Interaktion - perf: ⚡ Nicht-blockierendes QRZ-Laden – Seiten rendern sofort; QRZ-Daten werden im Hintergrund nach dem ersten Render nachgeladen (
OnAfterRenderAsyncstattOnInitializedAsync) - perf: ⚡ Doppelter Re-Render entfernt – zweites
StateHasChangedinOnChatChangednur noch wenn tatsächlich neue QRZ-Daten hinzukamen - perf: ⚡ Karte synchron –
UpdateMarkersAsyncverwendet neuen synchronenQrzService.GetCached()statt sequentiellerawait-Kette - feat: 💾 QRZ-Cache persistent – Lookups werden in
qrz-cache.jsonimDataPathgespeichert und beim nächsten App-Start automatisch geladen; jedes Rufzeichen wird nur noch einmal abgefragt (auch über Neustarts hinweg)
- feat: 🔍 QRZ.com callsign lookup – optional integration with the QRZ.com XML API; free account returns operator first name + home QTH; session key fetched and refreshed automatically; results cached in memory per session
- 📻 MH list – dedicated Name / Location column + hover tooltip on every callsign
- 💬 Chat – QRZ data shown as hover tooltip on tab buttons, chat-message callsigns and monitor From/To callsigns
- 🗺️ Map – callsign popup shows operator name and QTH below the bold callsign
- Configured in Settings → 🔍 QRZ.com; test button + cache-clear button included; on/off live without restart
- fix: 🐳 Docker – HTTPS outbound connections – added
ca-certificatestodebian:bookworm-slimruntime image; fixes "The SSL connection could not be established" for all outgoing HTTPS calls (QRZ.com, Webhook, InfluxDB Cloud etc.)
- feat: 📡 Resizable monitor pane – drag handle between chat and monitor lets the user resize the split freely (mouse + touch); last position persisted in
localStorage - fix: DH1FR greeting is now only sent when the tab is opened by an incoming message (not when opened manually with
+) - fix: 🔒 HTTP→HTTPS redirect disabled in non-
LanHttpsenvironments –POST /api/telemetryno longer redirects Home Assistant to HTTPS - feat: 🚀 Browser auto-opens at
http://localhost:5162when launched directly as an executable (skipped in Docker / Windows service / systemd) - feat: 🎨 ASCII startup banner now shows MESHCOM + WebDesk both in block-letter style
- feat: 📁 Portable default paths –
data/,logs/anddata/keys/stored next to the executable; no hard-codedC:\Temppaths - docs:
Screenshot_shell.pngadded; 🚀 Startup section added to README
- fix: 📱 iOS PWA – Leeraum unten (nur in PWA standalone, nicht im Browser) – Ursache war
height: -webkit-fill-availableinapp.css, das im PWA-Modus eine kleinere Höhe als100dvhliefert; ersetzt durchposition: fixed; inset: 0(von Apple empfohlene Methode für PWA Full-Screen-Layouts) - fix: 📱 Doppelte
.app-layout-Definition ausapp.cssentfernt – nur noch inMainLayout.razor.cssdefiniert
- fix: 📱 iOS PWA – Leeraum unten (iPhone + iPad) –
padding-bottom: env(safe-area-inset-bottom)auf.app-layoutmitbox-sizing: border-boxverschoben; wirkt jetzt zuverlässig auf allen iOS-Geräten - fix: 📱 iOS PWA – Titel zu groß – Breakpoint von
390pxauf480pxerhöht (trifft jetzt alle iPhones inkl. 14 Pro / 15 Plus); Schriftgröße auf11pxreduziert
- fix: 📱 iOS PWA – Safe Area Insets – App startete zu weit oben (Inhalt unter Statusleiste) und ließ unten Platz frei;
env(safe-area-inset-top/bottom)korrekt in Header-Höhe und Body eingebunden - fix: 📱 iOS PWA – Titel zu groß – Auf kleinen iPhones (SE, mini) war „MeshCom WebDesk" zu groß; neue Media Query für
≤390pxreduziert Schriftgröße und blendet Versionsnummer aus
- feat: 🐳
docker-compose.https.yml– HTTPS overlay für Docker Compose; HTTP bleibt indocker-compose.ymlunverändert; beide Dateien werden mit-fkombiniert - feat:
scripts/start-https.sh– Wrapper-Skript prüft obcerts/meshcom-lan.pfxexistiert und gibt klare Fehlermeldung wenn nicht; startet danndocker compose -f ... -f ... - fix:
docker-compose.ymlEncoding-Probleme in Kommentaren behoben
- feat: 🔒
scripts/create-lan-cert.sh– Linux/Docker equivalent of the Windows PowerShell cert script; usesopenssl, auto-detects LAN IP, exports PFX + CRT - docs: 🔒 HTTPS Docker – vollständige Anleitung für Zertifikatserstellung auf Linux und Einbindung via Docker Compose volume mount
- docs: Architecture updated with
create-lan-cert.sh
- fix: 🗺️ Map – 50km Button – "An unhandled error has occurred" behoben;
L.circle.getBounds()ersetzt durch manuelle Haversine-Bounding-Box (funktioniert ohne map context) - docs: 🔒 HTTPS & Zertifikat – vollständige Schritt-für-Schritt-Anleitung (Windows, iOS, Android) in der README
- docs: 📱 PWA – erklärt was PWA ist, wie man es auf iPhone/iPad/Android/Windows installiert
- feat: 🔒 HTTPS for LAN –
scripts/create-lan-cert.ps1generates a self-signed cert with IP SAN; newlan-httpslaunch profile binds HTTP :5162 + HTTPS :5163 simultaneously; required for PWA installation on Android / iPad / iPhone - feat:
appsettings.LanHttps.json– dedicated Kestrel HTTPS endpoint config; HTTP stays unaffected
- feat: 🗺️ Map – Zoom-Buttons – neue Button-Leiste über der Karte:
📡 Alle(alle Stationen),🌍 Europa,📍 50 km(Radius um eigene Position) - fix: 🗺️ Map – Auto-Zoom entfernt – Karte zoomt nur noch einmalig beim ersten Laden mit Daten; danach bleibt der Ausschnitt beim Eingang neuer Positionen stabil
- fix: 🗺️ Map – Rufzeichen-Kontrast verbessert – dunkles halbtransparentes Pill-Label statt Text-Shadow, jetzt auf hellen und dunklen Kartenbereichen lesbar
- fix: 🗺️ Map – "Noch keine GPS-Position" wurde angezeigt obwohl bereits Stationen auf der Karte eingetragen waren –
StateHasChanged()wird jetzt nachUpdateMarkersAsync()aufgerufen - fix: 🗺️ Map – Station-Strip ersetzt durch kompakte einzeilige Info-Leiste (
📡 N Station(en) · 📍 Rufzeichen) – bei vielen Stationen keine unleserliche Liste mehr
- feat: 🗺️ Live Map – interactive Leaflet.js + OpenStreetMap map at
/map; APRS-style circle markers colour-coded by RSSI; own position as gold diamond; auto-fit bounds; real-time updates - feat: 🔗 Webhook – HTTP POST fire-and-forget on incoming messages, position beacons and/or telemetry; configurable URL and triggers in Settings → 🔗 Webhook
- feat: 📱 PWA – installable as Progressive Web App ("Add to Home Screen");
manifest.webmanifest, minimal service worker, Apple meta tags, custom antenna SVG icon - feat: ☕ Donation link – PayPal link on the About page (
paypal.me/DH1FR) - docs: Architecture, Configuration and Changelog updated
- feat: 🗄️ Database integration – optional MySQL/MariaDB or InfluxDB 2 sink writes every monitor entry to an external database
- feat: Settings → Datenbank – provider dropdown, connection fields, "Test connection" button with automatic DB/table/bucket creation
- feat: Optional insert logging (
LogInserts) – logs the full SQLINSERTat Information level; privacy notice shown in Settings UI - feat: Message length guard – character counter
X/149in the chat input,maxlength="149"enforced in the browser and server-side warning log when limit is exceeded - docs: 🔒 Privacy / Datenschutz section added to README – covers log files, database storage, DB insert log and persistence; includes recommendations for GDPR-compliant operation
- feat:
TimeOffsetHourssetting – configurable UTC offset for timestamp display (supports half-hour offsets, e.g.5.5for IST) - fix: Docker container now runs with
TZ=Europe/Berlinby default (tzdatainstalled) –DateTime.Nowcorrectly reflects MEZ/MESZ including automatic DST switching;TimeOffsetHours = 0stays correct in Docker
- feat:
{version}placeholder supported inAutoReplyTextandBeaconText– replaced with the running application version at send time - feat: Test button for Auto-Reply in Settings – send to any callsign immediately without waiting for an incoming message
- feat: Application version shown in the navigation bar next to the app title
- feat: UDP registration packet (
{"type":"info",...}) sent on startup now appears in the monitor feed as aSYS TXentry - feat: Automatic greeting sent to
DH1FR-2whenever a new chat tab for that callsign is opened (incoming or manual)
- feat:
{version}placeholder supported inAutoReplyTextandBeaconText– replaced with the running application version at send time - feat: Test button for Auto-Reply in Settings – send to any callsign immediately without waiting for an incoming message
- feat: Application version shown in the navigation bar next to the app title
- feat: UDP registration packet (
{"type":"info",...}) sent on startup now appears in the monitor feed as aSYS TXentry - feat: Automatic greeting sent to
DH1FR-2whenever a new chat tab for that callsign is opened (incoming or manual)
- fix: UDP registration packet no longer sends
dstormsgfields – prevents broadcasting "info" text over the mesh and gateway on startup - feat: Author e-mail address
dh1fr@darc.deadded to About page
- Telemetry sender with HTTP POST endpoint (
POST /api/telemetry) - Live telemetry preview and instant-send button in Settings
- Home Assistant integration guide
- Auto-split of long telemetry messages (
TM1:/TM2:) - Configurable telemetry schedule hours
- Periodic beacon (Bake) with status indicator
- State persistence (chat tabs, MH list, monitor, own GPS)
- Browser GPS button for own position
- Audio notification for incoming direct messages
- Full relay path display in monitor
- ACK delivery tracking (
⏳/✓/✓✓/☁️/☁️✓) - MH list with GPS distance, battery level and hardware badge
- Web-based Settings editor
- Multi-language UI (de / en / it / es)
- Initial release



