EN

Companion-Protokoll

Companion-Protokoll

  • Zuletzt aktualisiert: 2026-03-08
  • Protokollversion: Companion-Firmware v1.12.0+
HINWEIS: Dieses Dokument befindet sich noch in Entwicklung. Einige Informationen könnten ungenau sein.

Dieses Dokument bietet eine umfassende Anleitung zur Kommunikation mit MeshCore-Geräten über Bluetooth Low Energy (BLE).

Es ist plattformunabhängig und kann für Android, iOS, Python, JavaScript oder jede andere Plattform verwendet werden, die BLE unterstützt.

Offizielle Bibliotheken

Eine Liste der bestehenden Bibliotheken für das MeshCore Companion-Protokoll finden Sie in den folgenden Repositories.

Wichtiger Sicherheitshinweis

Alle Geheimnisse (Secrets), Hashes und kryptografischen Werte, die in dieser Anleitung gezeigt werden, sind nur Beispielwerte.

  • Alle Hex-Werte, öffentlichen Schlüssel und Hashes dienen ausschließlich Demonstrationszwecken.
  • Verwenden Sie niemals Beispielgeheimnisse (Secret) in der Produktion.
  • Generieren Sie immer neue kryptografisch sichere Zufallsgeheimnisse (Secret).
  • Bitte implementieren Sie angemessene Sicherheitspraktiken in Ihrer Implementierung.
  • Diese Anleitung dient ausschließlich der Protokolldokumentation.

Inhaltsverzeichnis

  1. BLE Verbindung
  2. Paketstruktur
  3. Befehle
  4. Kanalverwaltung
  5. Nachrichtenverarbeitung
  6. Antwort-Parsing
  7. Beispielimplementierungsfluss
  8. Best Practices
  9. Fehlerbehebung
---

BLE Verbindung

Dienst und Merkmale

MeshCore Companion-Geräte stellen einen BLE-Dienst (Bluetooth Low Energy Dienst) mit den folgenden UUIDs (Universally Unique Identifiers) bereit:

  • Dienst-UUID: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
  • RX-Merkmal (App → Firmware): 6E400002-B5A3-F393-E0A9-E50E24DCCA9E
  • TX-Merkmal (Firmware → App): 6E400003-B5A3-F393-E0A9-E50E24DCCA9E

Verbindungsschritte

  1. Nach Geräten suchen
- Suchen Sie nach BLE-Geräten, die die MeshCore Service UUID bewerben. - Filtern Sie optional nach Gerätenamen (enthält typischerweise das Präfix „MeshCore“). - Notieren Sie die MAC-Adresse des Geräts für die erneute Verbindung.
  1. Mit GATT verbinden
- Verbinden Sie sich mit dem Gerät über die gefundene MAC-Adresse (Media Access Control-Adresse). - Warten Sie, bis die Verbindung hergestellt ist.
  1. Dienste und Merkmale entdecken
- Entdecken Sie den Dienst mit der UUID 6E400001-B5A3-F393-E0A9-E50E24DCCA9E. - Entdecken Sie das RX-Merkmal 6E400002-B5A3-F393-E0A9-E50E24DCCA9E. - Ihre App schreibt darauf, die Firmware liest davon. - Entdecken Sie das TX-Merkmal 6E400003-B5A3-F393-E0A9-E50E24DCCA9E. - Die Firmware schreibt darauf, Ihre App liest davon.
  1. Benachrichtigungen aktivieren
- Abonnieren Sie Benachrichtigungen auf dem TX-Merkmal, um Daten von der Firmware zu empfangen.
  1. Erste Befehle senden
- Senden Sie CMD_APP_START, um Ihre App bei der Firmware zu identifizieren und Funkeinstellungen zu erhalten. - Senden Sie CMD_DEVICE_QUERY, um Geräteinformationen abzurufen und unterstützte Protokollversionen auszuhandeln. - Senden Sie CMD_SET_DEVICE_TIME, um die Firmware-Uhrzeit einzustellen. - Senden Sie CMD_GET_CONTACTS, um alle Kontakte abzurufen. - Senden Sie CMD_GET_CHANNEL mehrfach, um alle Kanalslots abzurufen. - Senden Sie CMD_SYNC_NEXT_MESSAGE, um die nächste in der Firmware gespeicherte Nachricht abzurufen. - Richten Sie Listener für Push-Codes ein, wie z.B. PUSH_CODE_MSG_WAITING oder PUSH_CODE_ADVERT. - Weitere Informationen zu Befehlen finden Sie im Abschnitt Befehle. Hinweis: MeshCore-Geräte können sich nach Inaktivität trennen. Implementieren Sie eine automatische Wiederverbindungslogik mit exponentiellem Backoff (Staffelung der Wartezeit).

BLE Schreibtyp

Beim Schreiben von Befehlen an das RX-Merkmal geben Sie den Schreibtyp an:

  • Schreiben mit Antwort (Standard): Wartet auf Bestätigung vom Gerät.
  • Schreiben ohne Antwort: Schneller, aber ohne Bestätigung.
Plattformspezifisch:
  • Android: Verwenden Sie BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT oder WRITE_TYPE_NO_RESPONSE.
  • iOS: Verwenden Sie CBCharacteristicWriteType.withResponse oder .withoutResponse.
  • Python (bleak): Verwenden Sie write_gatt_char() mit response=True oder False.
Empfehlung: Verwenden Sie für Zuverlässigkeit das Schreiben mit Antwort.

MTU (Maximale Übertragungseinheit)

Die Standard-BLE-MTU beträgt 23 Bytes (20 Bytes Nutzdaten). Für größere Befehle wie SET_CHANNEL (50 Bytes) müssen Sie möglicherweise:

  1. Eine größere MTU anfordern: Fordern Sie eine MTU von 512 Bytes an, falls unterstützt.
