Commits vergleichen

..
Autor SHA1 Nachricht Datum
tom 0a1c781f0d foxmesh-ci.yml actions/checkout with all tags download
Build of FoxMesh / build-and-test (push) Successful in 2m55s
Only with these https://gitea.com/actions/checkout usage, we can checkout all tags, for versioning.
2026-10-21 01:23:22 +02:00
tom b54a9fc714 Merge pull request 'Refactor/lora cli' (#5) from refactor/lora-cli into main
Build of FoxMesh / build-and-test (push) Successful in 2m45s
Reviewed-on: #5
2026-10-21 00:29:14 +02:00
tom 1a56c456f6 version 0.1
Build of FoxMesh / build-and-test (pull_request) Successful in 2m38s
2026-10-21 00:23:54 +02:00
tom fb0e096e4e refactor Build Date, GIT_TAG, and GIT_HASH support
Build of FoxMesh / build-and-test (pull_request) Successful in 2m44s
2026-10-21 00:13:59 +02:00
tom 618ff87d5c lora: race condition removed, preparation for rx, getSnapshot, beautify led and i2c files 2026-10-20 22:13:38 +02:00
tom 256fb9cdac display: utf8 table for 0xc3 and 0xc2, umlauts, etc. 2026-10-20 22:11:59 +02:00
tom 4823f519b2 cli: add LoRa snapshot status, test-display command 2026-10-20 22:10:29 +02:00
tom 1d22bcb4ce Merge pull request 'display: looking ahead function for UTF-8 with efficiency case' (#4) from refactor/display-utf8-decoder into main
Build of FoxMesh / build-and-test (push) Successful in 2m40s
Reviewed-on: #4
2026-10-20 12:56:17 +02:00
tom 1616bb25f0 display: looking ahead function for UTF-8 with efficiency case
Build of FoxMesh / build-and-test (pull_request) Successful in 2m43s
2026-10-20 12:54:18 +02:00
tom aab6f8de43 sdkconfig.ttgo-lora32-v21 gelöscht
Build of FoxMesh / build-and-test (push) Successful in 2m41s
2026-10-20 12:37:58 +02:00
tom f2e4df9cc5 Merge branch 'feature/display-lines' into main
Build of FoxMesh / build-and-test (push) Successful in 2m47s
2026-10-20 12:29:40 +02:00
tom 6b43bd8dbb Merge pull request 'Feature/add sx1276 support' (#2) from feature/add-sx1276-support into main
Build of FoxMesh / build-and-test (push) Successful in 2m41s
Reviewed-on: #2
2026-10-20 04:34:43 +02:00
tom f8088d3793 Merge branch 'main' into feature/add-sx1276-support
Build of FoxMesh / build-and-test (pull_request) Successful in 2m51s
2026-10-20 04:34:17 +02:00
tom 5c90b419bf Add Bob WB4APR's SSID document
foxmesh-ci.yml (push) Invalid workflow file
2026-10-20 04:23:07 +02:00
tom 13b9df523d display: move FoxMesh and version to line # 0 2026-10-20 04:18:02 +02:00
tom 398cf135a4 docs: update foxmash protocol 2026-10-20 04:04:16 +02:00
tom ae747dc0cc upd documentation foxmash-protocol and APRS ref. 2026-10-20 02:16:38 +02:00
tom e175568531 cli: replace fraction of frequency to just frequency 2026-10-19 20:29:54 +02:00
tom c0a94ce627 configuration: add lora specific default values 2026-10-19 17:25:48 +02:00
tom 9124f55b32 Merge pull request 'Feature/gitea support' (#1) from feature/gitea-support into main
Build of FoxMesh / build-and-test (push) Successful in 2m51s
Reviewed-on: #1
2026-10-19 15:21:09 +02:00
tom 95e74a960a yaml: trigger the workflow on pull-request events targeting the main branch
Build of FoxMesh / build-and-test (push) Skipped
Build of FoxMesh / build-and-test (pull_request) Successful in 2m54s
2026-10-19 15:16:39 +02:00
tom 8b5145bc30 platform: add no warnings for #include-next
Build of FoxMesh / build-and-test (push) Skipped
2026-10-19 15:03:09 +02:00
tom f16a243e3f yaml: fix the indentification, adapt to host only 2026-10-19 15:02:16 +02:00
18 geänderte Dateien mit 1058 neuen und 2994 gelöschten Zeilen

Keine Dateien entsprechen Ihrer Suche

+6 -3
Datei anzeigen
@@ -2,8 +2,9 @@ name: Build of FoxMesh
on:
push:
branches:
- main
branches: [ main ]
pull_request:
branches: [ main ] # Trigger the workflow on pull request events targeting the main branch
workflow_dispatch:
inputs:
target_branch:
@@ -23,11 +24,13 @@ jobs:
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
# 1. Checkout the target branch
# 1. Checkout the target branch, with fetch-depth: 0 and fetch-tags: true -> loading all tags
- name: Checkout Target branch
uses: actions/checkout@v7
with:
ref: ${{ github.event.inputs.target_branch || github.ref }}
fetch-depth: 0
fetch-tags: true
# 2. Extract Git and Date Metadata for the dynamic artifact filename
- name: Generate Dynamic Artifact Metadata
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
+107
Datei anzeigen
@@ -0,0 +1,107 @@
APRS SSID Recommendations 6 Feb 2012
---------------------------------------------------------------------
Updated 6 Feb 2012 to clarify the title of this document to be
SSID RECOMMENDATIONS not to imply any kind of
decoding "standard". That original NMEA
decodign standard is covered in the original spec
Updated 9 June 2010 for more flexibility using 1,2,3,4 and 15
Revised 2 June 2004 to add -10, -11, 12 and -15
SSID's have seen two different uses in APRS. Initially as an ICON
indicator back in the early 1990's. But that is obsolete for over
a decade. Now SSID's are used as an informal way of indicating one
of several different typical APRS applications.
Since many small displays for the handheld and mobile operator show
nearby APRS station callsigns that flash up on the screen, it is nice
to have some idea of what type of station or activity might be
involved simply from the callsign SSID without having to push
buttons, search lists, or look at maps to find out more about them.
SSID RECOMMENDATIONS: It is very convenient to other mobile
operators or others looking at callsigns flashing by, to be able to
recognize some common applications at a glance. Here are the
recommendations for the 16 possible SSID's (the limit of 16 comes
from the 4 bits available in the AX.25 protocol. Note, The SSID of
zero is dropped by most display applications. So a callsign with no
SSID has an SSID of 0.
-0 Your primary station usually fixed and message capable
-1 generic additional station, digi, mobile, wx, etc
-2 generic additional station, digi, mobile, wx, etc
-3 generic additional station, digi, mobile, wx, etc
-4 generic additional station, digi, mobile, wx, etc
-5 Other networks (Dstar, Iphones, Androids, Blackberry's etc)
-6 Special activity, Satellite ops, camping or 6 meters, etc
-7 walkie talkies, HT's or other human portable
-8 boats, sailboats, RV's or second main mobile
-9 Primary Mobile (usually message capable)
-10 internet, Igates, echolink, winlink, AVRS, APRN, etc
-11 balloons, aircraft, spacecraft, etc
-12 APRStt, DTMF, RFID, devices, one-way trackers*, etc
-13 Weather stations
-14 Truckers or generally full time drivers
-15 generic additional station, digi, mobile, wx, etc
* One-way trackers should best use the -12 one-way SSID indicator
because the -9's usually mean a ham in full APRS communication
both message and voice. The -9's can be contacted by APRS message
or by Voice on his frequency included in his beacon, or on Voice
Alert if he is in simplex range. The -12's are just moving Icons
on the map and since they have no 2 way communication for ham radio
they are not generally of routine interest to other operators.
OBJECTS or INTERNET: In addition, Objects or internet generated
stations can have any SSID, not just the original 16, since
Objects are not constrained by the AX.25 header and can have a
9 byte name. Here are some common OBJECT/Internet SSID's:
-63 for PSK63 HF stations
-tt for APRS TouchTone users (DTMF)
-ID for RFID
-A through -Z for Dstar
SSID BACKGROUND: Originally, in 1992, we had to use the SSID as
a way of indicating the type of station that transmitted a raw
NMEA-0183 GPS sting. But in the mid 1990's we began indicating
any of the nearly 200 APRS symbols by the setting of the AX.25
TOCALL of "GPSxyz". The "xyz" characters define the symbol from
the standard APRS symbol table www.aprs.org/symbols.html.
The GPSxyz concept worked so well, the original SSID associations
are no longer a required part of the spec. But the conventions
that evolved from those early SSID's are still encouraged as
noted above, for easy recognition of station type or activity by
when only the callsign is seen.
The -1, -2, -3, -4 and -15 are kept generic so that anyone with
as many as 6 digipeaters, or 6 trackers or 6 weather stations or
6 vehicles can still have unique SSID's for each of his stations.
Beyond 6, people will just have to use any SSID that suits their
fancy. In some areas there might be 15 digipeaters all under
one guy's call!
SSID USAGE: The SSID's also might give a hint as to how someone
is getting into APRS whether via satellite, a one-way tracker, a
mobile, an HT or even via DTMF or an RFID device or whether he is
doing something special.
For example, if you are doing something special, change your
SSID to -6 to alert others to your excitement, or to make the
track-history begin and end on site, and not be tied to all
your other -9 travels. Or use -6 SSID for a packet sent
via the ISS or APRS satellite or for a 6 meter test so the
successful packet is preserved and not overwritten by the same
radio the next time you use it not via the ISS on the 144.39
national channel. By using separate SSID's the WEB data bases
will keep statistics and data separate from when you are working
normally on other bands with other SSID's.
So stick to the suggestions above for the obvious applications
where you can. Of course these are not rigid. If you have
more than 6 digipeaters, use any SSID you want. These are only
guidelines to hint at a station's possible application when all
you can see easily is the callsign on a screen or in a list....
de WB4APR, Bob
Binäre Datei nicht angezeigt.
+91
Datei anzeigen
@@ -0,0 +1,91 @@
# FoxMash - Protocol
English version. In case of doubt, the English text shall be binding.
*[Deutsche Version anzeigen](foxmash-protocol_de.md)*
## Automatic Stations at the ARDF Transmitting Facilities (Foxes)
A fox regularly broadcasts its identifier and telemetry data.
This is done, whenever possible, at times when the fox is not transmitting on the 80m or 2m bands.
The purpose of this is, on the one hand, the time synchronization of adjacent nodes, and on the other hand, the regular official announcement of the call sign.
Usually, the call sign of the organizer or host of the direction-finding competition is used.
## Beacon
Composed based on the modified AX.25, APRS, and MeshCom protocols as follows:
Pseudo-Byte-Stream:
`{OE3TKT-03nnnnnn`
### Structure:
* **Byte 0: `{` (0x7B)**
* **Data Type Identifier (DTI):** Derived from the APRS & MeshCom protocol specification (see [APRS Protocol Reference](https://ui-view.net)).
* **Reasoning:** This data type identifier is currently marked as `[Unused]` in the specification, and the character visually resembles a station icon.
* **Bytes 1–6: `"OE3TKT"` (0x4F 0x45 0x33 0x54 0x4B 0x54)**
* **International Call Sign:** Formatted as a 1–2 character prefix, followed by one or more digits, and a 1–3 letter suffix.
* **Padding:** This field is strictly 6 characters wide. If the call sign is shorter, it must be right-padded with trailing spaces.
* **Bytes 7+: Station Number / SSID Identifier**
* **Standard Notation (IDs 0–99):** Uses a hyphen followed by a two-digit SSID (Bytes 7–9), for example, `"-03"` (0x2D 0x30 0x33).
* **Extended Notation (IDs ≥ 100):** If the station number exceeds 100, you may take any letters as described in https://aprs11/SSIDs.txt (e.g., `OE3TKT-S6` for any special 6th station).
* **Backup / Hot-Standby Nodes:** To designate a station as a replacement node or an immediate hot-standby node, the hyphen is replaced by a tilde symbol (e.g., `OE3TKT~03`).
* **Bytes 10–13: Telemetry Data (`nnnnnn` / Timestamp)**
* **Timestamp:** Signed 32-bit integer representing the number of milliseconds since the start of the competition day.
* **Byte 14: Own Battery Level**
* **Value:** Unsigned 8-bit integer (`unsigned char`) representing the node's own battery capacity in percent (0 to 100%).
A value of `100` (0x64) means fully charged, while `0` means empty.
Values above 100 can be used to indicate charging or mains power (e.g., `255`).
* **Bytes 15+: Heard Neighbor Nodes (Format: `xRSqxRSqxRSq...`)**
* Each neighbor node found in the mesh is represented by a multi-byte block containing its SSID, link quality, and battery status.
* **Bytes 15–16 (x):** Neighbor SSID (16-bit unsigned integer, e.g., `0x0003` for station number 3).
* **Byte 17 (R):** RSSI value + 164 stored as an `unsigned char` (e.g., `0x3A` = 58 ➡️ 58 - 164 = -106 dBm).
* **Byte 18 (S):** SNR value multiplied by 4 stored as a `signed char` (e.g., `0x42` = 66 ➡️ 66 / 4 = +16.5 dB).
* **Byte 19 (q):** Combined Quality and Battery Status Byte:
* **Bits 7–6 (Battery Status of the neighbor):**
* `00` = Normal (Battery OK / Powered)
* `01` = Low (Battery low, intervention recommended)
* `10` = Critical (Battery critical, node might shut down soon)
* `11` = Reserved
* **Bits 5–0 (Counter):** Moving average counter of how many times this station was heard within the last 30 minutes (Max value: 63). If this counter is ≥ 3, the neighbor is considered a stable link. Stations not heard at least once within 30 minutes are dropped from the list.
## Checkpoint Messages (Posten)
As soon as a participant checks in at a checkpoint using their RFID tag or identifier, they are registered at that node. The runner is assigned a timestamp and their ID (runner number). The mapping between the runner number and the RFID code is stored within the system. During the next available time slot, a checkpoint message is transmitted to the destination with QoS 2 (guaranteed packet delivery, duplicate detection, and filtering).
Messages are sent to the destination and optionally to the start node simultaneously. Both nodes must have an SSID < 15, as the remaining bits in the address byte are reserved for protocol flags.
This message packet is designed to be extremely short and efficient. It contains the node number, runner number, capture timestamp, and the current hop count.
Pseudo-Byte-Stream:
`vppMLzzzzRR`
### Structure (Checkpoint Message):
* **Byte 0: `(` (0x40)**
* **Data Type Identifier (DTI):** Special identifier designated for FoxMash checkpoint messages.
* **Bytes 1–2: Own SSID (`pp`)**
* **Value:** 16-bit unsigned integer (`uint16_t`) of the transmitting checkpoint node.
* **Byte 3: `M` (Target SSID, Hop Count & Flags)**
* **Bits 7–5 (Hop Count):** Dynamic routing counter. If the node sees the destination directly with good quality, this value is `0`. If no acknowledgment is received within 5 minutes, the permitted hop value is incremented by one. If the hop value exceeds the defined maximum, the packet is discarded.
* **Bit 4 (ACK Flag):** `0` for a standard data message, `1` for an acknowledgment packet (ACK).
* **Bits 3–0 (Target SSID):** The SSID of the destination node (Supports IDs from 0 to 15).
* **Bytes 4–5: `L` (Runner Number)**
* **Value:** 16-bit unsigned integer (`uint16_t`) for unique participant identification (Supports IDs from 0 to 65,535).
* **Bytes 6–9: `zzzz` (Timestamp)**
* **Value:** Signed 32-bit integer (milliseconds since the start of the day) capturing the exact time the runner checked in at the post. Also used to verify packet validity within the allowed time window.
* **Bytes 10+: `RR` (Routing List / Optional)**
* **Value:** Optional list of intermediate nodes. For example, when utilizing three relay nodes, this field is structured as `RR RR RR`. It remains empty if a direct connection is established.
### Acknowledgments and Retries
The maximum number of retries is configurable (default: 4). Every checkpoint packet must be acknowledged. The acknowledgment uses the exact same format (`vppMLzzzzRR`), but with **Bit 4 in Byte 3 (`M`) set to `1`**. The return routing follows the identical path in the reverse direction based on the route recorded in the packet (source routing). Remit nodes detect and discard duplicate packets by matching the runner's unique timestamp.
+106
Datei anzeigen
@@ -0,0 +1,106 @@
# FoxMash - Protokoll
Deutsche Übersetzung. Im Zweifelsfall ist die englischsprachige Version bindend.
*[View English Version](foxmash-protocol.md)*
## Automatische Stationen bei den ARDF-Sendeeinrichtungen (Füchsen)
Ein Fuchs sendet regelmäßig seine Kennung (Identifier) und Telemetriedaten aus.
Dies geschieht, wann immer möglich, zu Zeiten, in denen kein Sendebetrieb des Fuchses im 80m- oder 2m-Band stattfindet.
Dies dient einerseits der Zeitsynchronisation der Nachbarknoten und andererseits der regelmäßigen, behördlich vorgeschriebenen Aussendung des Rufzeichens.
Üblicherweise wird das Rufzeichen des Ausrichters bzw. Veranstalters des Peilwettbewerbs verwendet.
## Bake (Broadcast)
Setzt sich wie folgt aus den modifizierten AX.25-, APRS- und MeshCom-Protokollen zusammen:
Pseudo-Byte-Stream:
`{OE3TKT-03nnnnxxRSqxxRSqxxRSq`
### Struktur:
* **Byte 0: `{` (0x7B)**
* **Data Type Identifier (DTI):** Abgeleitet aus der APRS- & MeshCom-Protokollspezifikation (siehe [APRS Protocol Reference](https://ui-view.net)).
* **Begründung:** Dieser Datentyp-Bezeichner ist in der Spezifikation derzeit noch als `[Unused]` (unbenutzt) markiert, und das Zeichen sieht visuell wie ein Stationssymbol aus.
* **Bytes 1–6: `"OE3TKT"` (0x4F 0x45 0x33 0x54 0x4B 0x54)**
* **Internationales Rufzeichen:** Formatiert als 1–2 Zeichen langes Präfix, gefolgt von einer oder mehreren Ziffern und einem 1–3 Zeichen langen Suffix.
* **Padding (Auffüllung):** Dieses Feld ist strikt 6 Zeichen lang. Wenn das Rufzeichen kürzer ist, muss es rechtsbündig mit Leerzeichen aufgefüllt werden.
* **Bytes 7+: Stationsnummer / SSID-Kennung**
* **Standard-Notation (IDs 0–99):** Verwendet einen Bindestrich, gefolgt von einer zweistelligen SSID (Bytes 7–9), zum Beispiel `"-03"` (0x2D 0x30 0x33).
* **Erweiterte Notation (IDs ≥ 100):** Wenn die Stationsnummer 100 überschreitet, können beliebige Buchstaben verwendet werden, wie unter https://aprs11/SSIDs.txt beschrieben (z. B. `OE3TKT-S6` für eine beliebige spezielle 6. Station).
* **Ersatz- / Hot-Standby-Knoten:** Um eine Station als Ersatzstation oder sofort einsatzbereiten Knoten zu kennzeichnen, wird der Bindestrich durch ein Tilde-Zeichen ersetzt (z. B. `OE3TKT~03`).
* **Bytes 10–13: Telemetriedaten (`nnnnnn` / Zeitstempel)**
* **Zeitstempel:** Vorzeichenbehaftete 32-Bit-Ganzzahl (Signed 32-Bit Integer), die die Millisekunden seit Beginn des Wettbewerbstages darstellt.
Dieser Zeitstempel dient gleichzeitig auch als Nachrichten-ID, und die gesamte Nachricht wird gehasht und damit kann festgestellt werden, ob sie doppelt ist.
* **Byte 14: Eigener Batteriestand**
* **Wert:** Vorzeichenlose 8-Bit-Ganzzahl (`unsigned char`), die die eigene Batteriekapazität in Prozent (0 bis 100 %) darstellt. Ein Wert von `100` (0x64) bedeutet voll geladen, `0` bedeutet leer. Werte über 100 können optional für Netzbetrieb oder Ladevorgang reserviert werden (z. B. `255`).
* **Bytes 15+: Gehörte Nachbarknoten (Format: `xxRSqxxRSqxxRSq...`)**
* Jeder im Mesh gefundene Nachbarknoten wird durch einen Multi-Byte-Block dargestellt, der seine SSID, die Link-Qualität und den Batteriestatus enthält.
* **Bytes 15–16 (x):** Nachbar-SSID (16-Bit Unsigned Integer, z. B. `0x0003` für Stationsnummer 3).
* **Byte 17 (R):** RSSI-Wert + 164 als `unsigned char` (z. B. `0x3A` = 58 ➡️ 58 - 164 = -106 dBm).
* **Byte 18 (S):** Vierfacher SNR-Wert als `signed char` (z. B. `0x42` = 66 ➡️ 66 / 4 = +16.5 dB).
* **Byte 19 (q):** Kombiniertes Qualitäts- und Batteriestatus-Byte:
* **Bits 7–6 (Batteriezustand des Nachbarn):**
* `00` = Normal (Batterie OK / Netzbetrieb)
* `01` = Niedrig (Batterie schwach, Wartung empfohlen)
* `10` = Kritisch (Batterie kritisch, Knoten fällt bald aus)
* `11` = Reserviert
* **Bits 5–0 (Zähler):** Gleitender Zähler, wie oft diese Station in den letzten 30 Minuten gehört wurde (Maximalwert: 63). Wenn dieser Zähler ≥ 3 ist, gilt der Nachbar als stabiler Link. Stationen, die innerhalb von 30 Minuten kein einziges Mal gehört wurden, werden aus der Liste gelöscht.
Falls der eigne Knoten die Ziel/Start-Knoten nicht selbst sehen, so müssen
## Nachrichten von Posten
Sobald eine Person am Postenschirm seine Kennung bekannt gibt wird er an diesem Knoten registriert.
Ein Zeitstempel und seine ID wird zugewiesen (Läufernummer).
Die Läufernummer und die entsprechende RFID-Nummer ist im System hinterlegt und es wird beim nächsten Zeitschlitz eine Posten-Nachricht an das Ziel verschickt.
Das Ziel und der Start-Knoten ist dem System bekannt (muss nicht zwingend SSID 0 haben).
Jedoch: Muss eine SSID < 15 haben, da wir die anderen bits für Flags verwenden.
Eventuell wird an beide Knoten, Ziel und Start gleichzeitig verschickt.
Die QoS ist dabei nicht mehr 0 sondern 2. (Sicherstellung der Paketzustellung und doppelte Pakete werden erkannt und weggeworfen).
Dieses Nachrichtenpaket ist sehr kurz und effizient aufgebaut:
Wir brauchen folgende Informationen: Knotennummer, Läufernummer, Zeitstempel, und der wievielte Hopp es ist.
## Nachrichten von Posten
Sobald sich ein Teilnehmer am Postenschirm per RFID/Kennung anmeldet, wird er an diesem Knoten registriert. Dem Läufer wird ein Zeitstempel und seine ID (Läufernummer) zugewiesen. Die Verknüpfung zwischen Läufernummer und RFID-Code ist im System hinterlegt. Beim nächsten verfügbaren Zeitschlitz wird eine Posten-Nachricht mit der Dienstgüte QoS 2 (Sicherstellung der Paketzustellung, Erkennung und Verwerfen von Duplikaten) an das Ziel übermittelt.
Die Nachrichten werden an das Ziel und optional gleichzeitig an den Start-Knoten gesendet. Beide Knoten müssen eine SSID < 15 besitzen, da die verbleibenden Bits im Adress-Byte für Protokoll-Flags reserviert sind.
Dieses Nachrichtenpaket ist extrem kurz und effizient aufgebaut. Es enthält die Knotennummer, die Läufernummer, den Zeitstempel der Erfassung sowie den aktuellen Hop-Zähler.
Pseudo-Byte-Stream:
`vppMLzzzzRR`
### Struktur (Posten-Nachricht):
* **Byte 0: `(` (0x40)**
* **Data Type Identifier (DTI):** Spezieller Bezeichner für FoxMash-Postenmeldungen.
* **Bytes 1–2: Eigene SSID (`pp`)**
* **Wert:** 16-Bit vorzeichenlose Ganzzahl (`uint16_t`) des sendenden Posten-Knotens.
* **Byte 3: `M` (Ziel-SSID, Hop-Limit / TTL & Flags)**
* **Bits 7–5 (Hop-Limit / TTL):** Startwert für die maximale Anzahl an erlaubten Hops (Time-to-Live). Der Startknoten setzt diesen Wert (z. B. auf 3). Jeder weiterleitende Zwischenknoten verringert (dekrementiert) diesen Wert um 1. Erreicht der Wert `0`, wird das Paket nicht mehr weitergeleitet und verworfen.
* **Bit 4 (ACK-Flag):** `0` für eine normale Nachricht, `1` für ein Bestätigungspaket (ACK).
* **Bits 3–0 (Ziel-SSID):** Die SSID des Zielknotens (Unterstützt IDs von 0 bis 15).
* **Bytes 4–5: `L` (Läufernummer)**
* **Wert:** 16-Bit vorzeichenlose Ganzzahl (`uint16_t`) zur eindeutigen Identifikation des Teilnehmers (Unterstützt IDs von 0 bis 65.535).
* **Bytes 6–9: `zzzz` (Zeitstempel)**
* **Wert:** Vorzeichenbehaftete 32-Bit-Ganzzahl (Millisekunden seit Tagesbeginn), die den exakten Zeitpunkt der Registrierung des Läufers am Posten festhält. Dient auch zur Gültigkeitsprüfung im Zeitrahmen.
* **Bytes 10+: `RR` (Routingliste / Optional)**
* **Wert:** Optionale Liste der Zwischenknoten. Bei der Verwendung von beispielsweise drei Relaisknoten setzt sich das Feld aus `RR RR RR` zusammen. Bleibt leer, wenn eine Direktverbindung besteht.
### Bestätigung und Wiederholungen (Retries)
Die maximale Anzahl an Sendeversuchen (Retries) ist konfigurierbar (Standard: 4). Jedes Posten-Paket muss zwingend bestätigt werden. Die Bestätigung verwendet das exakt gleiche Format (`vppMLzzzzRR`), wobei jedoch **Bit 4 im Byte 3 (`M`) auf `1` gesetzt** ist. Das Antwort-Routing nutzt anhand des im Paket aufgezeichneten Pfades dieselbe Strecke in umgekehrter Richtung (Source Routing). Duplikate werden empfängerseitig anhand des eindeutigen Läufer-Zeitstempels erkannt und verworfen.
+50
Datei anzeigen
@@ -0,0 +1,50 @@
# Protocol MeshCom 4.0
## Stream - Example
```
:~1~9~SP3TUT-1,OE1XAR-12,OE3TKT-12>*:z okolic Pi~~y kto~~ tu jest?~~~~~(~a~
S
Hex: 3A BB 31 F3 39 93 53 50 33 54 55 54 2D 31 2C 4F 45 31 58 41 52 2D 31 32 2C 4F 45 33 54 4B 54 2D 31 32 3E 2A 3A 7A 20 6F 6B 6F 6C 69 63 20 50 69 C5 82 79 20 6B 74 6F C5 9B 20 74 75 20 6A 65 73 74 3F 00 03 88 16 C5 28 8C 61 7E
| =========== | ===================================================================================
| | | |
| | | > APRS Address Header (Source,Path, Destination) - "SP3TUT-1": Source Callsign of the transmitting amateur radio station from Poland, using SSID 1.
| | | > -
| | | >
| | > Flags & Hops [7]
B02 | > Message Identifier "~1~9" 0xBB31F339 - The unique 32-bit Message ID transmitted in Little-Endian format (least significant byte first).
| It is used by the mesh network to track packets and prevent duplication loops.
B01 > APRS Data Type Identifier ":" 0x3A - APRS Text Message, used for person-to-person Direct Messages (DMs), group messages, or general announcements.
Following this character, the parser expects a destination callsign, a tracking number, and the message content.
"@" 0x40 - APRS Position with Timestamp, used for automated position reports that include a precise time (such as weather node beacons or sttic trackers).
It tells the terminal to parse fixed-length chunks for latitude, longitude, and elevation.
"!" 0x21 - APRS Position without Timestamp, used for real-time mobile tracking (e.g., a node) with an integrated GPS sending live movement updates).
It skips the time fields to reduce airtime over LoRa.
";" 0x3B - APRS Object Report, used to map an "Object" rather than a station.
For example, a node can broadcast the location of a local hamfest, emergency shelter, or repeater.
It is followed by a fixed 9-character object name.
">" 0x3E - APRS Status Report: Used to transmit a station's current operating state or hardware information rather than geographic coordinates.
"$" 0x24 - APRS Raw NMEA Sentence: A legacy data type that allows raw GPS string passthrough (like $GPRMC or $GPGGA) directly from a serial GPS mouse.
Other DTI(Data Type Identifiers)
"=" 0x3D - Position with compressed APRS capability, a standard position beacon, but formatted in Base-91 compression to save airtime over busy RF networks.
"/" 0x2F - Mic-E Format Data, Microphone Encoder telemetry. Used by commercial radios (like Yaesu or Kenwood) to embed a GPS burst into a standard voice transmission PTT release.
"`" 0x60 - Mic-E Old Format, Legacy Microphone Encoder burst, rarely used by modern trackers but still supported for backward compatibility.
"_" 0x5F - Positionless Weather Report, used by automated weather stations (like fine-offset units or CWOP stations) to transmit pure wind speed, temperature, and barometric values without re-sending latitude/longitude.
")" 0x29 - Item Report, identical to a map Object (;), but used for temporary or small Points of Interest (POIs) with a much shorter name (1–9 characters) and no timestamp.
"<" 0x3C - Station Capabilities, sent by intelligent digipeaters and internet gateways (iGates) to report what features they support (e.g., whether they handle messaging or special path routing).
"T" 0x54 - Raw Telemetry Data, used for automated analog data streams. Frequently found on high-altitude balloon payloads or weather satellites transmitting five channels of changing mathematical numbers.
"S" 0x53 - Telemetry Structural Definition, defines the scale and offsets of raw numbers sent in the T packet (so client maps like aprs.fi know if a raw value of 142 means volts, millibars, or degrees).
"?" 0x3F - Query/Poll Command, a packet explicitly asking another station or a server to respond. For instance, sending ?APRSP asks surrounding nodes for their current paths.
"{" 0x7B - User-Defined / Proprietary, reserved entirely for experimental protocols or internal applications. MeshCom and similar experimental LoRa platforms utilize these spaces to run custom network setups.
```
## Callsign
```
Every callsign is build with [Personal Amateur Radio Callsign]-[SSID], SSID is a number between 1 and 99.
• -0 (or no suffix): Standard home station / fixed station (usually avoided by MeshCom for mobile operation).
• -1 to -3: Additional stations with potential special functions (e.g., digipeaters).
• -7: Portable stations, handheld radios (e.g., a Heltec or T-Echo node carried in a pocket).
• -9: Mobile stations (vehicles).
• -10: Gateways, digipeaters, or Winlink nodes.
• -11: Pure client stations without gateway functionality.
• -12: Gateway stations including mesh routing functionality (very common for permanently running master nodes with an internet/Wi-Fi connection).
• -13: Weather stations or telemetry sensors.
```
+6
Datei anzeigen
@@ -25,6 +25,11 @@ build_flags =
; -isystem /home/tom/.platformio/packages/framework-espidf/components/esp_libc/platform_include
-Wno-include-next
; --- STRICT MODE FOR PROJECT APPLICATION CODE ONLY ---
#-DGIT_HASH=\"\"${sysenv.GIT_HASH}\"\"
!echo -DGIT_HASH=\\\"$(git rev-parse --short HEAD)\\\"
!echo -DGIT_TAG=\\\"$(git describe --tags --always)\\\"
; These flags are strictly applied ONLY to C++ files inside the src/ directory.
build_src_flags =
; Enables all standard compiler warning flags
@@ -43,3 +48,4 @@ build_src_flags =
-Wno-include-next
; Promotes all warnings to hard compilation errors to guarantee clean code
;-Werror
;-D MATH_COMPARE=1
Datei-Diff unterdrückt, da er zu groß ist. Diff laden
+10 -2
Datei anzeigen
@@ -19,6 +19,7 @@
#include "esp_log.h"
#include "config.hpp"
#include "lora.hpp"
#include "display.hpp"
static const char* TAG = "CLI";
static i2c* i2c_bus = nullptr;
@@ -91,9 +92,11 @@ void process_command(const char* cmd) {
printf(" freq <hz> - Set dynamic radio frequency (433 / 868 MHz bounds)\r\n");
printf(" bw <62|125|250|500>- Set dynamic LoRa bandwidth in kHz\r\n");
printf(" i2c - Scan the configured I2C bus infrastructure\r\n");
printf(" test-display - Run a comprehensive visual test suite\r\n");
}
else if (strcmp(cmd, "version") == 0) {
printf("%s System - Running Version: %s\r\n", Config::PROJECT_TITLE, Config::PROJECT_VERSION);
printf("%s System - Running Version: %s Tag: " GIT_TAG "\r\n", Config::PROJECT_TITLE, Config::PROJECT_VERSION);
printf("Build Date: " __DATE__ ", Git-Hash: " GIT_HASH "\r\n");
}
else if (strcmp(cmd, "status") == 0) {
printf("--- %s Status ---\r\n", Config::PROJECT_TITLE);
@@ -103,7 +106,7 @@ void process_command(const char* cmd) {
printf("ESP-IDF Version : %s\r\n", esp_get_idf_version());
if (lora_system != nullptr) {
// Holt die atomare, thread-sichere Kopie der Telemetriedaten (Stack-Allokation)
// atomar, thread-safe copy of telemtry data (stack allocation)
LoraStateSnapshot snapshot = lora_system->getSnapshot();
printf("LoRa Hardware : %s\r\n", snapshot.isInitialized ? "INITIALIZED" : "FAILED / UNINITIALIZED");
@@ -237,6 +240,11 @@ void process_command(const char* cmd) {
printf("Error: I2C Bus not initialized in CLI!\r\n");
}
}
else if (strcmp(cmd, "test-display") == 0) {
printf("[CLI] Triggering asynchronous OLED font verification...\n");
run_display_font_test(oled_display);
printf("[CLI] Test completed.\n");
}
else {
printf("Unknown command: '%s'. Type 'help' for options.\r\n", cmd);
}
+12 -14
Datei anzeigen
@@ -22,7 +22,7 @@
namespace Config {
// Compile-time constants with strict type enforcement
constexpr const char* PROJECT_TITLE = "FoxMesh";
constexpr const char* PROJECT_VERSION = "1.0.5";
constexpr const char* PROJECT_VERSION = "0.1";
constexpr const char* PROJECT_AUTHORS = "Tom OE3TKT";
// CLI Mute Configuration
@@ -44,35 +44,33 @@ namespace Config {
constexpr gpio_num_t LORA_SCK_PIN = GPIO_NUM_5;
constexpr gpio_num_t LORA_CS_PIN = GPIO_NUM_18;
constexpr gpio_num_t LORA_RST_PIN = GPIO_NUM_23; // Adjusted configuration for T3_V1.6 hardware revision
constexpr gpio_num_t LORA_IRQ_PIN = GPIO_NUM_26; // DIO0_PIN
constexpr gpio_num_t LORA_DIO1_PIN = GPIO_NUM_33; // Wichtig für PLL-Lock & Timeout im RX-Modus
constexpr gpio_num_t LORA_IRQ_PIN = GPIO_NUM_26;
constexpr gpio_num_t LORA_DIO1_PIN = GPIO_NUM_33; // Important: PLL lock & Timeout in RX mode
// Semtech SX1276 LoRa Configuration Parameters
// 433.175 MHz is calculated by: 433175000 / (32000000 / 2^19) = 7097139.2 = 0x6c4b33 // wrong: 0x06C4CD
constexpr uint32_t LORA_DEFAULT_FREQ_HZ = 433175000;
constexpr uint8_t LORA_FRF_MSB = 0x6C; // @TODO: will be removed in v1.1
constexpr uint8_t LORA_FRF_MID = 0x4B; // @TODO: will be removed in v1.1
constexpr uint8_t LORA_FRF_LSB = 0x33; // @TODO: will be removed in v1.1
constexpr uint32_t LORA_DEFAULT_BW_KHZ = 250;
constexpr uint8_t LORA_DEFAULT_SF = 11;
constexpr uint8_t LORA_DEFAULT_CR = 6; // CR 4/6 -> Denominator is 6
// Predefined LoRa Sync Word options for hardware correlation filtering
// Predefined LoRa Sync Word options for hardware correlation filtering, see docs/lora/device-settings.md
constexpr uint8_t LORA_SYNC_WORD_PUBLIC_NETWORK_MODE = 0x34; // MeshCom (ÖVSV standard for amateur radio mesh),
// The Things Network, Helium
constexpr uint8_t LORA_SYNC_WORD_TACTICAL_NETWORK_MODE = 0x24; // Meshtastic (Civilian off-grid tactical mesh network)
constexpr uint8_t LORA_SYNC_WORD_PRIVATE_NETWORK_MODE = 0x12; // Semtech Default (Factory default / Private point-to-point)
constexpr uint8_t LORA_SYNC_WORD_CUSTOM_NETWORK_MODE = 0x55; // Custom / Experimental individual networks (highly isolated)
constexpr uint8_t LORA_SYNC_WORD_EXPERIMENTAL_NETWORK_MODE = 0xAA; // Custom / Experimental individual networks (highly isolated)
constexpr uint8_t LORA_SYNC_WORD_ISOLATED_ARRAY_A = 0x55; // Custom / Experimental individual networks (highly isolated)
constexpr uint8_t LORA_SYNC_WORD_ISOLATED_ARRAY_B = 0xAA; // Custom / Experimental individual networks (highly isolated)
// Active hardware synchronization configuration selector
constexpr uint8_t LORA_SYNC_WORD = LORA_SYNC_WORD_PUBLIC_NETWORK_MODE; // Selected default synchronization profile
// Expand hardware preamble length to 12 symbols for robust network correlation (MeshCom value)
constexpr uint8_t LORA_PREAMBLE_LENGTH_LSB = 0x0C; // Semtech Factor Default is 0x08
// The compiler automatically pre-calculates the 24-bit frequency word at compile-time!
constexpr uint64_t LORA_FRF_CALC = ((uint64_t)LORA_DEFAULT_FREQ_HZ << 19) / 32000000ULL;
constexpr uint8_t LORA_FRF_MSB = static_cast<uint8_t>((LORA_FRF_CALC >> 16) & 0xFFU);
constexpr uint8_t LORA_FRF_MID = static_cast<uint8_t>((LORA_FRF_CALC >> 8) & 0xFFU);
constexpr uint8_t LORA_FRF_LSB = static_cast<uint8_t>(LORA_FRF_CALC & 0xFFU);
constexpr uint8_t LORA_PREAMBLE_LENGTH_MSB = 0x00;
constexpr uint8_t LORA_PREAMBLE_LENGTH_LSB = 0x0C; // Semtech Factor Default is 0x08, MeshCom with 0x0C, i.e. 12 symbols
// --- DYNAMIC COMPILE-TIME BIT-SHIFTING EXPRESSIONS ---
+227 -60
Datei anzeigen
@@ -116,26 +116,139 @@ static const uint8_t font_5x7[][5] = {
{0x00, 0x00, 0x77, 0x00, 0x00}, // 124 |
{0x00, 0x41, 0x36, 0x08, 0x00}, // 125 }
{0x08, 0x08, 0x2a, 0x1c, 0x08}, // 126 ~
// German Umlaute mappings
// {0x7d, 0x11, 0x11, 0x11, 0x7d}, // 127 Ä
// {0x3c, 0x41, 0x41, 0x41, 0x3c}, // 128 Ö
// {0x3d, 0x40, 0x40, 0x40, 0x3d}, // 129 Ü
// {0x20, 0x55, 0x54, 0x54, 0x78}, // 130 ä
// {0x38, 0x54, 0x54, 0x54, 0x38}, // 131 ö
// {0x3c, 0x40, 0x40, 0x20, 0x7c}, // 132 ü
// {0x7f, 0x49, 0x49, 0x36, 0x20} // 133 ß
// new German Umlaute mapping (5x7/5x8 Font Matrix)
{0,0,0,0,0}, //127 not allowed
{0x7d, 0x13, 0x13, 0x13, 0x7d}, // 128 Ä
{0x3c, 0x43, 0x43, 0x43, 0x3c}, // 129 Ö
{0x3d, 0x42, 0x42, 0x42, 0x3d}, // 130 Ü
{0x20, 0x55, 0x54, 0x54, 0x78}, // 131 ä
{0x38, 0x55, 0x55, 0x55, 0x38}, // 132 ö
{0x3c, 0x42, 0x42, 0x22, 0x7c}, // 133 ü
{0x7f, 0x49, 0x49, 0x36, 0x00}, // 134 ß (Letzte Spalte auf 0x00 bereinigt)
// {0x3c, 0x42, 0x51, 0x4a, 0x34} // 134 © Copyright
// {0x3c, 0x42, 0x4d, 0x4d, 0x32} // 135
{0x3c, 0x42, 0x95, 0x95, 0x52}
{0x00, 0x00, 0x00, 0x00, 0x00}, // 127 DEL (Control character)
// --- UTF-8 0xC2 RANGE (ISO-8859-1 Indizes 128 to 191) ---
{0x00, 0x00, 0x00, 0x00, 0x00}, // 128 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 129 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 130 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 131 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 132 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 133 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 134 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 135 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 136 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 137 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 138 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 139 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 140 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 141 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 142 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 143 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 144 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 145 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 146 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 147 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 148 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 149 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 150 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 151 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 152 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 153 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 154 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 155 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 156 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 157 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 158 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 159 Padding (Control character)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 160 Non-breaking space
{0x00, 0x00, 0x2f, 0x00, 0x00}, // 161 Inverted exclamation mark (¡)
{0x43, 0x4c, 0x78, 0x4c, 0x43}, // 162 Cent sign (¢)
{0x18, 0x7c, 0x14, 0x3c, 0x54}, // 163 Pound sign (£)
{0x6c, 0x6c, 0x6c, 0x6c, 0x6c}, // 164 Currency sign (¤)
{0x30, 0x78, 0x4d, 0x78, 0x30}, // 165 Yen sign (¥)
{0x00, 0x00, 0x77, 0x00, 0x00}, // 166 Broken bar (¦)
{0x00, 0x4d, 0x4d, 0x6c, 0x00}, // 167 Section sign / Paragraph (§)
{0x00, 0x00, 0x01, 0x00, 0x00}, // 168 Diaeresis (¨)
{0x00, 0x15, 0x16, 0x7c, 0x14}, // 169 Copyright sign (©)
{0x00, 0x42, 0x7b, 0x42, 0x42}, // 170 Feminine ordinal indicator (ª)
{0x08, 0x14, 0x2a, 0x14, 0x22}, // 171 Left-pointing double-angle quotation mark («)
{0x04, 0x04, 0x04, 0x04, 0x1c}, // 172 Not sign (¬)
{0x00, 0x08, 0x08, 0x08, 0x00}, // 173 Soft hyphen
{0x00, 0x14, 0x14, 0x14, 0x14}, // 174 Registered sign (®)
{0x00, 0x02, 0x02, 0x02, 0x02}, // 175 Macron (¯)
{0x00, 0x03, 0x03, 0x00, 0x00}, // 176 Degree sign (°)
{0x44, 0x44, 0x5f, 0x44, 0x44}, // 177 Plus-minus sign (±)
{0x00, 0x00, 0x07, 0x04, 0x04}, // 178 Superscript two (²)
{0x00, 0x00, 0x05, 0x05, 0x07}, // 179 Superscript three (³)
{0x02, 0x01, 0x02, 0x04, 0x00}, // 180 Acute accent (´)
{0x30, 0x48, 0x30, 0x08, 0x06}, // 181 Micro sign (µ)
{0x00, 0x6e, 0x6e, 0x6e, 0x00}, // 182 Pilcrow sign / Paragraph sign (¶)
{0x00, 0x00, 0x04, 0x00, 0x00}, // 183 Middle dot (·)
{0x00, 0x40, 0x44, 0x40, 0x40}, // 184 Cedilla (¸)
{0x00, 0x00, 0x01, 0x00, 0x00}, // 185 Superscript one (¹)
{0x00, 0x44, 0x7d, 0x40, 0x00}, // 186 Masculine ordinal indicator (º)
{0x22, 0x14, 0x2a, 0x14, 0x08}, // 187 Right-pointing double-angle quotation mark (»)
{0x1c, 0x22, 0x49, 0x49, 0x30}, // 188 Vulgar fraction one quarter (¼)
{0x38, 0x44, 0x54, 0x24, 0x18}, // 189 Vulgar fraction one half (½)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 190 Vulgar fraction three quarters (¾)
{0x40, 0x00, 0x45, 0x06, 0x00}, // 191 Inverted question mark (¿)
// --- UTF-8 0xC3 RANGE (ISO-8859-1 Indizes 192 to 255) ---
{0x7c, 0x12, 0x11, 0x12, 0x7c}, // 192 Capital A with grave (À)
{0x7c, 0x11, 0x12, 0x14, 0x78}, // 193 Capital A with acute (Á)
{0x7c, 0x15, 0x16, 0x14, 0x7c}, // 194 Capital A with circumflex (Â)
{0x7c, 0x16, 0x19, 0x16, 0x7c}, // 195 Capital A with tilde (Ã)
{0x7c, 0x11, 0x13, 0x11, 0x7c}, // 196 Capital A with umlaut (Ä)
{0x7c, 0x11, 0x15, 0x11, 0x7c}, // 197 Capital A with ring above (Å)
{0x7e, 0x51, 0x7f, 0x41, 0x73}, // 198 Capital AE ligature (Æ)
{0x3e, 0x41, 0x49, 0x22, 0x10}, // 199 Capital C with cedilla (Ç)
{0x7f, 0x4a, 0x49, 0x4a, 0x41}, // 200 Capital E with grave (È)
{0x7f, 0x49, 0x4a, 0x4c, 0x40}, // 201 Capital E with acute (É)
{0x7f, 0x4d, 0x4e, 0x4d, 0x41}, // 202 Capital E with circumflex (Ê)
{0x7f, 0x49, 0x4b, 0x49, 0x41}, // 203 Capital E with umlaut (Ë)
{0x00, 0x42, 0x7d, 0x40, 0x00}, // 204 Capital I with grave (Ì)
{0x00, 0x40, 0x7d, 0x42, 0x00}, // 205 Capital I with acute (Í)
{0x00, 0x46, 0x7d, 0x46, 0x00}, // 206 Capital I with circumflex (Î)
{0x00, 0x45, 0x7d, 0x45, 0x00}, // 207 Capital I with umlaut (Ï)
{0x7f, 0x41, 0x41, 0x22, 0x1c}, // 208 Capital Eth (Ð)
{0x7f, 0x05, 0x06, 0x0c, 0x7f}, // 209 Capital N with tilde (Ñ)
{0x3e, 0x42, 0x41, 0x42, 0x3e}, // 210 Capital O with grave (Ò)
{0x3e, 0x40, 0x41, 0x42, 0x3c}, // 211 Capital O with acute (Ó)
{0x3e, 0x44, 0x45, 0x44, 0x3e}, // 212 Capital O with circumflex (Ô)
{0x3e, 0x45, 0x46, 0x45, 0x3e}, // 213 Capital O with tilde (Õ)
{0x3e, 0x41, 0x43, 0x41, 0x3e}, // 214 Capital O with umlaut (Ö)
{0x22, 0x14, 0x08, 0x14, 0x22}, // 215 Multiplication sign (×)
{0x2e, 0x41, 0x41, 0x41, 0x3a}, // 216 Capital O with stroke (Ø)
{0x3f, 0x42, 0x41, 0x40, 0x3f}, // 217 Capital U with grave (Ù)
{0x3f, 0x40, 0x41, 0x42, 0x3e}, // 218 Capital U with acute (Ú)
{0x3f, 0x44, 0x45, 0x44, 0x3f}, // 219 Capital U with circumflex (Û)
{0x3f, 0x41, 0x43, 0x41, 0x3f}, // 220 Capital U with umlaut (Ü)
{0x03, 0x08, 0x70, 0x0c, 0x03}, // 221 Capital Y with acute (Ý)
{0x7f, 0x49, 0x09, 0x09, 0x06}, // 222 Capital Thorn (Þ)
{0x7f, 0x49, 0x49, 0x36, 0x00}, // 223 Small sharp s / Eszett (ß)
{0x20, 0x56, 0x55, 0x56, 0x78}, // 224 Small a with grave (à)
{0x20, 0x55, 0x56, 0x58, 0x70}, // 225 Small a with acute (á)
{0x20, 0x59, 0x5a, 0x58, 0x78}, // 226 Small a with circumflex (â)
{0x20, 0x5a, 0x5d, 0x5a, 0x78}, // 227 Small a with tilde (ã)
{0x20, 0x55, 0x57, 0x55, 0x78}, // 228 Small a with umlaut (ä)
{0x20, 0x55, 0x59, 0x55, 0x78}, // 229 Small a with ring above (å)
{0x24, 0x54, 0x78, 0x54, 0x5c}, // 230 Small ae ligature (æ)
{0x38, 0x44, 0x4c, 0x54, 0x24}, // 231 Small c with cedilla (ç)
{0x38, 0x56, 0x55, 0x56, 0x18}, // 232 Small e with grave (è)
{0x38, 0x55, 0x56, 0x58, 0x10}, // 233 Small e with acute (é)
{0x38, 0x59, 0x5a, 0x58, 0x18}, // 234 Small e with circumflex (ê)
{0x38, 0x55, 0x57, 0x55, 0x18}, // 235 Small e with umlaut (ë)
{0x00, 0x45, 0x7e, 0x40, 0x00}, // 236 Small i with grave (ì)
{0x00, 0x40, 0x7e, 0x45, 0x00}, // 237 Small i with acute (í)
{0x00, 0x49, 0x7e, 0x49, 0x00}, // 238 Small i with circumflex (î)
{0x00, 0x46, 0x7e, 0x46, 0x00}, // 239 Small i with umlaut (ï)
{0x38, 0x44, 0x44, 0x48, 0x7f}, // 240 Small eth (ð)
{0x7c, 0x09, 0x0a, 0x04, 0x78}, // 241 Small n with tilde (ñ)
{0x38, 0x46, 0x45, 0x46, 0x38}, // 242 Small o with grave (ò)
{0x38, 0x45, 0x46, 0x48, 0x30}, // 243 Small o with acute (ó)
{0x38, 0x4a, 0x4b, 0x4a, 0x38}, // 244 Small o with circumflex (ô)
{0x38, 0x4b, 0x4c, 0x4b, 0x38}, // 245 Small o with tilde (õ)
{0x38, 0x45, 0x47, 0x45, 0x38}, // 246 Small o with umlaut (ö)
{0x08, 0x08, 0x2a, 0x08, 0x08}, // 247 Division sign (÷)
{0x38, 0x44, 0x44, 0x44, 0x38}, // 248 Small o with stroke (ø)
{0x3c, 0x42, 0x41, 0x20, 0x7c}, // 249 Small u with grave (ù)
{0x3c, 0x40, 0x41, 0x22, 0x78}, // 250 Small u with acute (ú)
{0x3c, 0x44, 0x45, 0x24, 0x7c}, // 251 Small u with circumflex (û)
{0x3c, 0x41, 0x43, 0x21, 0x7c}, // 252 Small u with umlaut (ü)
{0x0c, 0x50, 0x52, 0x50, 0x3c}, // 253 Small y with acute (ý)
{0x7f, 0x14, 0x14, 0x08, 0x04}, // 254 Small thorn (þ)
{0x0c, 0x51, 0x53, 0x51, 0x3c} // 255 Small y with umlaut (ÿ)
};
/**
@@ -264,14 +377,16 @@ void DisplayController::startTask(uint32_t priority) {
}
/**
* @brief Safe cross-core interface dispatching a text block onto the internal processing pipeline buffer.
* @param line Selected hardware page index mapping from 0 to 7 (Bit 7 disables Auto-Clear).
* @param text Pointer to a standard null-terminated sequence of tracking information.
* @param line Selected hardware page index mapping from 0 to 7.
* @param startX Starting column coordinate index on the horizontal axis (0 to 127).
*/
void DisplayController::printMessage(uint8_t line, const char* text) {
void DisplayController::printMessage(uint8_t line, const char* text, uint8_t startX) {
if (m_msgQueue == nullptr || text == nullptr) return;
DisplayMessage msg = {};
msg.targetLine = line;
msg.startX = (startX > 127) ? 127 : startX; // Clamp to maximum physical display width
// Thread-safe copy buffer sanitization sequence
strncpy(msg.content, text, sizeof(msg.content) - 1);
@@ -311,56 +426,73 @@ void DisplayController::run() {
while (true) {
// Asynchronous structural block yield: consumes zero CPU until a new message package arrives
if (xQueueReceive(m_msgQueue, &incoming, portMAX_DELAY) == pdTRUE) {
if (incoming.targetLine > 7) incoming.targetLine = 7;
// Positioning the internal hardware pointer inside the target page boundary
sendCommand(static_cast<uint8_t>(0xB0 + incoming.targetLine)); // Set page address boundary
sendCommand(0x00); // Set lower column pointer
sendCommand(0x10); // Set higher column pointer
uint8_t totalBytesWritten = 0;
// Extract the Auto-Clear suppression flag from the MSB (Bit 7) and mask the line index
const bool disableAutoClear = incoming.targetLine & 0x80;
const uint8_t cleanLine = incoming.targetLine & 0x07; // Native 0-7 clamp using bitmask
// Basic built-in 5x7 ASCII micro character font generation layer
// This prevents adding bloating third party render dependencies into your tight stack space
size_t textLength = strlen(incoming.content);
// POSITIONING THE HARDWARE POINTER
sendCommand(static_cast<uint8_t>(0xB0 + cleanLine)); // Set page address boundary
// Set the horizontal column start pointer (startX) elegantly without magic 0x00 OR-mask
sendCommand(static_cast<uint8_t>(incoming.startX & 0x0F));
sendCommand(static_cast<uint8_t>(0x10 | (incoming.startX >> 4)));
// Track absolute pixel position starting from the custom X coordinate
uint8_t totalBytesWritten = incoming.startX;
const size_t textLength = strlen(incoming.content);
for (size_t i = 0; i < textLength; i++) {
uint8_t c_primary = static_cast<uint8_t>(incoming.content[i]);
const uint8_t c_primary = static_cast<uint8_t>(incoming.content[i]);
size_t fontIndex = 0;
bool validCharacter = false;
const size_t current_char_idx = i; // Store original index for connection logic
// Basic UTF-8 multi-byte decoder for native German Umlaute translation
if (c_primary == 0xC3 && (i + 1) < textLength) {
uint8_t c_secondary = static_cast<uint8_t>(incoming.content[i + 1]);
i++; // Advance the pointer to consume the compound UTF-8 token pair
// Multi-byte UTF-8 decoder targeting the complete ISO-8859-1 (Latin-1) spectrum
if ((c_primary == 0xC3 || c_primary == 0xC2) && (i + 1) < textLength) {
const uint8_t c_secondary = static_cast<uint8_t>(incoming.content[++i]);
if (c_secondary == 0x84) { fontIndex = 96; validCharacter = true; } // Ä
if (c_secondary == 0x96) { fontIndex = 97; validCharacter = true; } // Ö
if (c_secondary == 0x9C) { fontIndex = 98; validCharacter = true; } // Ü
if (c_secondary == 0xA4) { fontIndex = 99; validCharacter = true; } // ä
if (c_secondary == 0xB6) { fontIndex = 100; validCharacter = true; } // ö
if (c_secondary == 0xBC) { fontIndex = 101; validCharacter = true; } // ü
// Mathematical reconstruction of the raw Latin-1 byte position
// 0xC3 sequence maps to 0xC0-0xFF (c_secondary + 0x40)
// 0xC2 sequence maps directly to 0x80-0xBF
uint8_t iso_char = (c_primary == 0xC3) ? (c_secondary + 0x40) : c_secondary;
// Subtract table offset since font array starts at index 32 (Space)
fontIndex = static_cast<size_t>(iso_char - 32);
validCharacter = true;
}
else if (c_primary == 0xC2 && (i + 1) < textLength) {
uint8_t c_secondary = static_cast<uint8_t>(incoming.content[i + 1]);
i++;
if (c_secondary == 0x9F) { fontIndex = 102; validCharacter = true; } // ß
if (c_secondary == 0xA9) { fontIndex = 103; validCharacter = true; } // ©
}
else if ((c_primary >= 32 && c_primary <= 126) || (c_primary > 127 && c_primary <= 135)) {
// Handle standard printable 7-bit ASCII characters
else if (c_primary >= 32 && c_primary <= 126) {
fontIndex = static_cast<size_t>(c_primary - 32);
validCharacter = true;
}
if (validCharacter && totalBytesWritten < 122) {
uint8_t txBuffer[7] = {0};
uint8_t txBuffer[7];
// copy all 5 font columns in a single, hardware-accelerated burst
txBuffer[0] = 0x40; // Data identifier
// txBuffer[1] = font_5x7[fontIndex][0];
// txBuffer[2] = font_5x7[fontIndex][1];
// txBuffer[3] = font_5x7[fontIndex][2];
// txBuffer[4] = font_5x7[fontIndex][3];
// txBuffer[5] = font_5x7[fontIndex][4];
std::memcpy(&txBuffer[1], font_5x7[fontIndex], 5);
txBuffer[1] = font_5x7[fontIndex][0];
txBuffer[2] = font_5x7[fontIndex][1];
txBuffer[3] = font_5x7[fontIndex][2];
txBuffer[4] = font_5x7[fontIndex][3];
txBuffer[5] = font_5x7[fontIndex][4];
txBuffer[6] = 0x00; // 1-pixel spacing between characters
// Check if current character is a potential line segment
if (c_primary == '-' || c_primary == '_' || c_primary == '=') {
// Look behind: Direct boolean evaluation in a single line
bool connect = (current_char_idx > 0 && incoming.content[current_char_idx - 1] == static_cast<char>(c_primary));
// Look ahead: Only checks if the look-behind didn't already establish a connection
if (!connect && (i + 1) < textLength && incoming.content[i + 1] == static_cast<char>(c_primary)) {
connect = true;
}
// Seamlessly bridge the 1-pixel gap if part of a multi-character line sequence
txBuffer[6] = connect ? txBuffer[5] : 0x00;
} else {
txBuffer[6] = 0x00; // Standard 1-pixel spacing for regular text elements
}
if (m_devHandle != nullptr) {
i2c_master_transmit(m_devHandle, txBuffer, sizeof(txBuffer), 50);
@@ -369,14 +501,13 @@ void DisplayController::run() {
}
}
// AUTO-CLEAR: Fill the remaining horizontal page width with blank pixels to flush out trailing leftovers
if (totalBytesWritten < 128) {
uint8_t remainingBytes = static_cast<uint8_t>(128 - totalBytesWritten);
// AUTO-CLEAR: Fill the remaining horizontal page width with blank pixels only if flag is NOT active
if (!disableAutoClear && totalBytesWritten < 128) {
const uint8_t remainingBytes = static_cast<uint8_t>(128 - totalBytesWritten);
// Allocate a burst buffer with dynamic sizing plus control byte room
uint8_t flushBuffer[129] = {0};
flushBuffer[0] = 0x40; // Data mode identifier
if (m_devHandle != nullptr) {
// Send out the precise amount of zeros required to wipe the right margin clean
i2c_master_transmit(m_devHandle, flushBuffer, static_cast<size_t>(remainingBytes + 1), 50);
@@ -385,3 +516,39 @@ void DisplayController::run() {
}
}
}
/**
* @brief Run a comprehensive visual test suite and clear the screen after 5 seconds.
* @param display Pointer to the active DisplayController instance.
*/
void run_display_font_test(DisplayController* display) {
if (display == nullptr) return;
// Test 1: Standard printable ASCII characters (Lines 0 and 1)
display->printMessage(0, "=== FONT TEST ===", 12);
display->printMessage(1, "ASCII: abcXYZ 123!?", 0);
// Test 2: Native German Umlaute via multi-byte UTF-8 (Lines 2, 3 and 4)
display->printMessage(2, "Umlaute: AE-OE-UE-ss", 0);
display->printMessage(3, "Outputs: Ä Ö Ü ß", 0);
display->printMessage(4, "Outputs: ä ö ü", 0);
// Test 3: Newly supported Latin-1 symbols (Line 5)
display->printMessage(5, "Symbols: 23°C © 2026", 0);
// Test 4: Dynamic line-segment connection logic (Lines 6 and 7)
display->printMessage(6, "--------------------", 0);
display->printMessage(7, "Line test: ___ === _", 0);
// Let the user look at the test pattern for 60 seconds
vTaskDelay(pdMS_TO_TICKS(60000));
// Print a quick shutdown notice to the CLI console before restarting
printf("\r\n[SYSTEM] Visual test completed. Triggering hardware reset now...\n");
vTaskDelay(pdMS_TO_TICKS(1000));
// Perform a clean software-controlled hardware restart of the ESP32
esp_restart();
// Clear all lines by printing empty strings (triggers Auto-Clear)
// for (uint8_t line = 0; line < 8; line++) {
// display->printMessage(line, "", 0);
// }
}
+22 -1
Datei anzeigen
@@ -33,13 +33,27 @@ public:
* @brief Thread-safe non-blocking method to print a message to the OLED display.
* @param line Destination line identifier index (0 to 7 for page addressing mode).
* @param text Null-terminated string message (maximum 20 characters for simple lines).
* @param startX Starting column coordinate index on the horizontal axis (0 to 127). Defaults to 0.
*/
void printMessage(uint8_t line, const char* text);
void printMessage(uint8_t line, const char* text, uint8_t startX = 0);
/**
* @brief Appends text to a line starting at a specific X coordinate WITHOUT clearing the rest of the row.
* Perfect for adding dynamic values next to static labels.
* @param line Selected hardware page index mapping from 0 to 7.
* @param text Pointer to a standard null-terminated sequence of tracking information.
* @param startX Starting column coordinate index on the horizontal axis (0 to 127).
*/
inline void appendMessage(uint8_t line, const char* text, uint8_t startX) {
// Automatically injects the 0x80 bit to tell the consumer task to disable auto-clear
printMessage(static_cast<uint8_t>(line | 0x80), text, startX);
}
private:
// Internal structure mapping the safe asynchronous data pipeline
struct DisplayMessage {
uint8_t targetLine;
uint8_t startX;
char content[24];
};
@@ -54,3 +68,10 @@ private:
esp_err_t sendCommand(uint8_t cmd) const;
esp_err_t initializeHardware();
};
/**
* @brief Run a comprehensive visual test suite and clear the screen after 5 seconds.
* Note: This is a global helper function decoupled from the main class interface.
* @param display Pointer to the active DisplayController instance.
*/
void run_display_font_test(DisplayController* display);
+276 -641
Datei anzeigen
Datei-Diff unterdrückt, da er zu groß ist. Diff laden
+84 -156
Datei anzeigen
@@ -10,69 +10,108 @@
#pragma once
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "lora_types.hpp"
#include "esp_err.h"
#include <iostream>
#include <string>
#include <cstring>
/**
* @brief Simple internal abstraction layer mirroring the raw incoming packet payload.
*/
struct LoraPacket {
float rssi; /**< High-precision calibrated received signal power in dBm. */
float snr; /**< High-precision calibrated signal-to-noise ratio in dB. */
uint8_t length; /**< Payload size explicitly specified in bytes. */
uint8_t payload[256]; /**< Fixed internal memory buffer capturing raw ingress bytes. Max SX1276 FIFO is 256. */
};
/**
* @brief Controller class orchestrating hardware configurations, data transfers, and metrics collection.
*/
class LoraController {
public:
/**
* @brief Simple internal abstraction layer mirroring the raw incoming packet payload.
*/
struct LoraPacket {
float rssi; /**< High-precision calibrated received signal power in dBm. */
float snr; /**< High-precision calibrated signal-to-noise ratio in dB. */
uint8_t length; /**< Payload size explicitly specified in bytes. */
uint8_t payload[256]; /**< Fixed internal memory buffer capturing raw ingress bytes. Max SX1276 FIFO is 256. */
};
explicit LoraController();
virtual ~LoraController();
// Disallow copying to prevent duplicate hardware resource assignment
LoraController(const LoraController&) = delete;
LoraController& operator=(const LoraController&) = delete;
/**
* @brief Configures peripheral resources and registers the background worker thread on Core 1.
* @param priority Scheduler task priority level execution metric.
*/
void startTask(uint32_t priority = 6);
/**
* @brief Transmits a raw data frame onto the physical radio frequency layer.
* @param buffer Pointer to the source byte array holding the packet context.
* @param length Total number of bytes to dispatch over the air interface.
* @return esp_err_t status indicating transaction success.
*/
esp_err_t transmitFrame(const uint8_t* buffer, uint8_t length);
/**
* @brief Dynamically updates the transceiver operating frequency at runtime.
* @param msb Most Significant Byte of the frequency fraction.
* @param mid Middle Significant Byte of the frequency fraction.
* @param lsb Least Significant Byte of the frequency fraction.
*/
// void setFrequency(uint8_t msb, uint8_t mid, uint8_t lsb);
/**
* @brief Thread-safely updates the operational state tracking variables and overrides transceiver registers.
* @param freq_hz Core RF targeting frequency structured in absolute Hertz scale units.
*/
void setFrequency(uint32_t freq_hz);
/**
* @brief Dynamically updates the LoRa signal bandwidth at runtime.
* @param bw_khz Target bandwidth in kHz (e.g., 62, 125, 250, 500).
*/
void setBandwidth(uint32_t bw_khz);
/**
* @brief Generates an atomic, high-performance runtime snapshot of configuration parameters and metrics.
* @details This routine performs a rapid, thread-safe memory copy of the volatile tracking state variables.
* Unlike getPacketListCopy(), this method explicitly excludes the heavy heap-allocated packet list data,
* making it exceptionally lightweight and highly suitable for high-frequency UI display refreshes,
* CLI updates, or periodic remote health diagnostics.
* @note Acquires the internal synchronization mutex lock momentarily to guarantee structural telemetry alignment.
* @return A self-contained snapshot structure mapping the current system hardware configurations and operational counts.
*/
LoraStateSnapshot getSnapshot();
private:
static const char* TAG; ///< Logging identifier tag for ESP_LOG statements.
void* m_spiHandle = nullptr; ///< SPI device interface reference token pointer.
SemaphoreHandle_t m_isrSemaphore = nullptr; ///< Binary semaphore facilitating hardware-to-task sync notifications.
static const char* TAG;
spi_device_handle_t m_spiHandle = nullptr;
TaskHandle_t m_taskHandle = nullptr;
SemaphoreHandle_t m_isrSemaphore = nullptr;
LoraStateTable m_state; ///< Internal volatile tracking record data context.
SemaphoreHandle_t m_stateMutex = nullptr; ///< Mutex primitive enforcing mutual exclusion over m_state modifications.
/**
* @brief Reads a value from an individual internal register address of the transceiver.
* @param address Target transceiver memory sub-space location address byte.
* @return The read byte value configuration currently active.
*/
uint8_t readRegister(uint8_t address);
/**
* @brief Writes a specific option parameter directly into a single transceiver register address.
* @param address Target transceiver memory sub-space location address byte.
* @param value Setting parameter layout specification code byte.
*/
void writeRegister(uint8_t address, uint8_t value);
/**
* @brief Interrupt Service Routine wrapper triggered directly from GPIO state changes.
* @param arg Opaque payload pointer referencing the target thread synchronization primitive.
*/
static void taskTrampoline(void* pvParameters);
static void IRAM_ATTR gpioIsrHandler(void* arg);
/**
* @brief Helper context processing background calculations to prune list overflows.
* @note This internal call assumes that the m_stateMutex lock has already been successfully acquired.
*/
void enforceHistoryLimit();
/**
* @brief Main processing task thread loop loopback entry execution hub.
*/
void run();
uint8_t readRegister(uint8_t address);
void writeRegister(uint8_t address, uint8_t value);
/**
* @brief Static FreeRTOS bridge method mapping execution control context flows.
* @param pvParameters Context reference address pointing back into the instantiating class layer.
*/
static void taskTrampoline(void* pvParameters);
esp_err_t initializeHardware();
void setModeReceiver();
void applyModemConfig();
/**
* @brief Evaluates raw register readings into calibrated RSSI and SNR telemetry metrics.
@@ -85,117 +124,6 @@ private:
*/
void calculateTelemetry(uint8_t rawRssi, uint8_t rawSnr, float& outRssi, float& outSnr);
public:
/**
* @brief Construct a new Lora Controller instance and allocate threading primitives.
*/
LoraController();
size_t utf8_advance(const std::string& str, size_t start_byte, size_t num_chars);
/**
* @brief Destroy the Lora Controller object, releasing active allocations and hardware hooks.
*/
~LoraController();
/**
* @brief Initializes active interface bindings, reset signals, and evaluates valid silicon signatures.
* @return ESP_OK on operational handshake match, alternative error codes upon physical failures.
*/
esp_err_t initializeHardware();
/**
* @brief Programs basic standard profiling configurations and flags continuous reception monitoring modes.
*/
void setModeReceiver();
/**
* @brief Directly transmits a raw binary frame layout out onto the RF medium link.
* @param buffer Reference data memory segment holding the outbound bytes block.
* @param length The complete amount of data characters structured within the payload.
* @return ESP_OK upon transmission launch clearance, error conditions otherwise.
*/
esp_err_t transmitFrame(const uint8_t* buffer, uint8_t length);
/**
* @brief Spawns a dedicated processing task context onto a specifically designated execution core.
* @param priority FreeRTOS scheduling execution assignment depth factor.
*/
void startTask(uint32_t priority);
/**
* @brief Thread-safely updates the operational state tracking variables and overrides transceiver registers.
* @param freq_hz Core RF targeting frequency structured in absolute Hertz scale units.
*/
void setFrequency(uint32_t freq_hz);
/**
* @brief Thread-safely reads out the active target operation frequency metrics context.
* @return Current system baseline operational tuning value expressed in Hertz units.
*/
uint32_t getFrequency();
/**
* @brief Thread-safely updates the operational modem modulation parameter targets.
* @param bw_khz Operating modulation channel grid width metric parameters.
*/
void setBandwidth(uint32_t bw_khz);
/**
* @brief Thread-safely updates the operational spreading factor configuration.
* @param sf Spreading factor scale (6-12).
*/
void setSpreadingFactor(uint8_t sf);
/**
* @brief Thread-safely updates the operational coding rate protection overhead.
* @param cr_denominator Coding rate denominator index (5-8).
*/
void setCodingRate(uint8_t cr_denominator);
/**
* @brief Thread-safely logs an incoming transaction capture directly into the historical context nodes collection.
* @param packet Structural dataset mapping telemetry markers alongside the payload block.
* @param sequence Absolute hardware sequence reference step index.
* @return The unique database ID assigned to the tracking instance container wrapper node.
*/
uint32_t addPacketToList(const LoraPacket& packet, uint32_t sequence);
/**
* @brief Thread-safely extracts and purges an explicit single tracking record node entry via database criteria.
* @param databaseId Unique identifier associated with the specific targeted row block element to discard.
* @return True if a matching criteria target was located and eliminated, false if no ID matched.
*/
bool removePacketFromList(uint32_t databaseId);
/**
* @brief Thread-safely resets the tracked tracking records data tables list back to empty states.
*/
void clearPacketList();
/**
* @brief Thread-safely sorts the data list according to RSSI signal strength levels.
* @param ascending If true, lowest signal strengths come first; if false, strongest signals sort to front.
*/
void sortListByRssi(bool ascending = false);
/**
* @brief Thread-safely recovers structural chronological data ordering schemes according to database IDs.
*/
void sortListChronologically();
/**
* @brief Performs a deep copy operation over the linked list under mutex locks to return a safe detached copy.
* @return A standalone, thread-isolated linked list duplicate snapshot copy containing current history rows.
*/
std::list<LoraPacketNode> getPacketListCopy();
/**
* @brief Generates an atomic, high-performance runtime snapshot of configuration parameters and metrics.
* @details This routine performs a rapid, thread-safe memory copy of the volatile tracking state variables.
* Unlike getPacketListCopy(), this method explicitly excludes the heavy heap-allocated packet list data,
* making it exceptionally lightweight and highly suitable for high-frequency UI display refreshes,
* CLI updates, or periodic remote health diagnostics.
* @note Acquires the internal synchronization mutex lock momentarily to guarantee structural telemetry alignment.
* @return A self-contained snapshot structure mapping the current system hardware configurations and operational counts.
*/
LoraStateSnapshot getSnapshot();
};
+21 -87
Datei anzeigen
@@ -8,72 +8,22 @@
* For the full license terms, see the LICENSE file in the project root.
*/
#pragma once
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#include <list>
#include <vector>
#pragma GCC diagnostic pop
#include <stdint.h>
/**
* @brief Structure representing a single packet node within the doubly-linked tracking list.
*/
struct LoraPacketNode {
/** @brief Dynamic raw binary storage buffer containing the received message payload. */
std::vector<uint8_t> payload;
/** @brief Unique auto-incremented database identifier for targeting explicit items. */
uint32_t databaseId;
/** @brief The absolute sequence index counting the packets received since device boot. */
uint32_t packetSequence;
/** @brief Received Signal Strength Indicator (RSSI) measured in dBm. */
float rssi;
/** @brief Signal-to-Noise Ratio (SNR) measured in dB. */
float snr;
/** @brief Payload size explicitly specified in bytes. */
uint8_t length;
};
/**
* @brief Runtime operational state table monitoring configurations and historical packet data.
* @note Optimized using "largest-to-smallest" data type ordering to completely eliminate memory padding.
*/
struct LoraStateTable {
/** @brief Central database component built on a doubly-linked list for versatile node operations. (Contains pointers) */
std::list<LoraPacketNode> packetList;
/** @brief Soft-limit threshold capping the maximum number of history records stored before auto-purge. (4 or 8 Bytes) */
size_t maxHistorySize = 10;
/** @brief RF carrier operating frequency configured in Hertz. (4 Bytes) */
uint32_t frequencyHz = 433175000;
/** @brief Accumulator metric track-counting successful ingress frames. (4 Bytes) */
uint32_t totalPacketsReceived = 0;
/** @brief Accumulator metric track-counting egress frames transmitted. (4 Bytes) */
uint32_t totalPacketsTransmitted = 0;
/** @brief Identity sequencer pointer controlling the next node's databaseId field. (4 Bytes) */
uint32_t nextDatabaseId = 1;
/** @brief Modem bandwidth performance target configured in kHz. (4 Bytes) */
uint32_t bandwidthKhz = 250;
/** @brief LoRa Spreading Factor configuration (typically ranges between 6 and 12). (1 Byte) */
uint8_t spreadingFactor = 11;
/** @brief Coding Rate denominator setting (e.g., value 6 yields a 4/6 coding protection overhead). (1 Byte) */
uint8_t codingRate = 6;
/** @brief True if hardware was successfully validated and attached during initialization. (1 Byte) */
bool isInitialized = false;
// std::list<LoraPacketNode> packetList; ///< Central database component built on a doubly-linked list for versatile node operations. (Contains pointers)
size_t maxHistorySize = 10; ///< Soft-limit threshold capping the maximum number of history records stored before auto-purge. (4 or 8 Bytes)
uint32_t frequencyHz = 433175000; ///< RF carrier operating frequency configured in Hertz. (4 Bytes)
uint32_t totalPacketsReceived = 0; ///< Accumulator metric track-counting successful ingress frames. (4 Bytes)
uint32_t totalPacketsTransmitted = 0; ///< Accumulator metric track-counting egress frames transmitted. (4 Bytes)
uint32_t nextDatabaseId = 1; ///< Identity sequencer pointer controlling the next node's databaseId field. (4 Bytes)
uint32_t bandwidthKhz = 250; ///< Modem bandwidth performance target configured in kHz. (4 Bytes)
uint8_t currentMode = 0; ///< Current operationl modem mode (1 Byte)
uint8_t spreadingFactor = 11; ///< LoRa Spreading Factor configuration (typically ranges between 6 and 12). (1 Byte)
uint8_t codingRate = 6; ///< Coding Rate denominator setting (e.g., value 6 yields a 4/6 coding protection overhead). (1 Byte)
bool isInitialized = false; ///< True if hardware was successfully validated and attached during initialization. (1 Byte)
// The three 1-byte variables at the bottom align perfectly into a 32-bit slot, leaving minimal trailing padding.
};
@@ -83,31 +33,15 @@ struct LoraStateTable {
* @note Optimized using "largest-to-smallest" data type ordering to minimize memory padding.
*/
struct LoraStateSnapshot {
/** @brief Soft-limit threshold capping the maximum number of history records stored before auto-purge. (4 or 8 Bytes) */
size_t currentHistoryCount;
size_t currentHistoryCount; ///< Soft-limit threshold capping the maximum number of history records stored before auto-purge. (4 or 8 Bytes)
uint32_t frequencyHz; ///< RF carrier operating frequency configured in Hertz. (4 Bytes)
uint32_t bandwidthKhz; ///< Modem bandwidth performance target configured in kHz. (4 Bytes)
uint32_t totalPacketsReceived; ///< Accumulator metric track-counting successful ingress frames. (4 Bytes)
uint32_t totalPacketsTransmitted; ///< Accumulator metric track-counting egress frames transmitted. (4 Bytes)
char currentModeStr[16]; ///< Human-readable string representation of the current operational modem mode. (16 Bytes)
uint8_t spreadingFactor; ///< LoRa Spreading Factor configuration (typically ranges between 6 and 12). (1 Byte)
uint8_t codingRate; ///< Coding Rate denominator setting (e.g., value 6 yields a 4/6 coding protection overhead). (1 Byte)
bool isInitialized; ///< True if hardware was successfully validated and attached during initialization. (1 Byte)
/** @brief RF carrier operating frequency configured in Hertz. (4 Bytes) */
uint32_t frequencyHz;
/** @brief Modem bandwidth performance target configured in kHz. (4 Bytes) */
uint32_t bandwidthKhz;
/** @brief Accumulator metric track-counting successful ingress frames. (4 Bytes) */
uint32_t totalPacketsReceived;
/** @brief Accumulator metric track-counting egress frames transmitted. (4 Bytes) */
uint32_t totalPacketsTransmitted;
/** @brief LoRa Spreading Factor configuration (typically ranges between 6 and 12). (1 Byte) */
uint8_t spreadingFactor;
/** @brief Coding Rate denominator setting (e.g., value 6 yields a 4/6 coding protection overhead). (1 Byte) */
uint8_t codingRate;
/** @brief True if hardware was successfully validated and attached during initialization. (1 Byte) */
bool isInitialized;
/** @brief Current Mode */
char currentModeStr[16];
// The three 1-byte variables at the bottom align perfectly into a 32-bit slot, leaving only 1 byte of trailing padding.
// Elements are strictly organized by size alignment requirements to completely prevent intermittent alignment gaps.
};
+40 -5
Datei anzeigen
@@ -8,11 +8,15 @@
* For the full license terms, see the LICENSE file in the project root.
*/
#ifndef GIT_TAG
#define GIT_TAG "unknown"
#endif
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
//#include "driver/spi_master.h"
#include "driver/spi_master.h"
#include "esp_log.h"
#include "config.hpp"
#include "led.hpp"
@@ -46,6 +50,32 @@ LoraController* lora_module = nullptr;
*/
static LedController led(Config::LED_GPIO_PIN);
/**
* @brief Initializes the physical SPI peripheral for the Semtech SX1276 transceiver.
*
* Configures the VSPI (SPI3_HOST) back-end utilizing strict compile-time layouts
* mapped inside config.hpp and configures automatic DMA channel routing.
*/
void init_lora_spi(void) {
esp_err_t ret;
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = Config::LORA_MOSI_PIN;
buscfg.miso_io_num = Config::LORA_MISO_PIN;
buscfg.sclk_io_num = Config::LORA_SCK_PIN;
buscfg.quadwp_io_num = -1;
buscfg.quadhd_io_num = -1;
buscfg.max_transfer_sz = 0;
// Initialize the physical peripheral bus with automatic DMA channel assignment
ret = spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
return;
}
ESP_LOGI(TAG, "SPI bus initialized successfully on VSPI (SPI3)");
}
// Global application anchor marked as extern "C" for the native ESP-IDF bootloader
extern "C" {
/**
@@ -78,6 +108,9 @@ extern "C" {
// Pass the bus instance dependency to the interactive terminal layer
cli_init(i2c_bus, &led, display);
// Initialize the Semtech LoRa SPI connection layer
init_lora_spi();
// Spin up the background console interface thread on Core 0
start_cli_task();
@@ -88,7 +121,7 @@ extern "C" {
//connects the lora instance with the CLI control directly (commands freq and bw)
cli_init_lora(lora_module);
// start the radio task straight to the Core 1 (Priority 6)
// start the radio task strict to the Core 1 (Priority 6)
lora_module->startTask(6);
ESP_LOGI(TAG, "LoRa subsystem successfully activated on Core 1.");
} else {
@@ -98,16 +131,18 @@ extern "C" {
// Trigger synchronous welcome splash screen frame
if (display != nullptr) {
char msg[32];
snprintf(msg, sizeof(msg), "FoxMesh %s OE3TKT", Config::PROJECT_VERSION);
display->printMessage(1, msg);
snprintf(msg, sizeof(msg), "FoxMesh " GIT_TAG);
display->printMessage(0, msg);
// display->appendMessage(0, GIT_TAG, 12*6);
display->printMessage(1, "----------------------");
}
// Main system watchdog and status monitor loop
while (true) {
ESP_LOGI(TAG, "[%s v%s] Operational. Free Heap: %lu bytes",
Config::PROJECT_TITLE,
Config::PROJECT_VERSION,
(unsigned long)esp_get_free_heap_size());
// ESP_LOGI(TAG, "Git-Hash: %s", GIT_HASH);
vTaskDelay(pdMS_TO_TICKS(5000));
}