Zum Inhalt

Architektur – Meshhessen Client

Stand: v1.6.4.7 · abgeleitet aus dem Quellcode (Services/, Models/, Fenster im Projektroot). Die Kernmuster (geteilter Protokoll-Service, IConnectionService-Abstraktion, Event-Fluss) sind seit v1.5 stabil; seither kamen u. a. Vektorkarte (WebView2/MapLibre), Telemetrie-Dashboard, rf_links/Triangulation und TerrainService hinzu.

Der Client ist schichtweise aufgebaut mit MainWindow als zentraler Drehscheibe. Empfangsdaten fließen von unten (Gerät) nach oben (UI) über Events, Befehle den umgekehrten Weg über SendXxxAsync-Methoden. Zwei Muster prägen alles: der geteilte MeshtasticProtocolService (ein Verbindungszustand für die ganze App) und die IConnectionService-Abstraktion (Serial/BLE/TCP komplett austauschbar).

Modul- und Kommunikationsdiagramm

flowchart TB

  %% ---------- External ----------
  subgraph EXT["🌐 Externe Welt (außerhalb des Prozesses)"]
    direction LR
    DEV["Meshtastic-Gerät<br/>USB · BLE · TCP"]
    RTILE["tile.meshhessenclient.de<br/>Raster-Kacheln"]
    VTILE["vectortile.meshhessenclient.de<br/>Styles · MVT · Glyphs · Sprites"]
    MQTTB["MQTT-Broker"]
    PHONE["Handy-Apps<br/>Android / iOS"]
  end

  %% ---------- Transport ----------
  subgraph XPORT["🔌 Transport – IConnectionService"]
    direction LR
    SER["SerialConnectionService<br/>(→ SerialPortService)"]
    BLE["BluetoothConnectionService"]
    TCP["TcpConnectionService"]
  end

  %% ---------- Protocol core ----------
  subgraph PROTO["⚙️ Protokoll-Kern"]
    PROTOSVC["MeshtasticProtocolService<br/>Protobuf en/decode · ~25 Events · SendXxxAsync"]
    PKI["PkiDecryptionService<br/>X25519 + AES-256-CCM"]
    NKS["NodeKeyService<br/>Public-Key-Auflösung (CSV)"]
  end

  %% ---------- Bridges ----------
  subgraph BRIDGE["🔀 Server-Brücken"]
    direction LR
    VNODE["VirtualNodeService<br/>TCP-Server :4404"]
    MQTTP["MqttProxyService"]
  end

  %% ---------- Hub ----------
  HUB["🖥️ MainWindow<br/>ObservableCollections · Event-Verdrahtung · Fenster-Spawner · besitzt Karte"]

  %% ---------- Windows ----------
  subgraph WIN["🪟 Fenster & Dialoge (≈23)"]
    direction LR
    DM["DirectMessagesWindow"]
    CFG["NodeConfig / RemoteAdmin"]
    TRW["Traceroute / SegmentSnr"]
    TEL["Telemetry / Dashboard"]
    MISC["NodeInfo · MapPicker · TDeck …"]
  end

  %% ---------- Persistence ----------
  subgraph PERSIST["💾 Persistenz (querschnittlich)"]
    direction LR
    SET["SettingsService<br/>*.ini"]
    TDB["TelemetryDatabaseService<br/>telemetry.db"]
    MDB["MessageDbManager<br/>+ MessageDatabaseService"]
    LOG["Logger · MessageLogger · LocationLogger"]
  end

  %% ---------- Map ----------
  subgraph MAP["🗺️ Karten-Subsystem (zwei Renderer)"]
    direction LR
    RAS["Mapsui MapControl (Raster)<br/>BruTile ITileProvider"]
    VEC["WebView2 + map.html (Vektor)<br/>MapLibre GL JS"]
    VCACHE["VectorTileCacheService<br/>Interceptor + Disk-Cache"]
    MTOOLS["MapOverlayRegistry · VectorPackageDownloader<br/>TileDownloader · TileMigration · TDeckTile/Drive"]
  end

  %% ---------- Edges: inbound / outbound ----------
  DEV -- "byte[]-Frames" --> XPORT
  XPORT -- "DataReceived (byte[])" --> PROTOSVC
  PROTOSVC -- "WriteAsync" --> XPORT
  XPORT -- "USB/BLE/TCP" --> DEV

  PROTOSVC --- PKI
  PROTOSVC --- NKS

  PROTOSVC -- "~25 typisierte Events" --> HUB
  HUB -- "SendXxxAsync" --> PROTOSVC

  %% ---------- Bridges ----------
  PROTOSVC -- "RawFrameReceived (via MainWindow)" --> VNODE
  VNODE -- "App-Kommandos · WriteAsync" --> XPORT
  VNODE <-->|TCP| PHONE
  PROTOSVC <-->|MqttClientProxyMessage| MQTTP
  MQTTP <-->|MQTT| MQTTB

  %% ---------- Hub distributes ----------
  HUB -- "geteilter ProtocolService + DB" --> WIN
  HUB --> PERSIST
  HUB --> MAP

  %% ---------- Persistence detail ----------
  PROTOSVC -- "Telemetrie schreiben" --> TDB
  TEL -- "lesen" --> TDB
  DM -- "lesen/schreiben" --> MDB

  %% ---------- Map detail ----------
  VEC <-->|"JS-Bridge (postMessage)"| HUB
  VEC -- "HTTP-Requests" --> VCACHE
  VCACHE -- "UA + Cache" --> VTILE
  RAS -- "Kacheln" --> RTILE

  %% Rahmenfarben pro Schicht – lesbar in GitHub Hell- und Dunkelmodus
  classDef ext stroke:#7a6cd6,stroke-width:2px;
  classDef xport stroke:#b26a1f,stroke-width:2px;
  classDef proto stroke:#0e9cb8,stroke-width:2px;
  classDef bridge stroke:#e2683c,stroke-width:2px;
  classDef hub stroke:#4a86ee,stroke-width:3px;
  classDef persist stroke:#c25689,stroke-width:2px;
  classDef map stroke:#3aa96c,stroke-width:2px;
  classDef win stroke:#4a86ee,stroke-width:2px;

  class DEV,RTILE,VTILE,MQTTB,PHONE ext;
  class SER,BLE,TCP xport;
  class PROTOSVC,PKI,NKS proto;
  class VNODE,MQTTP bridge;
  class HUB hub;
  class DM,CFG,TRW,TEL,MISC win;
  class SET,TDB,MDB,LOG persist;
  class RAS,VEC,VCACHE,MTOOLS map;