- Android: gatt.requestMtu(512) - iOS: peripheral.maximumWriteValueLength(for:) - Python (bleak): MTU wird automatisch ausgehandelt.

Befehlssequenzierung

Kritisch: Befehle müssen in der richtigen Reihenfolge gesendet werden:
  1. Nach der Verbindung:
- Warten Sie, bis die BLE-Verbindung hergestellt ist. - Warten Sie, bis Dienste/Merkmale entdeckt wurden. - Warten Sie, bis Benachrichtigungen aktiviert sind. - Jetzt können Sie sicher Befehle an die Firmware senden.
  1. Befehl-Antwort-Abgleich:
- Senden Sie einen Befehl nach dem anderen. - Warten Sie auf eine Antwort, bevor Sie einen weiteren Befehl senden. - Verwenden Sie ein Timeout (üblicherweise 5 Sekunden). - Gleichen Sie die Antwort mit dem Befehl nach Typ ab (z.B. CMD_GET_CHANNELRESP_CODE_CHANNEL_INFO).

Befehlswarteschlangenverwaltung

Für einen zuverlässigen Betrieb implementieren Sie eine Befehlswarteschlange.

Warteschlangenstruktur:
  • Verwalten Sie eine Warteschlange mit ausstehenden Befehlen.
  • Verfolgen Sie, welcher Befehl gerade auf eine Antwort wartet.
  • Senden Sie den nächsten Befehl erst, nachdem eine Antwort empfangen oder ein Timeout aufgetreten ist.
Fehlerbehandlung:
  • Bei einem Timeout: Löschen Sie den aktuellen Befehl, verarbeiten Sie den nächsten in der Warteschlange.
  • Bei einem Fehler: Protokollieren Sie den Fehler, löschen Sie den aktuellen Befehl, verarbeiten Sie den nächsten.
---

Paketstruktur

Das MeshCore-Protokoll verwendet ein binäres Format mit folgender Struktur:

  • Befehle: Werden von der App über das RX-Merkmal an die Firmware gesendet.
  • Antworten: Werden von der Firmware über TX-Merkmal-Benachrichtigungen empfangen.
  • Alle Mehrbyte-Ganzzahlen: Little-Endian-Byte-Reihenfolge (außer CayenneLPP, das Big-Endian ist).
  • Alle Zeichenketten: UTF-8-Codierung.
Die meisten Pakete folgen diesem Format:
CLI
[Pakettyp (1 Byte)] [Daten (variable Länge)]

Das erste Byte gibt den Pakettyp an (siehe Antwort-Parsing).

---

Befehle

1. App-Start

Zweck: Initialisiert die Kommunikation mit dem Gerät. Muss nach der Verbindung zuerst gesendet werden. Befehlsformat:
CLI
Byte 0: 0x01
Bytes 1-7: Reserviert (wird derzeit von der Firmware ignoriert)
Bytes 8+: Anwendungsname (UTF-8, optional)
Beispiel (hexadezimal):
CLI
01 00 00 00 00 00 00 00 6d 63 63 6c 69
Antwort: PACKET_SELF_INFO (0x05)

---

2. Geräteabfrage

Zweck: Abfragen von Geräteinformationen. Befehlsformat:
CLI
Byte 0: 0x16
Byte 1: 0x03
Beispiel (hexadezimal):
CLI
16 03
Antwort: PACKET_DEVICE_INFO (0x0D) mit Geräteinformationen

---

3. Kanalinfo abrufen

Zweck: Abrufen von Informationen über einen bestimmten Kanal. Befehlsformat:
CLI
Byte 0: 0x1F
Byte 1: Kanalindex (0-7)
Beispiel (Kanal 1 abrufen):
CLI
1F 01
Antwort: PACKET_CHANNEL_INFO (0x12) mit Kanaldetails

---

4. Kanal einstellen

Zweck: Erstellt oder aktualisiert einen Kanal auf dem Gerät. Befehlsformat:
CLI
Byte 0: 0x20
Byte 1: Kanalindex (0-7)
Bytes 2-33: Kanalname (32 Bytes, UTF-8, mit Nullen aufgefüllt)
Bytes 34-49: Geheimnis (16 Bytes)
Gesamtlänge: 50 Bytes Kanalindex:
  • Index 0: Reserviert für öffentliche Kanäle (kein Geheimnis).
  • Indizes 1-7: Verfügbar für private Kanäle.
Kanalname:
  • UTF-8-codiert.
  • Maximal 32 Bytes.
  • Mit Null-Bytes (0x00) aufgefüllt, falls kürzer.
Geheimnis-Feld (16 Bytes):
  • Für private Kanäle: 16-Byte-Geheimnis.
  • Für öffentliche Kanäle: Alle Nullen (0x00).
Beispiel (Erstellen eines Kanals "YourChannelName" an Index 1 mit Geheimnis):
CLI
20 01 53 4D 53 00 00 ... (Name auf 32 Bytes aufgefüllt)
    [16 Bytes Geheimnis]
Hinweis: Die 32-Byte-Geheimnis-Variante wird nicht unterstützt und gibt PACKET_ERROR zurück. Antwort: PACKET_OK (0x00) bei Erfolg, PACKET_ERROR (0x01) bei Fehler.

---

5. Kanalnachricht senden

Zweck: Sendet eine Textnachricht an einen Kanal. Befehlsformat:
CLI
Byte 0: 0x03
Byte 1: 0x00
Byte 2: Kanalindex (0-7)
Bytes 3-6: Zeitstempel (32-Bit Little-Endian Unix-Zeitstempel, Sekunden)
Bytes 7+: Nachrichtentext (UTF-8, variable Länge)
Zeitstempel: Unix-Zeitstempel in Sekunden (32-Bit vorzeichenlose Ganzzahl, Little-Endian). Beispiel (sendet "Hello" an Kanal 1 mit Zeitstempel 1234567890):
CLI
03 00 01 D2 02 96 49 48 65 6C 6C 6F
Antwort: PACKET_MSG_SENT (0x06) bei Erfolg

