Commits vergleichen

..
Autor SHA1 Nachricht Datum
tom bb4fc91703 test: add lora sx1276/1278 decision logic
Build of FoxMesh / build-and-test (push) Successful in 2m56s
2026-10-19 12:14:43 +02:00
tom e847e6c750 no funcional code but store
Build of FoxMesh / build-and-test (push) Successful in 2m41s
2026-10-19 03:14:09 +02:00
tom ff975ac87a Add DIO0 Pin to config
Build of FoxMesh / build-and-test (push) Successful in 2m54s
2026-10-19 01:48:50 +02:00
tom 6a8f5db055 add antenna switch automatic 2026-10-19 01:48:03 +02:00
tom 36c303464f Add .venv path to the environment for pio run
Build of FoxMesh / build-and-test (push) Successful in 4m28s
2026-10-18 22:19:57 +02:00
tom 04e95484a6 INstall platfomio in venv
Build of FoxMesh / build-and-test (push) Failing after 14s
2026-10-18 22:14:50 +02:00
tom 8eb3a7ff18 Python already installed on host machine
Build of FoxMesh / build-and-test (push) Failing after 4s
2026-10-18 22:06:51 +02:00
tom 35a2aae086 fix wrong ident in yaml
Build of FoxMesh / build-and-test (push) Failing after 6s
2026-10-18 22:01:53 +02:00
tom f6932c8c91 actions/cache@v6 : remove absolute paths
foxmesh-ci.yml (push) Invalid workflow file
2026-10-18 21:55:15 +02:00
tom fec37babcf FIX yaml with https://yamlvalidate.com
foxmesh-ci.yml (push) Invalid workflow file
2026-10-18 21:43:43 +02:00
tom 7680928dad update yaml file for gitea
foxmesh-ci.yml (push) Invalid workflow file
2026-10-18 21:30:31 +02:00
tom a14c540e1d documentation: LoRa Mesh and Device Settings
foxmesh-ci.yml (push) Invalid workflow file
2026-10-18 21:08:58 +02:00
tom d95a9a4620 lora updates with specific settings 2026-10-18 21:08:32 +02:00
tom 032b9be518 remove spi initialization from main b/c it is done in lora module 2026-10-18 21:07:56 +02:00
tom e7fbdb0f8d add compiler directive for no-include-next in both build_flags and build_src_flags 2026-10-18 21:07:01 +02:00
tom 1d1020716e add lora system snapshot, fix some lora troubles, interrupt
foxmesh-ci.yml (push) Invalid workflow file
2026-10-18 18:06:32 +02:00
tom 9dd939b335 UPD version 1.0.4
foxmesh-ci.yml (push) Invalid workflow file
2026-10-18 15:55:03 +02:00
tom a6ad249526 move calculateTelemetry to the end of private: area, built doxygen style 2026-10-18 14:46:46 +02:00
tom e0767d3322 FIX typo in comments 2026-10-18 14:45:40 +02:00
tom 418042bb3d clean whitespaces
foxmesh-ci.yml (push) Invalid workflow file
2026-10-18 14:34:29 +02:00
tom fb542f9661 Add a static packetCounter inside LoRa receiver and display in hex , refactoring LoRa driver 2026-10-18 14:33:17 +02:00
24 geänderte Dateien mit 3005 neuen und 1816 gelöschten Zeilen

Keine Dateien entsprechen Ihrer Suche