Kommunikationswege im Detail

Weg Auslöser & Nutzlast Was passiert
Gerät → Transport → Protokoll Rohframe als byte[] über DataReceived ProtocolService dekodiert Protobuf, entschlüsselt PKI-DMs (PkiDecryptionService), löst Keys auf (NodeKeyService), schreibt Telemetrie in die DB
Protokoll → MainWindow ~25 EventHandler<T> (z. B. MessageReceived) Handler aktualisieren die ObservableCollections; WPF-Databinding rendert die Listen; Kartenlayer bekommen neue Node-Pins
UI → Protokoll → Gerät UI-Aktion ruft SendXxxAsync(...) Protobuf-Encode → IConnectionService.WriteAsync → Transport → Gerät (auch NodeConfig/RemoteAdmin über den geteilten ProtocolService)
Protokoll ↔ VirtualNode ↔ Handy RawFrameReceived / TCP-Bytes Jeder Frame vom physischen Node geht an die App; App-Kommandos schreibt VirtualNode zurück auf die physische Verbindung
Protokoll ↔ MqttProxy ↔ Broker MqttClientProxyMessage Gerät nutzt den PC als MQTT-Uplink; bidirektional durchgereicht
MainWindow → Fenster Konstruktor bekommt ProtocolService + DB-Manager Unterfenster teilen sich Verbindung und Daten – kein eigener Socket, kein Doppel-State
WebView2 ↔ VectorTileCache Alle HTTP-Requests der Karten-Seite Interceptor beantwortet Style/MVT/Glyphs aus dem C#-HttpClient (mit UA) + permanentem Disk-Cache; online besuchte Gebiete sind offline verfügbar
Persistenz · querschnittlich Direktzugriff aus Protokoll & UI SettingsService (Start), TelemetryDB (Schreiben durch Protokoll / Lesen durch Dashboard), Nachrichten-DB (DM-Fenster), Logger überall

Die beiden Kartenrenderer laufen parallel und werden per Einstellung umgeschaltet (Raster = Standard). Beide teilen sich denselben lokalen Cache-Ordner, sodass ein Wechsel nahtlos ist. Models (NodeInfo, MessageItem, ChannelInfo, TracerouteResult, DashboardModels …) sind reine DTOs und werden schichtübergreifend geteilt.