---

6. Kanal-Datagramm senden

Zweck: Sendet ein binäres Datagramm an einen Kanal. Im Gegensatz zu Kanal-Textnachrichten tragen Datagramme keine integrierte Absenderidentität und keinen Zeitstempel – Anwendungen, die eines von beiden benötigen, müssen diese innerhalb der binären Nutzdaten codieren. Befehlsformat:
CLI
Byte 0:                         0x3E
Byte 1:                         Kanalindex (0-7)
Byte 2:                         Pfadlänge (0xFF = Flood, ansonsten tatsächliche Pfadlänge)
Bytes 3 .. 2+path_len:          Pfad (entfällt, wenn path_len == 0xFF)
Nächste 2 Bytes (Little-Endian): Datentyp (`data_type`, uint16)
Verbleibende Bytes:             Binäre Nutzdaten (variable Länge)
Beispiel (Flood-Modus, DATA_TYPE_DEV, Nutzdaten A1 B2 C3, Kanal 1):
CLI
3E 01 FF FF FF A1 B2 C3
Datentyp / Transport Mapping:
  • 0x0000 (DATA_TYPE_RESERVED) ist ungültig und wird mit PACKET_ERROR abgewiesen.
  • 0xFFFF (DATA_TYPE_DEV) ist der Entwickler-Namensraum für Experimente und App-Entwicklung.
  • Die Werte 0x00010xFFFE sind für registrierte Anwendungs-/Community-Namensräume verfügbar. Siehe die Tabelle Registrierte data_type-Werte unten.
Grenzwerte:
  • Die maximale Nutzdatenlänge beträgt MAX_CHANNEL_DATA_LENGTH = MAX_FRAME_SIZE - 9 = 163 Bytes.
  • Größere Nutzdaten werden mit PACKET_ERROR (ERR_CODE_ILLEGAL_ARG) abgewiesen.
Antwort: PACKET_OK (0x00) bei Erfolg, oder PACKET_ERROR (0x01) mit einem der folgenden Codes:
  • ERR_CODE_NOT_FOUND (2) — unbekannter channel_idx
  • ERR_CODE_ILLEGAL_ARG (6) — ungültige path_len, reservierter data_type (0x0000), oder Nutzdaten größer als MAX_CHANNEL_DATA_LENGTH
  • ERR_CODE_TABLE_FULL (3) — die Warteschlange für ausgehende Übertragungen ist voll; später erneut versuchen
Eingehende Datagramme werden dem Host über RESP_CODE_CHANNEL_DATA_RECV (0x1B) zugestellt; siehe Kanal-Datagramm empfangen.

Registrierte data_type-Werte

data_type ist ein Anwendungsbezeichner, kein Nutzdatenformat-Bezeichner. Jeder registrierte Wert identifiziert eine Anwendung, die ihre eigenen internen Nutzdatenschemata besitzt. Die Firmware inspiziert den Inhalt der Nutzdaten nicht – data_type wird undurchsichtig transportiert.
WertKonstanteZweck
0x0000DATA_TYPE_RESERVEDReserviert; ungültig beim Senden
0x0001 – 0x00FFReserviert für interne Verwendung
0x0100 – 0xFEFFRegistrierte Anwendungs-Namensräume (siehe number_allocations.md)
0xFF00 – 0xFFFETesten/Entwicklung; keine Registrierung erforderlich
0xFFFFDATA_TYPE_DEVEntwickler-/Experimentier-Namensraum

Um eine neue Anwendung zu registrieren, reichen Sie einen PR (Pull Request) ein, der eine Zeile zur Tabelle in docs/number_allocations.md hinzufügt. Interne Unterformate innerhalb einer zugewiesenen Anwendungs-ID gehören dieser Anwendung und werden weder in der MeshCore-Firmware noch in diesem Dokument verfolgt.

---

Kanal-Datagramm empfangen

Eingehende Gruppendatagramme (Radio-Ebene PAYLOAD_TYPE_GRP_DATA, 0x06) werden dem Host als RESP_CODE_CHANNEL_DATA_RECV-Benachrichtigungen weitergeleitet.

Frame-Format (RESP_CODE_CHANNEL_DATA_RECV, 0x1B):
CLI
Byte 0:                 0x1B (Pakettyp)
Byte 1:                 SNR (signed int8, skaliert ×4 — dividieren Sie durch 4.0, um dB zu erhalten)
Bytes 2-3:              Reserviert (Clients MÜSSEN ignorieren)
Byte 4:                 Kanalindex (0-7)
Byte 5:                 Pfadlänge (tatsächliche Pfadlänge bei Flood, ansonsten 0xFF für direkt)
Bytes 6-7:              Datentyp (uint16 Little-Endian)
Byte 8:                 Datenlänge
Bytes 9 .. 8+data_len:  Nutzdaten
Pfad-Bytes werden nicht weitergeleitet: Nur path_len wird im Empfangsrahmen gemeldet — der Pfad selbst wird nicht zum Host kopiert. Es gibt keine Pfad-Bytes zwischen Byte 5 und dem data_type-Feld bei Byte 6–7, ungeachtet von path_len. Die Semantik der Pfadlänge unterscheidet sich zwischen Senden und Empfangen:
Richtungpath_len = 0xFFpath_len ≠ 0xFF
SendenÜberflutet das Netzwerk (Flooding)Direkte Route; der kodierte Pfad folgt (niedrige 6 Bit = Hash-Anzahl, obere 2 Bit + 1 = Hash-Größe; On-Wire Byte-Zahl = hash_count × hash_size)
EmpfangenPaket kam über direkten Weg anPaket wurde geflutet (Flooding); dies ist das kodierte pkt->path_len-Feld wie beobachtet (keine Pfad-Bytes folgen)