-62
Datei anzeigen
@@ -1,62 +0,0 @@
name: Build and Release Typst PDF
on:
push:
branches:
- main
tags:
- 'v*' # Triggers when a release tag like v1.0.0 is pushed
workflow_dispatch: # Enables manual start in the Web interface
jobs:
build-pdf:
runs-on: host
steps:
# 1. Checkout the source code from Git
- name: Checkout Repository
uses: actions/checkout@v4
# 2. Download and extract the latest Typst Binary (No installation required)
- name: Download Latest Typst
run: |
cd docs/lora/
XZ_FILE_URL=$(curl -s https://api.github.com/repos/typst/typst/releases/latest | jq -r '.assets[] | select(.name | endswith("x86_64-unknown-linux-musl.tar.xz")) | .browser_download_url')
echo "$XZ_FILE_URL"
curl -L "$XZ_FILE_URL" | tar -xJ --strip-components=1
chmod +x ./typst
# 3. Compile the PDF using the local binary
- name: Compile Typst Documents
run: |
cd docs/lora/
ls -la
./typst compile foxmesh-protocol.typ FoxMesh_Protocol.pdf
./typst compile foxmesh-protocol_de.typ FoxMesh_Protocol_de.pdf
# 3. Upload PDF as a Gitea artifact (runs on every push to main)
- name: Upload PDF Artifact
uses: actions/upload-artifact@v7
with:
name: foxmesh-protocol-pdf
path: docs/lora/FoxMesh_Protocol.pdf
retention-days: 14
archive: false
- name: Upload PDF Artifact de
uses: actions/upload-artifact@v7
with:
name: foxmesh-protocol-de-pdf
path: docs/lora/FoxMesh_Protocol_de.pdf
retention-days: 14
archive: false
# 4. Optional: PDF direkt an ein Gitea-Release anhängen (wenn ein Tag gepusht wird)
# - name: Upload to Gitea Release
# if: startsWith(github.ref, 'refs/tags/')
# uses: svenstaro/upload-release-action@v2
# with:
# repo_token: ${{ secrets.GITHUB_TOKEN }}
# file: FoxMesh_Engineering_Manual.pdf
# asset_name: FoxMesh_Engineering_Manual.pdf
# tag: ${{ github.ref }}
# overwrite: true
+3 -8
Datei anzeigen
@@ -2,11 +2,8 @@ name: Build of FoxMesh
on:
push:
branches: [ main ]
pull_request:
branches: [ main ] # Trigger the workflow on pull request events targeting the main branch
tags:
- 'v*' # Triggers when a release tag like v1.0.0 is pushed
branches:
- main
workflow_dispatch:
inputs:
target_branch:
@@ -26,13 +23,11 @@ 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, with fetch-depth: 0 and fetch-tags: true -> loading all tags
# 1. Checkout the target branch
- 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
-3
Datei anzeigen
@@ -22,6 +22,3 @@ sdkconfig.*
# --- Log files ---
build_log.txt
logs/
# PDF files generated
*.pdf
!docs/lora/aprs*.pdf
+2 -3
Datei anzeigen
@@ -28,9 +28,8 @@ The firmware is designed to target popular LoRa development ecosystems, with pri
## 🌐 Project Mirrors / Hosting
To ensure high availability and decentralization, this project is maintained across multiple platforms:
* **Primary Self-Hosted Repo (Gitea):** [git.kuschel.at/tom/FoxMesh](https://git.kuschel.at/tom/FoxMesh)
* **Mirror Repo (GitHub):** [github.com/tkuschel/FoxMesh](https://github.com/tkuschel/FoxMesh)
* **Primary Repo (GitHub):** [github.com/tkuschel/FoxMesh](https://github.com/tkuschel/FoxMesh)
* **Self-Hosted Mirror (Gitea):** [git.kuschel.at/tom/FoxMesh](https://git.kuschel.at/tom/FoxMesh)
*Note: Both repositories are automatically kept synchronized. You can clone, open issues, or contribute on either platform.*
Binäre Datei nicht angezeigt.
-28
Datei anzeigen
@@ -106,31 +106,3 @@ All public APIs, public structures, and method signatures within header files mu
*/
void updateProfile(const BlinkProfile& profile);
```
## 8. Git Branching and Commit Standards
To ensure traceability and maintain a clean repository history, FoxMesh strictly follows a standardized branching and commit convention based on *Conventional Commits*.
### 8.1 Branch Naming Convention
Developers must prefix every newly created Git branch with a specific scope identifier followed by a slash (`/`), the Gitea issue number, and a short description.
* **Format:** `<type>/<issue-number>-<short-description>`
* **Example:** `docs/123-readme-update` or `feat/45-lora-mesh-ack`
### 8.2 Scope Identifiers
The following prefixes are permitted for branch creation and commit messages:
* `feat/` (or `feature/`): Introduction of new hardware or software functionalities.
* `fix/` (or `bugfix/`): Resolution of bugs, runtime stability issues, or crashes.
* `docs/`: Exclusively reserved for pure documentation updates (Readmes, Doxygen, Wiki).
* `refactor/`: Code reorganization or optimization without changing external behavior.
* `chore/`: Updates to the build system, `platformio.ini`, CI/CD pipelines, or external dependencies.
### 8.3 Commit and Issue Automation
Every commit message should start with the lowercase prefix (e.g., `docs: description`) and must include the targeted Gitea issue number in parentheses at the end to allow automated tracking and timeline linkage:
```bash
git commit -m "docs: added docker installation guide (#123)"
```
When submitting a Pull Request (PR) to the `main` branch, developers must use standard closure keywords (`closes #123`, `resolves #123`) within the PR description to ensure Gitea automatically resolves the ticket upon integration.
Binäre Datei nicht angezeigt.
-107
Datei anzeigen
@@ -1,107 +0,0 @@
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
@@ -1,91 +0,0 @@
# 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.
-318
Datei anzeigen
@@ -1,318 +0,0 @@
#set page(
paper: "a4",
margin: (x: 2cm, top: 3.5cm, bottom: 3cm),
// Kopfzeile mit dynamischem Platzhalter
header: align(right)[
#text(size: 8pt, fill: luma(120))[FoxMash Protocol v0.1]
]
)
#set text(font: "Libertinus Serif", lang: "en", size: 10.5pt)
#set par(justify: true)
// Styling-Funktion für Tabellen-Header
#let table-header(..cells) = {
cells.pos().map(cell => text(weight: "bold", fill: rgb("1a1a1a"))[#cell])
}
// ==========================================
// DYNAMISCHES HEADER-LOGO (Fuchs + ARDF-Schirm)
// ==========================================
#place(top + left, dy: -2.5cm)[
#grid(
columns: (60pt, 1fr),
gutter: 15pt,
align: horizon,
// Der ARDF-Postenschirm (Orange/Weißes Dreieck) gepaart mit einer Fuchs-Farbe
stack(
spacing: 2pt,
rect(width: 50pt, height: 50pt, stroke: 1pt + rgb("1a1a1a"), fill: white)[
#place(top + left)[
#polygon(
fill: rgb("ff5500"), // ARDF Orange
(0pt, 50pt),
(50pt, 0pt),
(50pt, 50pt)
)
]
// Stilisierte Fuchs-Silhouette (Ohren und Schnauze) ohne Kurven-Fehler
#place(center + horizon, dy: 2pt)[
#polygon(
fill: rgb("1a1a1a"),
(0pt, 0pt), // Linkes Ohr Spitze
(6pt, 8pt), // Kopf Mitte oben
(12pt, 0pt), // Rechtes Ohr Spitze
(12pt, 10pt), // Rechte Wange
(6pt, 18pt), // Schnauze unten
(0pt, 10pt) // Linke Wange
)
]
]
),
[
#text(size: 24pt, weight: "bold", tracking: 1pt, fill: rgb("1a1a1a"))[FoxMash] \
#v(-8pt)
#text(size: 10pt, weight: "medium", fill: rgb("ff5500"))[Amateur Radio Direction Finding Mesh Protocol]
]
)
]
#v(1cm)
= Protocol
English version. In case of doubt, the English text shall be binding. \
#link("foxmash-protocol_de.pdf")[_Deutsche Version anzeigen_]
== 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:
```text
{OE3TKT-03CpnnnnxRSqxRSqxRSq..
```
=== Structure
#table(
columns: (1.5fr, 2fr, 5fr),
align: (center, left, left),
stroke: 0.5pt + luma(150),
fill: (x, y) => if y == 0 { rgb("f0f0f0") } else { none },
..table-header("Byte / Offset", "Field / Format", "Description"),
[Byte 0], [`"{"` `0x7B`], [
*Data Type Identifier (DTI):* Derived from the APRS & MeshCom protocol specification (see #link("https://ui-view.net")[APRS Protocol Reference]). \
*Reasoning:* Currently marked as `[Unused]` in the specification; character visually resembles a station icon.
],
[Bytes 1–6], [`"OE3TKT"` \ (6 Bytes ASCII)], [
*International Call Sign:* Formatted as a 1–2 character prefix, followed by one or more digits, and a 1–3 letter suffix. Strictly 6 characters wide, right-padded with trailing spaces if shorter.
],
[Byte 7], [`"-"` `0x2D` or `"~"` `0x7E`], [
*Node Type Separator:* Hyphen for standard nodes, tilde for backup/hot-standby nodes. Serves as a visual delimiter and structural anchor for the parser.
],
[Bytes 8–9], [`"03"`, or `"S6"` \ (2 Bytes ASCII)], [
*Station SSID Identifier:* Sub-string format representing the node ID. Supports numeric IDs (0–99) or extended notation (e.g., `"S6"`) i.e. any letters as described in #link("https://aprs11/SSIDs.txt")[aprs11/SSIDs.txt] (e.g., `OE3TKT-S6` for any special 6th station).
],
[Byte 10], [Own Battery Level \ (`unsigned char`)], [
*Value:* Capacity in percent (0 to 100%). Values above 100 indicate charging or mains power.
],
[Byte 11], [Protocol / Net Flags \ (`unsigned char`)], [
*Network Flags / Padding:* Reserved for mesh network flags and hop control. Ensures precise 4-byte memory alignment for the subsequent 32-bit timestamp.
],
[Bytes 12–15], [`nnnn` / \ Timestamp \ (`int32_t`, Little-Endian)], [
*Timestamp:* Signed 32-bit integer representing the number of milliseconds since the start of the competition day. Formatted as Little-Endian (LSB first) and aligned to a 4-byte boundary for optimized, native ESP32 pointer access. All official ARDF contest times and logs must be recorded in Coordinated Universal Time (UTC). To convert _[Local Time Name]_ to UTC, subtract/add _[X]_ hours (UTC = _[Local Time]_ +/- _[X]_h). Please pay close attention to date changes for events crossing midnight UTC.
],
[Bytes 15+], [Heard Neighbor Nodes \ (`xRSqxRSqxRSq...`)], [
Multi-byte blocks containing mesh neighbor details (SSID, link quality, battery status). See detailed breakdown below.
]
)
==== Heard Neighbor Nodes Block Breakdown (Bytes 15+)
#table(
columns: (1.5fr, 2fr, 5fr),
align: (center, left, left),
stroke: 0.5pt + luma(150),
fill: (x, y) => if y == 0 { rgb("f0f0f0") } else { none },
..table-header("Byte Offset", "Field / Format", "Description"),
[Bytes 15–16], [Neighbor SSID \ (2 Bytes ASCII)], [Two ASCII characters representing the neighbor's station ID/SSID (e.g., `"03"` or `"S6"`). This matches the exact sub-string format from Bytes 8–9 of the source node for easy string matching.],
[Byte 17], [RSSI Value (`R`) \ (`unsigned char`)], [Stored as RSSI + 164 (e.g., `0x3A` = 58 -> 58 - 164 = -106 dBm).],
[Byte 18], [SNR Value (`S`) \ (`signed char`)], [Stored as SNR multiplied by 4 (e.g., `0x42` = 66 -> 66 / 4 = +16.5 dB).],
[Byte 19], [Status Byte (`q`) \ (`unsigned char`)], [Combined Quality and Battery Status Byte. (See Bit-mapping below).]
)
#v(0.5em)
*Byte 19 (`q`) Bit-mapping:*
#table(
columns: (1.5fr, 1.5fr, 6fr),
align: (center, center, left),
stroke: 0.5pt + luma(150),
fill: (x, y) => if y == 0 { rgb("f5f5f5") } else { none },
..table-header("Bits", "Value", "Definition"),
[Bits 7–6], [`00`], [Normal (Battery OK / Powered)],
[Bits 7–6], [`01`], [Low (Battery low, intervention recommended)],
[Bits 7–6], [`10`], [Critical (Battery critical, node might shut down soon)],
[Bits 7–6], [`11`], [Reserved],
[Bits 5–0], [0 to 63], [Moving average counter of how many times this station was heard within the last 30 minutes. If >= 3, the link is considered stable. Dropped if 0 within 30 minutes.]
)
== 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:
```text
(ppMLzzzzRR
```
=== Structure (Checkpoint Message)
#table(
columns: (1.5fr, 2fr, 5fr),
align: (center, left, left),
stroke: 0.5pt + luma(150),
fill: (x, y) => if y == 0 { rgb("f0f0f0") } else { none },
..table-header("Byte / Offset", "Field / Format", "Description"),
[Byte 0], [`(` (0x40)], [
*Data Type Identifier (DTI):* Special identifier designated for FoxMash checkpoint messages.
],
[Bytes 1–2], [Own SSID (`pp`) \ (2 Bytes ASCII)], [
Two ASCII characters representing the transmitting checkpoint node.
],
[Byte 3], [Routing & Flags (`M`) \ (`unsigned char`)], [
Combined Target SSID, Hop Count, and ACK Flags. (See Bit-mapping below).
],
[Bytes 4–7], [`zzzz` (Timestamp) \ (`int32_t`, Little-Endian)], [
*Timestamp:* Signed 32-bit integer representing the number of milliseconds since the start of the competitoin day. Formatted as Little-Endian (LSB first) and aligned to a 4-byte boundery for direct, high-performance ESP32 pointer access.
],
[Bytes 8–9], [Runner Number (`L`) \ (`uint16_t`)], [
Unique participant identification / runner number (Supports IDs from 0 to 65,535).
],
[Bytes 10], [Hop Count / Flags \ (`unsigned char`)], [
Current network hop count and remaing mesh routing flags.
]
)
#v(0.5em)
*Byte 3 (`M`) Bit-mapping:*
#table(
columns: (1.5fr, 1.5fr, 6fr),
align: (center, center, left),
stroke: 0.5pt + luma(150),
fill: (x, y) => if y == 0 { rgb("f5f5f5") } else { none },
..table-header("Bits", "Flag / Field", "Definition"),
[Bits 7–5], [Hop Count], [Dynamic routing counter. `0` if destination is seen directly with good quality. Incremented by 1 if no ACK within 5 minutes. Discarded if it exceeds max hop value.],
[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).]
)
=== 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.
== Protocol Optimization Analysis
=== Hardware-Optimized Protocol Design for ESP32 and LoRa
When designing low-power, high-performance mesh networks using *ESP32* microcontrollers over *LoRa* modulation, data structure layout critically impacts both transmission time (Airtime) and CPU efficiency. The updated FoxMash protocol layout restructures fields to achieve an optimal balance between RF efficiency and hardware execution.
==== Why this Layout is Perfect
1. *Native Word Alignment (No Performance Penalties):* The Tensilica Xtensa and RISC-V architectures used in ESP32 chips are natively *32-bit Little-Endian* processors. Accessing a 4-byte integer from a memory address that is not a multiple of 4 (_unaligned access_) causes the hardware to execute multi-cycle fixes, shifting bytes internally. In worst-case scenarios or specific compiler configurations, this causes a `LoadStoreAlignmentCause` hardware crash (Core Dump). By placing the 32-bit timestamp at precisely *Byte 4* (Checkpoint) and *Byte 12* (Beacon), the integer aligns perfectly with the processor's natural word boundaries.
2. *Zero-Copy Pointer Casting:* Because fields are naturally aligned, the firmware does not need to waste clock cycles using `memcpy()` or bit-shifting operators (`<<`, `|`) to reconstruct integers from individual bytes. The application layer can execute direct pointer casting directly out of the LoRa hardware receive buffer.
3. *Zero Airtime Overhead:* Traditional alignment techniques insert dummy padding bytes (`0x00`) to force alignment, which inflates the packet size. In LoRa networks, every additional byte directly increases the Time-on-Air (ToA), resulting in higher energy consumption and a higher probability of packet collisions. This optimized layout achieves perfect alignment solely by *reordering existing essential fields*, keeping the frame size strictly minimal.
4. *Human-Readable Delimiters Maintained:* Keeping the standard hyphen (`"-"`) or tilde (`"~"`) delimiter between the international call sign and the SSID preserves compatibility with legacy APRS/MeshCom parsers and keeps raw hexadecimal logs intuitive during serial debugging.
=== High-Performance C++ Implementation Example
The following code snippet demonstrates how to leverage this memory-aligned layout within an ESP32 application utilizing standard Arduino or ESP-IDF LoRa libraries.
#rect(width: 100%, stroke: 0.5pt + luma(150), fill: luma(245), radius: 4pt)[
```cpp
#include <Arduino.h>
// Ensure the compiler doesn't add structural padding within our structs
#pragma pack(push, 1)
struct BeaconPacket {
char dti; // Offset 0 (0x7B '{')
char callsign[6]; // Offset 1 (e.g., "OE3TKT")
char separator; // Offset 7 (e.g., '-')
char ssid[2]; // Offset 8 (e.g., "03")
uint8_t battery; // Offset 10 (0-100%)
uint8_t net_flags; // Offset 11 (Routing control / Padding)
int32_t timestamp_ms; // Offset 12 -> PERFECTLY 4-BYTE ALIGNED!
uint8_t neighbor_block[]; // Offset 16+ (Dynamic mesh payload)
};
struct CheckpointPacket {
char dti; // Offset 0 (0x28 '(')
uint16_t own_ssid; // Offset 1
uint8_t routing_flags; // Offset 3 (Target / Control Flags)
int32_t timestamp_ms; // Offset 4 -> PERFECTLY 4-BYTE ALIGNED!
uint16_t runner_id; // Offset 8
uint8_t hop_count; // Offset 10
};
#pragma pack(pop)
// Example parser function processing incoming raw LoRa bytes
void processIncomingLoRa(uint8_t* rxBuffer, size_t packetLength) {
if (packetLength < 1) return;
// Check Data Type Identifier (DTI)
char dti = rxBuffer[0];
if (dti == '(') {
// Zero-copy direct cast pointer access
CheckpointPacket* cp = (CheckpointPacket*)rxBuffer;
// Fast native access directly from memory
int32_t time = cp->timestamp_ms;
uint16_t runner = cp->runner_id;
Serial.printf("Runner %d checked in. Local Timestamp: %d ms\n", runner, time);
}
else if (dti == '{') {
BeaconPacket* beacon = (BeaconPacket*)rxBuffer;
// Fast native access directly from memory
int32_t time = beacon->timestamp_ms;
Serial.printf("Beacon received. Network Timestamp: %d ms\n", time);
}
}
```
]
// ==========================================
// GESTALTETER FOOTER (Name & Typst-Hinweis)
// ==========================================
#v(3em)
#line(length: 100%, stroke: 0.5pt + luma(200))
#v(0.5em)
#align(center)[
#text(size: 9pt, fill: luma(100))[
Document maintained and compiled by *[OE3TKT, Tom]* \
#v(2pt)
#text(size: 8pt, style: "italic")[
Typeset beautifully with #link("https://typst.app")[Typst] #box(width: 8pt, image(bytes("<svg viewBox='0 0 24 24' width='8' height='8'><path fill='#ff5500' d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/></svg>"), format: "svg"))
]
]
]
-106
Datei anzeigen
@@ -1,106 +0,0 @@
# 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.
-317
Datei anzeigen
@@ -1,317 +0,0 @@
#set page(
paper: "a4",
margin: (x: 2cm, top: 3.5cm, bottom: 3cm),
// Kopfzeile mit dynamischem Platzhalter
header: align(right)[
#text(size: 8pt, fill: luma(120))[FoxMash-Protokoll v0.1]
]
)
#set text(font: "Libertinus Serif", lang: "de", size: 10.5pt)
#set par(justify: true)
// Styling-Funktion für Tabellen-Header
#let table-header(..cells) = {
cells.pos().map(cell => text(weight: "bold", fill: rgb("1a1a1a"))[#cell])
}
// ==========================================
// DYNAMISCHES HEADER-LOGO (Fuchs + ARDF-Schirm)
// ==========================================
#place(top + left, dy: -2.5cm)[
#grid(
columns: (60pt, 1fr),
gutter: 15pt,
align: horizon,
// Der ARDF-Postenschirm (Orange/Weißes Dreieck) gepaart mit einer Fuchs-Farbe
stack(
spacing: 2pt,
rect(width: 50pt, height: 50pt, stroke: 1pt + rgb("1a1a1a"), fill: white)[
#place(top + left)[
#polygon(
fill: rgb("ff5500"), // ARDF Orange
(0pt, 50pt),
(50pt, 0pt),
(50pt, 50pt)
)
]
// Stilisierte Fuchs-Silhouette (Ohren und Schnauze) ohne Kurven-Fehler
#place(center + horizon, dy: 2pt)[
#polygon(
fill: rgb("1a1a1a"),
(0pt, 0pt), // Linkes Ohr Spitze
(6pt, 8pt), // Kopf Mitte oben
(12pt, 0pt), // Rechtes Ohr Spitze
(12pt, 10pt), // Rechte Wange
(6pt, 18pt), // Schnauze unten
(0pt, 10pt) // Linke Wange
)
]
]
),
[
#text(size: 24pt, weight: "bold", tracking: 1pt, fill: rgb("1a1a1a"))[FoxMash] #v(-8pt)
#text(size: 10pt, weight: "medium", fill: rgb("ff5500"))[Amateur Radio Direction Finding Mesh Protocol]
]
)
]
#v(1cm)
= Protokoll
Deutsche Version. Im Zweifelsfall ist der englische Text verbindlich. \
#link("foxmash-protocol.pdf")[_Show English version_]
== Automatische Stationen an den ARDF-Senderanlagen (Füchse)
Ein Fuchs sendet regelmäßig seine Kennung und Telemetriedaten aus.
Dies geschieht, wenn möglich, zu Zeiten, in denen der Fuchs nicht auf den Bändern 80m oder 2m sendet.
Dies dient einerseits der Zeitsynchronisation benachbarter Knoten und andererseits der regelmäßigen offiziellen Nennung des Rufzeichens.
Meist wird das Rufzeichen des Organisators oder Ausrichters des Peilwettbewerbs verwendet.
== Beacon (Bake)
Aufgebaut auf Basis der modifizierten AX.25-, APRS- und MeshCom-Protokolle wie folgt:
Pseudo-Byte-Stream:
```text
{OE3TKT-03CpnnnnxRSqxRSqxRSq..
```
=== Struktur
#table(
columns: (1.5fr, 2fr, 5fr),
align: (center, left, left),
stroke: 0.5pt + luma(150),
fill: (x, y) => if y == 0 { rgb("f0f0f0") } else { none },
..table-header("Byte / Offset", "Feld / Format", "Beschreibung"),
[Byte 0], [`"{"` `0x7B`], [
*Data Type Identifier (DTI):* Abgeleitet aus der APRS- und MeshCom-Protokollspezifikation (siehe #link("https://ui-view.net")[APRS-Protokollreferenz]). \
*Begründung:* Aktuell in der Spezifikation als `[Unused]` markiert; Zeichen ähnelt visuell einem Stationssymbol.
],
[Bytes 1–6], [`"OE3TKT"` \ (6 Bytes ASCII)], [
*Internationales Rufzeichen:* Formatiert als 1–2 Zeichen Präfix, gefolgt von einer oder mehreren Ziffern und einem 1–3 Zeichen Suffix. Strikt 6 Zeichen breit; bei kürzeren Rufen rechts mit Leerzeichen aufgefüllt.
],
[Byte 7], [`"-"` `0x2D` oder `"~"` `0x7E`], [
*Knotentyp-Trennzeichen:* Bindestrich für Standardknoten, Tilde für Backup- / Hot-Standby-Knoten. Dient als visueller Begrenzer und struktureller Anker für den Parser.
],
[Bytes 8–9], [`"03"`, oder `"S6"` \ (2 Bytes ASCII)], [
*Stations-SSID-Kennung:* Teilstring-Format, das die Knoten-ID darstellt. Unterstützt numerische IDs (0–99) oder erweiterte Schreibweisen (z. B. `"S6"`), d. h. alle Zeichen wie in #link("https://aprs11/SSIDs.txt")[aprs11/SSIDs.txt] beschrieben (z. B. `OE3TKT-S6` für eine spezielle 6. Station).
],
[Byte 10], [Eigener Batteriestand \ (`unsigned char`)], [
*Wert:* Kapazität in Prozent (0 bis 100 %). Werte über 100 weisen auf Ladevorgang oder Netzbetrieb hin.
],
[Byte 11], [Protokoll- / Netz-Flags \ (`unsigned char`)], [
*Netzwerk-Flags / Padding:* Reserviert für Mesh-Netzwerk-Flags und Hop-Steuerung. Gewährleistet die präzise 4-Byte-Speicherausrichtung (Alignment) für den nachfolgenden 32-Bit-Zeitstempel.
],
[Bytes 12–15], [`nnnn` / \ Zeitstempel \ (`int32_t`, Little-Endian)], [
*Zeitstempel:* Vorzeichenbehafteter 32-Bit-Integer, der die Anzahl der Millisekunden seit Beginn des Wettbewerbstages darstellt. Formatiert als Little-Endian (LSB zuerst) und auf eine 4-Byte-Grenze ausgerichtet für optimierten, nativen ESP32-Zeigerzugriff. Alle offiziellen ARDF-Wettbewerbszeiten und -Logs müssen in koordinierter Weltzeit (UTC) geführt werden. Um _[Lokaler Zeitzonenname]_ in UTC umzurechnen, subtrahiere/addiere _[X]_ Stunden (UTC = _[Lokale Zeit]_ +/- _[X]_h). Bitte achte genau auf Datumswechsel bei Ereignissen, die UTC-Mitternacht überschreiten.
],
[Bytes 16+], [Gehörte Nachbarknoten \ (`xRSqxRSqxRSq...`)], [
Multi-Byte-Blöcke, die Details zu Mesh-Nachbarknoten enthalten (SSID, Verbindungsqualität, Batteriestatus). Siehe detaillierte Aufschlüsselung unten.
]
)
==== Aufschlüsselung des Blocks der gehörten Nachbarknoten (Bytes 16+)
#table(
columns: (1.5fr, 2fr, 5fr),
align: (center, left, left),
stroke: 0.5pt + luma(150),
fill: (x, y) => if y == 0 { rgb("f0f0f0") } else { none },
..table-header("Byte-Offset", "Feld / Format", "Beschreibung"),
[Bytes 16–17], [Nachbar-SSID \ (2 Bytes ASCII)], [
Zwei ASCII-Zeichen, welche die SSID des Nachbarknotens darstellen (z. B. `"03"` oder `"S6"`). Dies entspricht dem exakten Teilstring-Format aus den Bytes 8–9 des Quellknotens für einfachen String-Abgleich. \
*Padding:* Einstellige Stationen sollten links mit einem Null-Byte (`0x00`) aufgefüllt werden (z. B. `0x00 0x33` für Station 3). \
*Parsing-Regeln:* Die Firmware-Implementierung muss flexibel sein. Der Parser muss führende Null-Bytes (`0x00`), führende Leerzeichen (`0x20`) und führende ASCII-Nullen (`0x30`) unterstützen und fehlerfrei dekodieren.
],
[Byte 18], [RSSI-Wert (`R`) \ (`unsigned char`)], [Gespeichert als RSSI + 164 (z. B. `0x3A` = 58 -> 58 - 164 = -106 dBm).],
[Byte 19], [SNR-Wert (`S`) \ (`signed char`)], [Gespeichert als SNR multipliziert mit 4 (z. B. `0x42` = 66 -> 66 / 4 = +16,5 dB).],
[Byte 20], [Status-Byte (`q`) \ (`unsigned char`)], [Kombiniertes Byte für Qualität und Batteriestatus. (Siehe Bit-Mapping unten).]
)
#v(0.5em)
*Bit-Mapping für Byte 20 (`q`):*
#table(
columns: (1.5fr, 1.5fr, 6fr),
align: (center, center, left),
stroke: 0.5pt + luma(150),
fill: (x, y) => if y == 0 { rgb("f5f5f5") } else { none },
..table-header("Bits", "Wert", "Definition"),
[Bits 7–6], [`00`], [Normal (Battery OK / Powered)],
[Bits 7–6], [`01`], [Low (Battery low, intervention recommended)],
[Bits 7–6], [`10`], [Critical (Battery critical, node might shut down soon)],
[Bits 7–6], [`11`], [Reserved],
[Bits 5–0], [0 to 63], [Moving average counter of how many times this station was heard within the last 30 minutes. If >= 3, the link is considered stable. Dropped if 0 within 30 minutes.]
)
== Kontrollpunkt-Nachrichten (Posten / Checkpoints)
Sobald sich ein Teilnehmer an einem Kontrollpunkt mittels seines RFID-Tags oder seiner Kennung registriert, wird er an diesem Knoten erfasst. Dem Läufer wird ein Zeitstempel und seine ID (Startnummer) zugewiesen. Die Zuordnung zwischen Startnummer und RFID-Code ist systemintern hinterlegt. Während des nächsten verfügbaren Zeitschlitzes wird eine Kontrollpunkt-Nachricht mit QoS 2 (garantierte Paketzustellung, Duplikaterkennung und -filterung) an das Ziel übertragen.
Nachrichten werden gleichzeitig an den Zielknoten und optional an den Startknoten 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 konzipiert. Es enthält die Knotennummer, die Startnummer, den Erfassungs-Zeitstempel und den aktuellen Hop-Count.
Pseudo-Byte-Stream:
```text
(ppMzzzzLLR
```
=== Structure (Checkpoint Message)
#table(
columns: (1.5fr, 2fr, 5fr),
align: (center, left, left),
stroke: 0.5pt + luma(150),
fill: (x, y) => if y == 0 { rgb("f0f0f0") } else { none },
..table-header("Byte / Offset", "Feld / Format", "Beschreibung"),
[Byte 0], [`(` (0x28)], [
*Data Type Identifier (DTI):* Spezielle Kennung, die für FoxMash-Kontrollpunkt-Nachrichten vorgesehen ist.
],
[Bytes 1–2], [Eigene SSID (`pp`) \ (`uint16_t`)], [
16-Bit unsigned Integer des sendenden Kontrollpunkt-Knotens.
],
[Byte 3], [Routing & Flags (`M`) \ (`unsigned char`)], [
Kombinierte Ziel-SSID und Protokoll-Steuerungs-Flags. Dient gleichzeitig als 1-Byte-Grenzausrichter (Alignment).
],
[Bytes 4–7], [`zzzz` (Zeitstempel) \ (`int32_t`, Little-Endian)], [
*Zeitstempel:* Vorzeichenbehafteter 32-Bit-Integer, der die Anzahl der Millisekunden seit Beginn des Wettbewerbstages darstellt. Formatiert als Little-Endian (LSB zuerst) und auf eine 4-Byte-Grenze ausgerichtet für direkten, hochperformanten ESP32-Zeigerzugriff.
],
[Bytes 8–9], [Startnummer (`L`) \ (`uint16_t`)], [
Eindeutige Teilnehmeridentifikation / Startnummer (Unterstützt IDs von 0 bis 65.535).
],
[Bytes 10], [Hop-Count / Flags \ (`unsigned char`)], [
Aktueller Netzwerk-Hop-Count und verbleibende Mesh-Routing-Flags.
]
)
#v(0.5em)
*Bit-Mapping für Byte 3 (`M`):*
#table(
columns: (1.5fr, 1.5fr, 6fr),
align: (center, center, left),
stroke: 0.5pt + luma(150),
fill: (x, y) => if y == 0 { rgb("f5f5f5") } else { none },
..table-header("Bits", "Flag / Feld", "Definition"),
[Bits 7–5], [Hop-Count], [Dynamischer Routing-Zähler. `0`, wenn das Ziel direkt mit guter Qualität sichtbar ist. Wird um 1 erhöht, wenn innerhalb von 5 Minuten kein ACK erfolgt. Wird verworfen, wenn der maximale Hop-Wert überschritten wird.],
[Bit 4], [ACK-Flag], [`0` für eine Standard-Datennachricht, `1` für ein Bestätigungspaket (ACK).],
[Bits 3–0], [Ziel-SSID], [Die SSID des Zielknotens (Unterstützt IDs von 0 bis 15).]
)
=== Bestätigungen (Acknowledgments) und Wiederholungen
Die maximale Anzahl der Sendeversuche ist konfigurierbar (Standard: 4). Jedes Kontrollpunkt-Paket muss quittiert werden. Die Bestätigung verwendet exakt dasselbe Format (`vppMLzzzzRR`), jedoch mit *auf `1` gesetztem Bit 4 in Byte 3 (`M`)*. Das Rückrouting erfolgt auf dem identischen Pfad in umgekehrter Richtung basierend auf der im Paket aufgezeichneten Route (Source-Routing). Weiterleitende Knoten erkennen und verwerfen Duplikate durch Abgleich des eindeutigen Zeitstempels des Läufers.
== Protokoll-Optimierungsanalyse
=== Hardware-optimiertes Protokolldesign für ESP32 und LoRa
Beim Entwurf von stromsparenden, hochperformanten Mesh-Netzwerken mit *ESP32*-Mikrocontrollern über *LoRa*-Modulation beeinflusst das Layout der Datenstrukturen sowohl die Übertragungszeit (Airtime) als auch die CPU-Effizienz maßgeblich. Das aktualisierte FoxMash-Protokolllayout strukturiert die Felder so um, dass ein optimales Gleichgewicht zwischen HF-Effizienz und Hardware-Ausführung erreicht wird.
==== Warum dieses Layout perfekt ist
1. *Native Wortausrichtung (Keine Performance-Verluste):* Die in ESP32-Chips verwendeten Tensilica Xtensa- und RISC-V-Architekturen sind von Natur aus *32-Bit Little-Endian*-Prozessoren. Der Zugriff auf einen 4-Byte-Integer von einer Speicheradresse, die kein Vielfaches von 4 is (_unaligned access_), zwingt die Hardware zu speicherintensiven Korrekturen durch internes Byte-Shifting. In ungünstigen Fällen oder spezifischen Compiler-Konfigurationen führt dies zu einem Hardware-Absturz (`LoadStoreAlignmentCause` Core Dump). Durch Platzierung des 32-Bit-Zeitstempels an exakt *Byte 4* (Kontrollpunkt) und *Byte 12* (Beacon) fügt sich der Integer perfekt in die natürlichen Wortgrenzen des Prozessors ein.
2. *Zero-Copy Zeiger-Casting:* Da die Felder nativ ausgerichtet sind, muss die Firmware keine Taktzyklen für `memcpy()` oder Bit-Shifting-Operatoren (`<<`, `|`) verschwenden, um Integer-Werte aus einzelnen Bytes zu rekonstruieren. Die Anwendungsschicht kann direkt aus dem LoRa-Hardware-Empfangspuffer ein Zero-Copy-Pointer-Casting durchführen.
3. *Null Airtime-Overhead:* Traditionelle Alignment-Techniken fügen künstliche Füllbytes (_Padding_, `0x00`) ein, um die Ausrichtung zu erheben, was das Paket vergrößert. In LoRa-Netzwerken erhöht jedes zusätzliche Byte direkt die Time-on-Air (ToA), was zu höherem Energieverbrauch und einer größeren Wahrscheinlichkeit von Paketkollisionen führt. Dieses optimierte Layout erreicht die perfekte Ausrichtung rein durch die *Umordnung bestehender, essenzieller Felder*, wodurch die Frame-Größe minimal bleibt.
4. *Menschlich lesbare Trennzeichen beibehalten:* Das Beibehalten des Standard-Bindestrichs (`"-"`) oder der Tilde (`"~"`) als Trennzeichen zwischen dem internationalen Rufzeichen und der SSID wahrt die Kompatibilität mit bestehenden APRS-/MeshCom-Parsern und hält rohe Hex-Logs beim seriellen Debuggen intuitiv lesbar.
=== Hochperformantes C++ Implementierungsbeispiel
Der folgende Code-Ausschnitt zeigt, wie dieses speicheroptimierte Layout in einer ESP32-Anwendung unter Verwendung von Standard-Arduino- oder ESP-IDF-LoRa-Bibliotheken genutzt werden kann.
#rect(width: 100%, stroke: 0.5pt + luma(150), fill: luma(245), radius: 4pt)[
```cpp
#include <Arduino.h>
// Sicherstellen, dass der Compiler kein strukturelles Padding in den Structs hinzufügt
#pragma pack(push, 1)
struct BeaconPacket {
char dti; // Offset 0 (0x7B '{')
char callsign[6]; // Offset 1 (z. B. "OE3TKT")
char separator; // Offset 7 (z. B. '-')
char ssid[2]; // Offset 8 (z. B. "03")
uint8_t battery; // Offset 10 (0-100%)
uint8_t net_flags; // Offset 11 (Routing-Steuerung / Padding)
int32_t timestamp_ms; // Offset 12 -> PERFEKT AUF 4-BYTE AUSGERICHTET!
uint8_t neighbor_block[]; // Offset 16+ (Dynamische Mesh-Nutzlast)
};
struct CheckpointPacket {
char dti; // Offset 0 (0x28 '(')
uint16_t own_ssid; // Offset 1
uint8_t routing_flags; // Offset 3 (Ziel- / Steuerungs-Flags)
int32_t timestamp_ms; // Offset 4 -> PERFEKT AUF 4-BYTE AUSGERICHTET!
uint16_t runner_id; // Offset 8
uint8_t hop_count; // Offset 10
};
#pragma pack(pop)
// Beispiel-Parserfunktion zur Verarbeitung eingehender roher LoRa-Bytes
void processIncomingLoRa(uint8_t* rxBuffer, size_t packetLength) {
if (packetLength < 1) return;
// Data Type Identifier (DTI) prüfen
char dti = rxBuffer[0];
if (dti == '(') {
// Zero-copy Direktzugriff über Zeiger-Cast
CheckpointPacket* cp = (CheckpointPacket*)rxBuffer;
// Schneller nativer Zugriff direkt aus dem Speicher
int32_t time = cp->timestamp_ms;
uint16_t runner = cp->runner_id;
Serial.printf("Läufer %d erfasst. Lokaler Zeitstempel: %d ms\n", runner, time);
}
else if (dti == '{') {
BeaconPacket* beacon = (BeaconPacket*)rxBuffer;
// Schneller nativer Zugriff direkt aus dem Speicher
int32_t time = beacon->timestamp_ms;
Serial.printf("Beacon empfangen. Netzwerk-Zeitstempel: %d ms\n", time);
}
}
```
]
// ==========================================
// GESTALTETER FOOTER (Name & Typst-Hinweis)
// ==========================================
#v(3em)
#line(length: 100%, stroke: 0.5pt + luma(200))
#v(0.5em)
#align(center)[
#text(size: 9pt, fill: luma(100))[
Dokument gepflegt und kompiliert von *[OE3TKT, Tom]* \
#v(2pt)
#text(size: 8pt, style: "italic")[
Wunderschön gesetzt mit #link("https://typst.app")[Typst] #box(width: 8pt, image(bytes("<svg viewBox='0 0 24 24' width='8' height='8'><path fill='#ff5500' d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/></svg>"), format: "svg"))
]
]
]
-50
Datei anzeigen
@@ -1,50 +0,0 @@
# 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,11 +25,6 @@ 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
@@ -48,4 +43,3 @@ 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
+2 -10
Datei anzeigen
@@ -19,7 +19,6 @@
#include "esp_log.h"
#include "config.hpp"
#include "lora.hpp"
#include "display.hpp"
static const char* TAG = "CLI";
static i2c* i2c_bus = nullptr;
@@ -92,11 +91,9 @@ 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 Tag: " GIT_TAG "\r\n", Config::PROJECT_TITLE, Config::PROJECT_VERSION);
printf("Build Date: " __DATE__ ", Git-Hash: " GIT_HASH "\r\n");
printf("%s System - Running Version: %s\r\n", Config::PROJECT_TITLE, Config::PROJECT_VERSION);
}
else if (strcmp(cmd, "status") == 0) {
printf("--- %s Status ---\r\n", Config::PROJECT_TITLE);
@@ -106,7 +103,7 @@ void process_command(const char* cmd) {
printf("ESP-IDF Version : %s\r\n", esp_get_idf_version());
if (lora_system != nullptr) {
// atomar, thread-safe copy of telemtry data (stack allocation)
// Holt die atomare, thread-sichere Kopie der Telemetriedaten (Stack-Allokation)
LoraStateSnapshot snapshot = lora_system->getSnapshot();
printf("LoRa Hardware : %s\r\n", snapshot.isInitialized ? "INITIALIZED" : "FAILED / UNINITIALIZED");
@@ -240,11 +237,6 @@ 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);
}
+14 -12
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 = "0.1";
constexpr const char* PROJECT_VERSION = "1.0.5";
constexpr const char* PROJECT_AUTHORS = "Tom OE3TKT";
// CLI Mute Configuration
@@ -44,33 +44,35 @@ 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;
constexpr gpio_num_t LORA_DIO1_PIN = GPIO_NUM_33; // Important: PLL lock & Timeout in RX mode
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
// 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, see docs/lora/device-settings.md
// Predefined LoRa Sync Word options for hardware correlation filtering
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_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)
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)
// 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_MSB = 0x00;
constexpr uint8_t LORA_PREAMBLE_LENGTH_LSB = 0x0C; // Semtech Factor Default is 0x08, MeshCom with 0x0C, i.e. 12 symbols
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);
// --- DYNAMIC COMPILE-TIME BIT-SHIFTING EXPRESSIONS ---
+60 -243
Datei anzeigen
@@ -116,137 +116,26 @@ 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 ~
{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, 0x5f, 0x00, 0x00}, // 161 Inverted exclamation mark (¡)
{0x24, 0x2a, 0x7f, 0x2a, 0x12}, // 162 Cent sign (¢)
{0x10, 0x7e, 0x24, 0x44, 0x28}, // 163 Pound sign (£)
{0x6c, 0x28, 0x28, 0x28, 0x6c}, // 164 Currency sign (¤)
{0x08, 0x3e, 0x49, 0x3e, 0x08}, // 165 Yen sign (¥)
{0x00, 0x00, 0x5f, 0x00, 0x00}, // 166 Broken bar (¦) (Alternativ durchgezogen)
{0x22, 0x49, 0x49, 0x49, 0x22}, // 167 Section sign / Paragraph (§)
{0x00, 0x03, 0x00, 0x03, 0x00}, // 168 Diaeresis (¨)
{0x3e, 0x41, 0x5d, 0x49, 0x3e}, // 169 Copyright sign (©)
{0x44, 0x28, 0x3e, 0x44, 0x3e}, // 170 Feminine ordinal indicator (ª)
{0x08, 0x14, 0x22, 0x00, 0x00}, // 171 Left-pointing double-angle quotation mark («)
{0x04, 0x04, 0x04, 0x04, 0x07}, // 172 Not sign (¬)
{0x04, 0x04, 0x04, 0x04, 0x04}, // 173 Soft hyphen
{0x3e, 0x41, 0x5d, 0x55, 0x3e}, // 174 Registered sign (®)
{0x01, 0x01, 0x01, 0x01, 0x01}, // 175 Macron (¯)
{0x00, 0x06, 0x09, 0x06, 0x00}, // 176 Degree sign (°)
{0x08, 0x08, 0x3e, 0x08, 0x08}, // 177 Plus-minus sign (±) (Unterstrich wird dynamisch generiert/Zweit-Byte)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 178 Superscript two (²) -> Oft custom generiert
{0x00, 0x00, 0x00, 0x00, 0x00}, // 179 Superscript three (³) -> Oft custom generiert
{0x00, 0x01, 0x02, 0x04, 0x00}, // 180 Acute accent (´)
{0x3c, 0x0a, 0x0a, 0x0a, 0x02}, // 181 Micro sign (µ)
{0x06, 0x7f, 0x4b, 0x4b, 0x3a}, // 182 Pilcrow sign / Paragraph sign (¶)
{0x00, 0x00, 0x0c, 0x0c, 0x00}, // 183 Middle dot (·)
{0x10, 0x20, 0x00, 0x00, 0x00}, // 184 Cedilla (¸)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 185 Superscript one (¹)
{0x38, 0x44, 0x44, 0x44, 0x38}, // 186 Masculine ordinal indicator (º)
{0x00, 0x00, 0x22, 0x14, 0x08}, // 187 Right-pointing double-angle quotation mark (»)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 188 Vulgar fraction one quarter (¼)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 189 Vulgar fraction one half (½)
{0x00, 0x00, 0x00, 0x00, 0x00}, // 190 Vulgar fraction three quarters (¾)
{0x30, 0x48, 0x4a, 0x40, 0x20}, // 191 Inverted question mark (¿)
// --- UTF-8 0xC3 RANGE (ISO-8859-1 Indizes 192 to 255) ---
{0x7c, 0x13, 0x11, 0x10, 0x7c}, // 192 Capital A with grave (À)
{0x7c, 0x10, 0x11, 0x13, 0x7c}, // 193 Capital A with acute (Á)
{0x7c, 0x12, 0x15, 0x12, 0x7c}, // 194 Capital A with circumflex (Â)
{0x7c, 0x12, 0x15, 0x12, 0x7c}, // 195 Capital A with tilde (Ã)
{0x7c, 0x11, 0x11, 0x11, 0x7c}, // 196 Capital A with umlaut (Ä)
{0x7c, 0x11, 0x11, 0x11, 0x7c}, // 197 Capital A with ring above (Å)
{0x7f, 0x49, 0x7f, 0x41, 0x63}, // 198 Capital AE ligature (Æ)
{0x3e, 0x41, 0x41, 0x22, 0x18}, // 199 Capital C with cedilla (Ç)
{0x7f, 0x4a, 0x4a, 0x42, 0x42}, // 200 Capital E with grave (È)
{0x7f, 0x42, 0x4a, 0x4a, 0x42}, // 201 Capital E with acute (É)
{0x7f, 0x43, 0x4d, 0x43, 0x41}, // 202 Capital E with circumflex (Ê)
{0x7f, 0x41, 0x41, 0x41, 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, 0x42, 0x7d, 0x42, 0x00}, // 206 Capital I with circumflex (Î)
{0x00, 0x41, 0x7d, 0x41, 0x00}, // 207 Capital I with umlaut (Ï)
{0x7f, 0x41, 0x41, 0x22, 0x1c}, // 208 Capital Eth (Ð)
{0x7f, 0x09, 0x05, 0x03, 0x7f}, // 209 Capital N with tilde (Ñ)
{0x3e, 0x43, 0x41, 0x41, 0x3e}, // 210 Capital O with grave (Ò)
{0x3e, 0x41, 0x41, 0x43, 0x3e}, // 211 Capital O with acute (Ó)
{0x3e, 0x42, 0x45, 0x42, 0x3e}, // 212 Capital O with circumflex (Ô)
{0x3e, 0x42, 0x45, 0x42, 0x3e}, // 213 Capital O with tilde (Õ)
{0x3e, 0x41, 0x41, 0x41, 0x3e}, // 214 Capital O with umlaut (Ö)
{0x08, 0x14, 0x22, 0x14, 0x08}, // 215 Multiplication sign (×)
{0x3e, 0x61, 0x51, 0x4b, 0x3e}, // 216 Capital O with stroke (Ø)
{0x3f, 0x42, 0x40, 0x40, 0x3f}, // 217 Capital U with grave (Ù)
{0x3f, 0x40, 0x40, 0x42, 0x3f}, // 218 Capital U with acute (Ú)
{0x3f, 0x42, 0x44, 0x42, 0x3f}, // 219 Capital U with circumflex (Û)
{0x3f, 0x41, 0x40, 0x41, 0x3f}, // 220 Capital U with umlaut (Ü)
{0x03, 0x04, 0x78, 0x04, 0x03}, // 221 Capital Y with acute (Ý)
{0x7f, 0x49, 0x49, 0x36, 0x00}, // 222 Capital Thorn (Þ)
{0x3e, 0x49, 0x49, 0x49, 0x32}, // 223 Small sharp s / Eszett (ß)
{0x20, 0x55, 0x55, 0x54, 0x78}, // 224 Small a with grave (à)
{0x20, 0x54, 0x55, 0x55, 0x78}, // 225 Small a with acute (á)
{0x20, 0x56, 0x55, 0x56, 0x78}, // 226 Small a with circumflex (â)
{0x20, 0x56, 0x55, 0x56, 0x78}, // 227 Small a with tilde (ã)
{0x20, 0x55, 0x54, 0x55, 0x78}, // 228 Small a with umlaut (ä)
{0x20, 0x55, 0x54, 0x55, 0x78}, // 229 Small a with ring above (å)
{0x24, 0x54, 0x78, 0x54, 0x5c}, // 230 Small ae ligature (æ)
{0x38, 0x44, 0x44, 0x28, 0x10}, // 231 Small c with cedilla (ç)
{0x38, 0x55, 0x55, 0x54, 0x18}, // 232 Small e with grave (è)
{0x38, 0x54, 0x55, 0x55, 0x18}, // 233 Small e with acute (é)
{0x38, 0x56, 0x55, 0x56, 0x18}, // 234 Small e with circumflex (ê)
{0x38, 0x55, 0x54, 0x55, 0x18}, // 235 Small e with umlaut (ë)
{0x00, 0x45, 0x7c, 0x40, 0x00}, // 236 Small i with grave (ì)
{0x00, 0x40, 0x7c, 0x45, 0x00}, // 237 Small i with acute (í)
{0x00, 0x46, 0x7c, 0x46, 0x00}, // 238 Small i with circumflex (î)
{0x00, 0x45, 0x7c, 0x45, 0x00}, // 239 Small i with umlaut (ï)
{0x38, 0x44, 0x44, 0x48, 0x7f}, // 240 Small eth (ð)
{0x7c, 0x05, 0x05, 0x04, 0x78}, // 241 Small n with tilde (ñ)
{0x38, 0x45, 0x45, 0x44, 0x38}, // 242 Small o with grave (ò)
{0x38, 0x44, 0x45, 0x45, 0x38}, // 243 Small o with acute (ó)
{0x38, 0x46, 0x45, 0x46, 0x38}, // 244 Small o with circumflex (ô)
{0x38, 0x46, 0x45, 0x46, 0x38}, // 245 Small o with tilde (õ)
{0x38, 0x45, 0x44, 0x45, 0x38}, // 246 Small o with umlaut (ö)
{0x08, 0x08, 0x2a, 0x08, 0x08}, // 247 Division sign (÷)
{0x38, 0x64, 0x54, 0x4c, 0x38}, // 248 Small o with stroke (ø)
{0x3c, 0x41, 0x40, 0x20, 0x7c}, // 249 Small u with grave (ù)
{0x3c, 0x40, 0x40, 0x21, 0x7c}, // 250 Small u with acute (ú)
{0x3c, 0x42, 0x40, 0x22, 0x7c}, // 251 Small u with circumflex (û)
{0x3c, 0x41, 0x40, 0x21, 0x7c}, // 252 Small u with umlaut (ü)
{0x4c, 0x30, 0x30, 0x30, 0x7c}, // 253 Small y with acute (ý)
{0x7f, 0x24, 0x24, 0x18, 0x00}, // 254 Small thorn (þ)
{0x4c, 0x31, 0x30, 0x31, 0x7c} // 255 Small y with umlaut (ÿ)
// 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}
};
/**
@@ -375,16 +264,14 @@ 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 startX Starting column coordinate index on the horizontal axis (0 to 127).
* @param line Selected hardware page index mapping from 0 to 7.
*/
void DisplayController::printMessage(uint8_t line, const char* text, uint8_t startX) {
void DisplayController::printMessage(uint8_t line, const char* text) {
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);
@@ -424,73 +311,56 @@ 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;
// 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
// 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;
// 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);
// 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);
for (size_t i = 0; i < textLength; i++) {
const uint8_t c_primary = static_cast<uint8_t>(incoming.content[i]);
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
// 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]);
// 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
// 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;
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; } // ü
}
// Handle standard printable 7-bit ASCII characters
else if (c_primary >= 32 && c_primary <= 126) {
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)) {
fontIndex = static_cast<size_t>(c_primary - 32);
validCharacter = true;
}
if (validCharacter && totalBytesWritten < 122) {
uint8_t txBuffer[7];
// copy all 5 font columns in a single, hardware-accelerated burst
uint8_t txBuffer[7] = {0};
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);
// 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
}
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
if (m_devHandle != nullptr) {
i2c_master_transmit(m_devHandle, txBuffer, sizeof(txBuffer), 50);
@@ -499,13 +369,14 @@ void DisplayController::run() {
}
}
// 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);
// 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);
// 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);
@@ -514,57 +385,3 @@ 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, " ",0); // clear line
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));
// Display löschen oder Zeilen vorbereiten, falls nötig
for (uint8_t line = 0; line < 8; line++) {
display->printMessage(line, " "); // Zeile leeren
}
int c = 32;
for ( uint8_t line = 0; line < 8 ; line++) {
for ( uint8_t i = 0; i < 22 ; i++ ) {
if (c > 255) break;
char buf[2] = { (char)c, '\0' };
display->appendMessage(line, buf , i);
c++; //next char
}
}
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);
// }
}
+1 -22
Datei anzeigen
@@ -33,27 +33,13 @@ 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, 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);
}
void printMessage(uint8_t line, const char* text);
private:
// Internal structure mapping the safe asynchronous data pipeline
struct DisplayMessage {
uint8_t targetLine;
uint8_t startX;
char content[24];
};
@@ -68,10 +54,3 @@ 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);
+649 -284
Datei anzeigen
Datei-Diff unterdrückt, da er zu groß ist. Diff laden
+157 -85
Datei anzeigen
@@ -10,108 +10,69 @@
#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 <iostream>
#include <string>
#include <cstring>
#include "esp_err.h"
/**
* @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;
spi_device_handle_t m_spiHandle = nullptr;
TaskHandle_t m_taskHandle = nullptr;
SemaphoreHandle_t m_isrSemaphore = nullptr;
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.
LoraStateTable m_state; ///< Internal volatile tracking record data context.
SemaphoreHandle_t m_stateMutex = nullptr; ///< Mutex primitive enforcing mutual exclusion over m_state modifications.
static void taskTrampoline(void* pvParameters);
static void IRAM_ATTR gpioIsrHandler(void* arg);
void run();
/**
* @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);
esp_err_t initializeHardware();
void setModeReceiver();
/**
* @brief Interrupt Service Routine wrapper triggered directly from GPIO state changes.
* @param arg Opaque payload pointer referencing the target thread synchronization primitive.
*/
static void IRAM_ATTR gpioIsrHandler(void* arg);
void applyModemConfig();
/**
* @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();
/**
* @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);
/**
* @brief Evaluates raw register readings into calibrated RSSI and SNR telemetry metrics.
@@ -124,6 +85,117 @@ private:
*/
void calculateTelemetry(uint8_t rawRssi, uint8_t rawSnr, float& outRssi, float& outSnr);
size_t utf8_advance(const std::string& str, size_t start_byte, size_t num_chars);
public:
/**
* @brief Construct a new Lora Controller instance and allocate threading primitives.
*/
LoraController();
/**
* @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();
};
+87 -21
Datei anzeigen
@@ -8,22 +8,72 @@
* 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 {
// 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)
/** @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;
// The three 1-byte variables at the bottom align perfectly into a 32-bit slot, leaving minimal trailing padding.
};
@@ -33,15 +83,31 @@ struct LoraStateTable {
* @note Optimized using "largest-to-smallest" data type ordering to minimize memory padding.
*/
struct LoraStateSnapshot {
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 Soft-limit threshold capping the maximum number of history records stored before auto-purge. (4 or 8 Bytes) */
size_t currentHistoryCount;
// Elements are strictly organized by size alignment requirements to completely prevent intermittent alignment gaps.
/** @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.
};
+5 -40
Datei anzeigen
@@ -8,15 +8,11 @@
* 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"
@@ -50,32 +46,6 @@ 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" {
/**
@@ -108,9 +78,6 @@ 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();
@@ -121,7 +88,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 strict to the Core 1 (Priority 6)
// start the radio task straight to the Core 1 (Priority 6)
lora_module->startTask(6);
ESP_LOGI(TAG, "LoRa subsystem successfully activated on Core 1.");
} else {
@@ -131,18 +98,16 @@ extern "C" {
// Trigger synchronous welcome splash screen frame
if (display != nullptr) {
char msg[32];
snprintf(msg, sizeof(msg), "FoxMesh " GIT_TAG);
display->printMessage(0, msg);
// display->appendMessage(0, GIT_TAG, 12*6);
display->printMessage(1, "----------------------");
snprintf(msg, sizeof(msg), "FoxMesh %s OE3TKT", Config::PROJECT_VERSION);
display->printMessage(1, msg);
}
// 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));
}