Mit anderen Worten, die Bedeutung von 0xFF ist zwischen den beiden Richtungen invertiert, und beim Empfang trägt das Feld nur Metadaten – niemals einen routbaren Pfad. path_len ist ein kodiertes Byte (siehe Packet::isValidPathLen / Packet::writePath in src/Packet.cpp), keine rohe Byte-Anzahl.

Hinweis: Das Gerät kann auch PACKET_MESSAGES_WAITING (0x83) senden, um den Host darüber zu informieren, dass Datagramme in der Warteschlange liegen; fragen Sie diese mit CMD_SYNC_NEXT_MESSAGE (0x0A) ab, um sie abzurufen. Parsing-Pseudocode:
CLI
def parse_channel_data_recv(data):
    if len(data) < 9:
        return None
    snr_byte = data[1]
    snr = (snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0
    channel_idx = data[4]
    path_len = data[5]
    data_type = int.from_bytes(data[6:8], 'little')
    data_len = data[8]
    if 9 + data_len > len(data):
        return None
    payload = data[9:9 + data_len]
    return {
        'snr': snr,
        'channel_idx': channel_idx,
        'path_len': path_len,
        'data_type': data_type,
        'payload': bytes(payload),
    }

---

7. Nachricht abrufen

Zweck: Fordert die nächste in der Warteschlange befindliche Nachricht vom Gerät an. Befehlsformat:
CLI
Byte 0: 0x0A
Beispiel (hexadezimal):
CLI
0A
Antwort:
  • PACKET_CHANNEL_MSG_RECV (0x08) oder PACKET_CHANNEL_MSG_RECV_V3 (0x11) für Kanalnachrichten.
  • PACKET_CONTACT_MSG_RECV (0x07) oder PACKET_CONTACT_MSG_RECV_V3 (0x10) für Kontaktnachrichten.
  • PACKET_CHANNEL_DATA_RECV (0x1B) für Kanal-Datagramme.
  • PACKET_NO_MORE_MSGS (0x0A), wenn keine Nachrichten verfügbar sind.
Hinweis: Fragen Sie diesen Befehl regelmäßig ab, um in der Warteschlange befindliche Nachrichten abzurufen. Das Gerät kann auch PACKET_MESSAGES_WAITING (0x83) als Benachrichtigung senden, wenn Nachrichten verfügbar sind.

---

8. Batterie und Speicher abrufen

Zweck: Fragt die Gerätespannung der Batterie und die Speichernutzung ab. Befehlsformat:
CLI
Byte 0: 0x14
Beispiel (hexadezimal):
CLI
14
Antwort: PACKET_BATTERY (0x0C) mit Batteriemillivolt und Speicherinformationen.

---

Kanalverwaltung

Kanaltypen

  1. Öffentlicher Kanal
- Verwendet einen öffentlich bekannten 16-Byte-Schlüssel: 8b3387e9c5cdea6ac9e5edbaa115cd72 - Jeder kann diesem Kanal beitreten, Nachrichten sollten als öffentlich betrachtet werden. - Wird als Standard-Gruppenchat verwendet.
  1. Hashtag-Kanäle
- Verwendet einen von dem Kanalnamen abgeleiteten geheimen Schlüssel. - Es sind die ersten 16 Bytes von sha256("#test"). - Zum Beispiel hat der Hashtag-Kanal #test den Schlüssel: 9cd8fcf22a47333b591d96a2b848b73f. - Der Verkehr ist in der Luft verschlüsselt, aber jeder, der den Kanalnamen kennt oder errät, kann den Schlüssel ableiten. Hashtag-Kanäle sollten nicht als privat behandelt werden. - Wird als themenbasierter öffentlicher Gruppenchat verwendet, getrennt vom Standard-Öffentlichen Kanal.
  1. Private Kanäle
- Verwendet einen zufällig generierten 16-Byte-Geheimschlüssel. - Nachrichten sollten als privat zwischen denjenigen betrachtet werden, die das Geheimnis kennen. - Benutzer sollten den Schlüssel geheim halten und nur mit denen teilen, mit denen sie kommunizieren möchten. - Wird als sicherer privater Gruppenchat verwendet.

Kanal-Lebenszyklus

  1. Kanal einstellen:
- Rufen Sie alle Kanalslots ab und suchen Sie einen mit leerem Namen und einem nur aus Nullen bestehenden Geheimnis. - Generieren oder stellen Sie ein 16-Byte-Geheimnis bereit. - Senden Sie CMD_SET_CHANNEL mit Namen und einem 16-Byte-Geheimnis.
  1. Kanal abrufen:
- Senden Sie CMD_GET_CHANNEL mit dem Kanalindex. - Parsen Sie die RESP_CODE_CHANNEL_INFO-Antwort.
  1. Kanal löschen:
- Senden Sie CMD_SET_CHANNEL mit leerem Namen und einem nur aus Nullen bestehenden Geheimnis. - Oder überschreiben Sie ihn mit einem neuen Kanal.

---

Nachrichtenverarbeitung

Nachrichten empfangen

Nachrichten werden über das TX-Merkmal (Benachrichtigungen) empfangen. Das Gerät sendet:

  1. Kanalnachrichten:
- PACKET_CHANNEL_MSG_RECV (0x08) – Standardformat - PACKET_CHANNEL_MSG_RECV_V3 (0x11) – Version 3 mit SNR (Signal-Rausch-Verhältnis)
  1. Kontaktnachrichten:
- PACKET_CONTACT_MSG_RECV (0x07) – Standardformat - PACKET_CONTACT_MSG_RECV_V3 (0x10) – Version 3 mit SNR
  1. Benachrichtigungen:
- PACKET_MESSAGES_WAITING (0x83) – Zeigt an, dass Nachrichten in der Warteschlange sind.

Kontaktnachrichtenformat

Standardformat (PACKET_CONTACT_MSG_RECV, 0x07):
CLI
Byte 0: 0x07 (Pakettyp)
Bytes 1-6: Präfix des öffentlichen Schlüssels (6 Bytes, hexadezimal)
Byte 7: Pfadlänge
Byte 8: Texttyp
Bytes 9-12: Zeitstempel (32-Bit Little-Endian)
Bytes 13-16: Signatur (4 Bytes, nur wenn txt_type == 2)
Bytes 17+: Nachrichtentext (UTF-8)
V3-Format (PACKET_CONTACT_MSG_RECV_V3, 0x10):
CLI
Byte 0: 0x10 (Pakettyp)
Byte 1: SNR (vorzeichenbehaftetes Byte, multipliziert mit 4)
Bytes 2-3: Reserviert
Bytes 4-9: Präfix des öffentlichen Schlüssels (6 Bytes, hexadezimal)
Byte 10: Pfadlänge
Byte 11: Texttyp
Bytes 12-15: Zeitstempel (32-Bit Little-Endian)
Bytes 16-19: Signatur (4 Bytes, nur wenn txt_type == 2)
Bytes 20+: Nachrichtentext (UTF-8)
Parsing-Pseudocode:
CLI
def parse_contact_message(data):
    packet_type = data[0]
    offset = 1
    
    # Auf V3-Format prüfen
    if packet_type == 0x10:  # V3
        snr_byte = data[offset]
        snr = ((snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0)
        offset += 3  # SNR + reserviert überspringen
    
    pubkey_prefix = data[offset:offset+6].hex()
    offset += 6
    
    path_len = data[offset]
    txt_type = data[offset + 1]
    offset += 2
    
    timestamp = int.from_bytes(data[offset:offset+4], 'little')
    offset += 4
    
    # Wenn txt_type == 2, 4-Byte-Signatur überspringen
    if txt_type == 2:
        offset += 4
    
    message = data[offset:].decode('utf-8')
    
    return {
        'pubkey_prefix': pubkey_prefix,
        'path_len': path_len,
        'txt_type': txt_type,
        'timestamp': timestamp,
        'message': message,
        'snr': snr if packet_type == 0x10 else None
    }

Kanalnachrichtenformat

Standardformat (PACKET_CHANNEL_MSG_RECV, 0x08):
CLI
Byte 0: 0x08 (Pakettyp)
Byte 1: Kanalindex (0-7)
Byte 2: Pfadlänge
Byte 3: Texttyp
Bytes 4-7: Zeitstempel (32-Bit Little-Endian)
Bytes 8+: Nachrichtentext (UTF-8)
V3-Format (PACKET_CHANNEL_MSG_RECV_V3, 0x11):
CLI
Byte 0: 0x11 (Pakettyp)
Byte 1: SNR (vorzeichenbehaftetes Byte, multipliziert mit 4)
Bytes 2-3: Reserviert
Byte 4: Kanalindex (0-7)
Byte 5: Pfadlänge
Byte 6: Texttyp
Bytes 7-10: Zeitstempel (32-Bit Little-Endian)
Bytes 11+: Nachrichtentext (UTF-8)
Parsing-Pseudocode:
CLI
def parse_channel_message(data):
    packet_type = data[0]
    offset = 1
    
    # Auf V3-Format prüfen
    if packet_type == 0x11:  # V3
        snr_byte = data[offset]
        snr = ((snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0)
        offset += 3  # SNR + reserviert überspringen
    
    channel_idx = data[offset]
    path_len = data[offset + 1]
    txt_type = data[offset + 2]
    timestamp = int.from_bytes(data[offset+3:offset+7], 'little')
    message = data[offset+7:].decode('utf-8')
    
    return {
        'channel_idx': channel_idx,
        'timestamp': timestamp,
        'message': message,
        'snr': snr if packet_type == 0x11 else None
    }

Nachrichten senden

Verwenden Sie den Befehl SEND_CHANNEL_MESSAGE (siehe Befehle).

Wichtig:
  • Nachrichten sind gemäß MeshCore-Spezifikation auf 133 Zeichen begrenzt.
  • Lange Nachrichten sollten in Abschnitte (Chunks) aufgeteilt werden.
  • Fügen Sie einen Chunk-Indikator hinzu (z.B. "[1/3] Nachrichtentext").
---

Antwort-Parsing

Terminologie

Dieses Dokument verwendet eine spezifikationskonforme Namenskonvention (PACKET_*) für Bytes, die die Firmware an den Host zurücksendet. Im Firmware-Quellcode sind dieselben Werte nach Zweck in zwei #define-Familien aufgeteilt:

  • RESP_CODE_* — direkte Antworten auf einen Befehl (z. B. RESP_CODE_CHANNEL_DATA_RECV = PACKET_CHANNEL_DATA_RECV = 0x1B).
  • PUSH_CODE_* — asynchrone Benachrichtigungen, die nicht an einen bestimmten Befehl gebunden sind (z. B. PUSH_CODE_MSG_WAITING = PACKET_MESSAGES_WAITING = 0x83).
Bytewerte sind maßgeblich; Namen sind Aliase. Beim Lesen des Firmware-Quellcodes entsprechen RESP_CODE_X / PUSH_CODE_X dem PACKET_X dieses Dokuments mit demselben numerischen Wert.

Pakettypen

WertNameBeschreibung
0x00PACKET_OKBefehl erfolgreich
0x01PACKET_ERRORBefehl fehlgeschlagen
0x02PACKET_CONTACT_STARTStart der Kontaktliste
0x03PACKET_CONTACTKontaktinformationen
0x04PACKET_CONTACT_ENDEnde der Kontaktliste
0x05PACKET_SELF_INFOGeräte-Eigeninformationen
0x06PACKET_MSG_SENTBestätigung gesendeter Nachricht
0x07PACKET_CONTACT_MSG_RECVKontaktnachricht (Standard)
0x08PACKET_CHANNEL_MSG_RECVKanalnachricht (Standard)
0x09PACKET_CURRENT_TIMEAntwort auf aktuelle Uhrzeit
0x0APACKET_NO_MORE_MSGSKeine weiteren Nachrichten verfügbar
0x0CPACKET_BATTERYBatteriestand
0x0DPACKET_DEVICE_INFOGeräteinformationen
0x10PACKET_CONTACT_MSG_RECV_V3Kontaktnachricht (V3 mit SNR)
0x11PACKET_CHANNEL_MSG_RECV_V3Kanalnachricht (V3 mit SNR)
0x12PACKET_CHANNEL_INFOKanalinformationen
0x1BPACKET_CHANNEL_DATA_RECVKanal-Datagramm
0x80PACKET_ADVERTISEMENTWerbepaket
0x82PACKET_ACKBestätigung (Acknowledgement)
0x83PACKET_MESSAGES_WAITINGBenachrichtigung über wartende Nachrichten
0x88PACKET_LOG_DATARF-Protokolldaten (können ignoriert werden)

Antworten parsen

PACKET_OK (0x00):
CLI
Byte 0: 0x00
Bytes 1-4: Optionaler Wert (32-Bit Little-Endian Ganzzahl)
PACKET_ERROR (0x01):
CLI
Byte 0: 0x01
Byte 1: Fehlercode (optional)
PACKET_CHANNEL_INFO (0x12):
CLI
Byte 0: 0x12
Byte 1: Kanalindex
Bytes 2-33: Kanalname (32 Bytes, Null-terminiert)
Bytes 34-49: Geheimnis (16 Bytes)
Hinweis: Das Gerät gibt in dieser Antwort das 16-Byte-Kanalgeheimnis zurück. PACKET_DEVICE_INFO (0x0D):
CLI
Byte 0: 0x0D
Byte 1: Firmware-Version (uint8)
Bytes 2+: Variable Länge basierend auf der Firmware-Version

Für Firmware-Version >= 3:
Byte 2: Max. Kontakte Roh (uint8, tatsächlich = Wert * 2)
Byte 3: Max. Kanäle (uint8)
Bytes 4-7: BLE PIN (32-Bit Little-Endian)
Bytes 8-19: Firmware-Build (12 Bytes, UTF-8, Null-gefüllt)
Bytes 20-59: Modell (40 Bytes, UTF-8, Null-gefüllt)
Bytes 60-79: Version (20 Bytes, UTF-8, Null-gefüllt)
Byte 80: Client Repeat aktiviert/bevorzugt (Firmware v9+)
Byte 81: Pfad-Hash-Modus (Firmware v10+)
Parsing-Pseudocode:
CLI
def parse_device_info(data):
    if len(data) < 2:
        return None
    
    fw_ver = data[1]
    info = {'fw_ver': fw_ver}
    
    if fw_ver >= 3 and len(data) >= 80:
        info['max_contacts'] = data[2] * 2
        info['max_channels'] = data[3]
        info['ble_pin'] = int.from_bytes(data[4:8], 'little')
        info['fw_build'] = data[8:20].decode('utf-8').rstrip('\x00').strip()
        info['model'] = data[20:60].decode('utf-8').rstrip('\x00').strip()
        info['ver'] = data[60:80].decode('utf-8').rstrip('\x00').strip()
    
    return info
PACKET_BATTERY (0x0C):
CLI
Byte 0: 0x0C
Bytes 1-2: Batteriespannung (16-Bit Little-Endian, Millivolt)
Bytes 3-6: Genutzter Speicher (32-Bit Little-Endian, KB)
Bytes 7-10: Gesamtspeicher (32-Bit Little-Endian, KB)
Parsing-Pseudocode:
CLI
def parse_battery(data):
    if len(data) < 3:
        return None
    
    mv = int.from_bytes(data[1:3], 'little')
    info = {'battery_mv': mv}
    
    if len(data) >= 11:
        info['used_kb'] = int.from_bytes(data[3:7], 'little')
        info['total_kb'] = int.from_bytes(data[7:11], 'little')
    
    return info
PACKET_SELF_INFO (0x05):
CLI
Byte 0: 0x05
Byte 1: Werbetyp
Byte 2: TX-Leistung
Byte 3: Max. TX-Leistung
Bytes 4-35: Öffentlicher Schlüssel (32 Bytes, hexadezimal)
Bytes 36-39: Werbelängengrad (32-Bit Little-Endian, geteilt durch 1e6)
Bytes 40-43: Werbebreitengrad (32-Bit Little-Endian, geteilt durch 1e6)
Byte 44: Multi-ACKs
Byte 45: Werbe-Positionsrichtlinie
Byte 46: Telemetrie-Modus (Bitfeld)
Byte 47: Manuelle Kontakte hinzufügen (boolean)
Bytes 48-51: Funkfrequenz (32-Bit Little-Endian, geteilt durch 1000.0)
Bytes 52-55: Funkbandbreite (32-Bit Little-Endian, geteilt durch 1000.0)
Byte 56: Funk-Spreizfaktor
Byte 57: Funk-Codiereffizienz
Bytes 58+: Gerätename (UTF-8, variable Länge, kein Null-Terminator erforderlich)
Parsing-Pseudocode:
CLI
def parse_self_info(data):
    if len(data) < 36:
        return None
    
    offset = 1
    info = {
        'adv_type': data[offset],
        'tx_power': data[offset + 1],
        'max_tx_power': data[offset + 2],
        'public_key': data[offset + 3:offset + 35].hex()
    }
    offset += 35
    
    lat = int.from_bytes(data[offset:offset+4], 'little') / 1e6
    lon = int.from_bytes(data[offset+4:offset+8], 'little') / 1e6
    info['adv_lat'] = lat
    info['adv_lon'] = lon
    offset += 8
    
    info['multi_acks'] = data[offset]
    info['adv_loc_policy'] = data[offset + 1]
    telemetry_mode = data[offset + 2]
    info['telemetry_mode_env'] = (telemetry_mode >> 4) & 0b11
    info['telemetry_mode_loc'] = (telemetry_mode >> 2) & 0b11
    info['telemetry_mode_base'] = telemetry_mode & 0b11
    info['manual_add_contacts'] = data[offset + 3] > 0
    offset += 4
    
    freq = int.from_bytes(data[offset:offset+4], 'little') / 1000.0
    bw = int.from_bytes(data[offset+4:offset+8], 'little') / 1000.0
    info['radio_freq'] = freq
    info['radio_bw'] = bw
    info['radio_sf'] = data[offset + 8]
    info['radio_cr'] = data[offset + 9]
    offset += 10
    
    if offset < len(data):
        name_bytes = data[offset:]
        info['name'] = name_bytes.decode('utf-8').rstrip('\x00').strip()
    
    return info
PACKET_MSG_SENT (0x06):
CLI
Byte 0: 0x06
Byte 1: Route-Flag (0 = direkt, 1 = Flood)
Bytes 2-5: Tag / Erwartete ACK (4 Bytes, Little-Endian)
Bytes 6-9: Vorgeschlagenes Timeout (32-Bit Little-Endian, Millisekunden)
PACKET_ACK (0x82):
CLI
Byte 0: 0x82
Bytes 1-6: ACK-Code (6 Bytes, hexadezimal)

Fehlercodes

PACKET_ERROR (0x01) enthält einen ein-Byte-Fehlercode in Byte 1. Die Werte entsprechen den ERR_CODE_*-Konstanten, die in examples/companion_radio/MyMesh.cpp definiert sind:
CodeKonstante (Firmware)Beschreibung
1ERR_CODE_UNSUPPORTED_CMDUnbekannter oder nicht unterstützter Befehlsbyte / Unterbefehl
2ERR_CODE_NOT_FOUNDZiel nicht gefunden (Kanal, Kontakt, Nachricht usw.)
3ERR_CODE_TABLE_FULLInterne Warteschlange oder Tabelle ist voll — später erneut versuchen
4ERR_CODE_BAD_STATEOperation im aktuellen Gerätezustand nicht gültig (z. B. Iterator läuft bereits)
5ERR_CODE_FILE_IO_ERRORDateisystem- oder Speicher-I/O-Fehler
6ERR_CODE_ILLEGAL_ARGUngültiges Argument (falsche Länge, Wert außerhalb des Bereichs, reserviertes Feld usw.)
Hinweis: Fehlercodes können je nach Firmware-Version variieren. Überprüfen Sie immer Byte 1 der PACKET_ERROR-Antwort und behandeln Sie unbekannte Codes als generische Fehler.

Frame-Verarbeitung

BLE-Implementierungen reihen auf der Firmware-Ebene pro BLE-Schreib-/Benachrichtigung einen Protokollrahmen ein und liefern ihn aus.

  • Apps sollten jede Merkmals-Schreib-/Benachrichtigung als genau einen Companion-Protokollrahmen behandeln.
  • Apps sollten die Frame-Längen vor dem Parsen weiterhin validieren.
  • Zukünftige Transporte oder Firmware-Revisionen können abweichen, daher sollten Sie bei variablen Antworten nicht von festen Nutzdatengrößen ausgehen.

Antwort-Handling

  1. Befehl-Antwort-Muster:
- Senden Sie einen Befehl über das RX-Merkmal. - Warten Sie auf eine Antwort über das TX-Merkmal (Benachrichtigung). - Gleichen Sie die Antwort mit dem Befehl über Sequenznummern oder den Befehlstyp ab. - Behandeln Sie ein Timeout (üblicherweise 5 Sekunden). - Verwenden Sie eine Befehlswarteschlange, um parallele Befehle zu verhindern.
  1. Asynchrone Nachrichten:
- Das Gerät kann jederzeit Nachrichten über das TX-Merkmal senden. - Behandeln Sie PACKET_MESSAGES_WAITING (0x83), indem Sie den GET_MESSAGE-Befehl abfragen. - Parsen Sie eingehende Nachrichten und leiten Sie sie an die entsprechenden Handler weiter. - Validieren Sie die Frame-Länge vor dem Dekodieren.
  1. Antwort-Abgleich:
- Gleichen Sie die Antworten mit Befehlen nach erwartetem Pakettyp ab: - APP_STARTPACKET_SELF_INFO - DEVICE_QUERYPACKET_DEVICE_INFO - GET_CHANNELPACKET_CHANNEL_INFO - SET_CHANNELPACKET_OK oder PACKET_ERROR - SEND_CHANNEL_MESSAGEPACKET_MSG_SENT - GET_MESSAGEPACKET_CHANNEL_MSG_RECV, PACKET_CONTACT_MSG_RECV, PACKET_CHANNEL_DATA_RECV, oder PACKET_NO_MORE_MSGS - SEND_CHANNEL_DATAPACKET_OK oder PACKET_ERROR - GET_BATTERYPACKET_BATTERY
  1. Timeout-Handling:
- Standard-Timeout: 5 Sekunden pro Befehl. - Bei Timeout: Fehler protokollieren, aktuellen Befehl löschen, mit dem nächsten in der Warteschlange fortfahren. - Einige Befehle können länger dauern (z.B. SET_CHANNEL benötigt möglicherweise 1-2 Sekunden). - Berücksichtigen Sie ein längeres Timeout für Kanaloperationen.
  1. Fehlerbehebung:
- Bei PACKET_ERROR: Fehlercode protokollieren, aktuellen Befehl löschen. - Bei Verbindungsverlust: Befehlswarteschlange löschen, Wiederverbindung versuchen. - Bei ungültiger Antwort: Warnung protokollieren, aktuellen Befehl löschen, fortfahren.

---

Beispielimplementierungsfluss

Initialisierung

CLI
# 1. Nach MeshCore-Geräten suchen
device = scan_for_device("MeshCore")

# 2. Mit BLE GATT verbinden
gatt = connect_to_device(device)

# 3. Dienste und Merkmale entdecken
service = discover_service(gatt, "6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
rx_char = discover_characteristic(service, "6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
tx_char = discover_characteristic(service, "6E400003-B5A3-F393-E0A9-E50E24DCCA9E")

# 4. Benachrichtigungen auf TX-Merkmal aktivieren
enable_notifications(tx_char, on_notification_received)

# 5. AppStart-Befehl senden
send_command(rx_char, build_app_start())
wait_for_response(PACKET_SELF_INFO)

Erstellen eines privaten Kanals

CLI
# 1. 16-Byte-Geheimnis generieren
secret_16_bytes = generate_secret(16)  # CSPRNG verwenden (kryptografisch sicherer Zufallszahlengenerator)
secret_hex = secret_16_bytes.hex()

# 2. SET_CHANNEL-Befehl erstellen
channel_name = "YourChannelName"
channel_index = 1  # 1-7 für private Kanäle verwenden
command = build_set_channel(channel_index, channel_name, secret_16_bytes)

# 3. Befehl senden
send_command(rx_char, command)
response = wait_for_response(PACKET_OK)

# 4. Geheimnis lokal speichern
store_channel_secret(channel_index, secret_hex)

Nachricht senden

CLI
# 1. Kanalnachrichtenbefehl erstellen
channel_index = 1
message = "Hallo, MeshCore!"
timestamp = int(time.time())
command = build_channel_message(channel_index, message, timestamp)

# 2. Befehl senden
send_command(rx_char, command)
response = wait_for_response(PACKET_MSG_SENT)

Nachrichten empfangen

CLI
def on_notification_received(data):
    packet_type = data[0]
    
    if packet_type == PACKET_CHANNEL_MSG_RECV or packet_type == PACKET_CHANNEL_MSG_RECV_V3:
        message = parse_channel_message(data)
        handle_channel_message(message)
    elif packet_type == PACKET_MESSAGES_WAITING:
        # Nachrichten abfragen
        send_command(rx_char, build_get_message())

---

Best Practices

  1. Verwaltungs-Verbindung:
- Implementieren Sie eine automatische Wiederverbindung mit exponentiellem Backoff (Staffelung der Wartezeit). - Gehen Sie gnädig mit Verbindungsabbrüchen um. - Speichern Sie die Adresse des zuletzt verbundenen Geräts für eine schnelle Wiederverbindung.
  1. Geheimnisverwaltung:
- Verwenden Sie immer kryptografisch sichere Zufallszahlengeneratoren. - Speichern Sie Geheimnisse sicher (verschlüsselte Speicherung). - Protokollieren oder übertragen Sie Geheimnisse niemals im Klartext.
  1. Nachrichtenverarbeitung:
- Senden Sie CMD_SYNC_NEXT_MESSAGE, wenn PUSH_CODE_MSG_WAITING empfangen wird. - Implementieren Sie eine Nachrichten-Deduplizierung, um zu vermeiden, dass dieselbe Nachricht zweimal angezeigt wird.
  1. Kanalverwaltung:
- Holen Sie alle Kanalslots ab, auch wenn Sie einen leeren Slot finden. - Speichern Sie neue Kanäle idealerweise im ersten leeren Slot.
  1. Fehlerbehandlung:
- Implementieren Sie Timeouts für alle Befehle (typischerweise 5 Sekunden). - Behandeln Sie RESP_CODE_ERR-Antworten entsprechend.

---

Fehlerbehebung

Verbindungsprobleme

  • Gerät nicht gefunden: Stellen Sie sicher, dass das Gerät eingeschaltet ist und sendet (Adverstising).
  • Verbindungs-Timeout: Überprüfen Sie Bluetooth-Berechtigungen und die Gerätenähe.
  • GATT-Fehler: Stellen Sie die korrekte Dienst-/Merkmalerkennung sicher.

Befehlsprobleme

  • Keine Antwort: Vergewissern Sie sich, dass Benachrichtigungen aktiviert sind, überprüfen Sie den Verbindungsstatus.
  • Fehlerantworten: Überprüfen Sie das Befehlsformat und den Fehlercode.
  • Timeout: Erhöhen Sie den Timeout-Wert oder versuchen Sie es erneut.

Nachrichtenprobleme

  • Nachrichten werden nicht empfangen: Fragen Sie den Befehl GET_MESSAGE regelmäßig ab.
  • Doppelte Nachrichten: Implementieren Sie die Nachrichten-Deduplizierung unter Verwendung von Zeitstempel/Inhalt als eindeutige ID.
  • Nachrichtentruncierung: Senden Sie lange Nachrichten als separate, kürzere Nachrichten.

Quelle: docs.meshcore.io