mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-24 11:40:27 +00:00
Compare commits
143 Commits
v2.5.19.f9
...
NextHopRou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5757b88c48 | ||
|
|
7eb77276cd | ||
|
|
7648391f91 | ||
|
|
4407d9e040 | ||
|
|
27fea5fc07 | ||
|
|
2f6cd02111 | ||
|
|
8c9947b05c | ||
|
|
50b7d6a0f7 | ||
|
|
c83ffd4911 | ||
|
|
9b46cb4ef0 | ||
|
|
01935ea35e | ||
|
|
495f69cf90 | ||
|
|
eb650a6adb | ||
|
|
7fdd262d55 | ||
|
|
d1fa27d353 | ||
|
|
8427072d79 | ||
|
|
4e2b47cc67 | ||
|
|
da1d78c882 | ||
|
|
7c4bf38647 | ||
|
|
96262b106c | ||
|
|
39e45d90e1 | ||
|
|
d70a9392af | ||
|
|
cea072288a | ||
|
|
4e8c4f0d55 | ||
|
|
4a6a0efcfd | ||
|
|
cb0519dd9c | ||
|
|
9db51a72a4 | ||
|
|
64def246ee | ||
|
|
1c8eb7ece3 | ||
|
|
447533aae5 | ||
|
|
1b457bcfbb | ||
|
|
ed07cc067a | ||
|
|
a3a295488c | ||
|
|
5c17afb2ac | ||
|
|
8cacdb65d6 | ||
|
|
3a34f8beaf | ||
|
|
d740934278 | ||
|
|
b370717dcd | ||
|
|
d9534cfc9d | ||
|
|
b91b66bc7a | ||
|
|
4c0e0b8471 | ||
|
|
b5cad2b65e | ||
|
|
cd8592ef4a | ||
|
|
78da8f6fc4 | ||
|
|
6a12760c3d | ||
|
|
30a31a3a13 | ||
|
|
2d42e1b2bc | ||
|
|
4747e73f37 | ||
|
|
10d553087c | ||
|
|
7649e70585 | ||
|
|
a14346bc4f | ||
|
|
fd56995764 | ||
|
|
4c97351187 | ||
|
|
3298df953a | ||
|
|
d1f7739bbe | ||
|
|
0d860882a8 | ||
|
|
3b40fe9805 | ||
|
|
8e8b22edb0 | ||
|
|
01892cbd1e | ||
|
|
7fb22cf678 | ||
|
|
fdc87d492c | ||
|
|
0fdbf70452 | ||
|
|
71591fb06a | ||
|
|
9041af365d | ||
|
|
f87c370123 | ||
|
|
c4fcbad372 | ||
|
|
0f981153eb | ||
|
|
c1beb44678 | ||
|
|
973b453d43 | ||
|
|
950341d1f9 | ||
|
|
b353bcc04a | ||
|
|
c4051c1a7b | ||
|
|
2262d77be4 | ||
|
|
9566d6ffd4 | ||
|
|
e466bf2475 | ||
|
|
b0fe5ef8ba | ||
|
|
f132158c3e | ||
|
|
8179e61fdc | ||
|
|
a085614aaa | ||
|
|
7acd72ede1 | ||
|
|
7ba593432e | ||
|
|
a48df91737 | ||
|
|
262f1d25a2 | ||
|
|
dc4279e7fd | ||
|
|
766bd614a4 | ||
|
|
a3d9582b35 | ||
|
|
d5a8587deb | ||
|
|
5154e29b07 | ||
|
|
919660e005 | ||
|
|
c7d04d7064 | ||
|
|
4915a07c2a | ||
|
|
98b4a29ef9 | ||
|
|
41d0a39ba6 | ||
|
|
0952c861ae | ||
|
|
017bff8be7 | ||
|
|
e593d54743 | ||
|
|
6a29793f23 | ||
|
|
47116f65cd | ||
|
|
98719e4c62 | ||
|
|
bfc6a1940d | ||
|
|
360637c25d | ||
|
|
3ea2918f7f | ||
|
|
b229abc2b4 | ||
|
|
dbe520c3ab | ||
|
|
42d17b3322 | ||
|
|
3725319b4b | ||
|
|
17495e7dbf | ||
|
|
93bcee3aab | ||
|
|
71a90b3b78 | ||
|
|
be73b099a7 | ||
|
|
f37abe8f0f | ||
|
|
78bf1e192b | ||
|
|
70aa28c53c | ||
|
|
fbefce7e10 | ||
|
|
69f88b9fdc | ||
|
|
24ff7c0bfb | ||
|
|
bb64b1480b | ||
|
|
790801f8e7 | ||
|
|
28944adf20 | ||
|
|
aab973e81b | ||
|
|
e4c98185d2 | ||
|
|
01344835af | ||
|
|
9de8d5ae66 | ||
|
|
ba4220fe50 | ||
|
|
aae4443e25 | ||
|
|
6fe42ed4c5 | ||
|
|
2e303a33be | ||
|
|
913268b132 | ||
|
|
e91dcb4ec3 | ||
|
|
b8e01b4044 | ||
|
|
d4ef0cdba5 | ||
|
|
b456e34c6e | ||
|
|
25ec0514da | ||
|
|
3ba9ecbbfe | ||
|
|
ef2c6eed05 | ||
|
|
81f57b65e1 | ||
|
|
42757d847f | ||
|
|
c7293cf6f1 | ||
|
|
27a492adf8 | ||
|
|
9b1dd75549 | ||
|
|
44dc270c8a | ||
|
|
3776064b80 | ||
|
|
0d6729b9eb |
52
.clusterfuzzlite/Dockerfile
Normal file
52
.clusterfuzzlite/Dockerfile
Normal file
@@ -0,0 +1,52 @@
|
||||
# This container is used to build Meshtastic with the libraries required by the fuzzer.
|
||||
# ClusterFuzzLite starts the container, runs the build.sh script, and then exits.
|
||||
|
||||
# As this is not a long running service, health-checks are not required. ClusterFuzzLite
|
||||
# also only works if the user remains unchanged from the base image (it expects to run
|
||||
# as root).
|
||||
# trunk-ignore-all(trivy/DS026): No healthcheck is needed for this builder container
|
||||
# trunk-ignore-all(checkov/CKV_DOCKER_2): No healthcheck is needed for this builder container
|
||||
# trunk-ignore-all(checkov/CKV_DOCKER_3): We must run as root for this container
|
||||
# trunk-ignore-all(trivy/DS002): We must run as root for this container
|
||||
# trunk-ignore-all(checkov/CKV_DOCKER_8): We must run as root for this container
|
||||
# trunk-ignore-all(hadolint/DL3002): We must run as root for this container
|
||||
|
||||
FROM gcr.io/oss-fuzz-base/base-builder:v1
|
||||
|
||||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
|
||||
# trunk-ignore(hadolint/DL3008): apt packages are not pinned.
|
||||
# trunk-ignore(terrascan/AC_DOCKER_0002): apt packages are not pinned.
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \
|
||||
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \
|
||||
libusb-1.0-0-dev libssl-dev pkg-config && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||
pip install --no-cache-dir -U \
|
||||
platformio==6.1.16 \
|
||||
grpcio-tools==1.68.1 \
|
||||
meshtastic==2.5.9
|
||||
|
||||
# Ugly hack to avoid clang detecting a conflict between the math "log" function and the "log" function in framework-portduino/cores/portduino/logging.h
|
||||
RUN sed -i -e 's/__MATHCALL_VEC (log,, (_Mdouble_ __x));//' /usr/include/x86_64-linux-gnu/bits/mathcalls.h
|
||||
|
||||
# A few dependencies are too old on the base-builder image. More recent versions are built from source.
|
||||
WORKDIR $SRC
|
||||
RUN git config --global advice.detachedHead false && \
|
||||
git clone --depth 1 --branch 0.8.0 https://github.com/jbeder/yaml-cpp.git && \
|
||||
git clone --depth 1 --branch v2.3.3 https://github.com/babelouest/orcania.git && \
|
||||
git clone --depth 1 --branch v1.4.20 https://github.com/babelouest/yder.git && \
|
||||
git clone --depth 1 --branch v2.7.15 https://github.com/babelouest/ulfius.git
|
||||
|
||||
COPY ./.clusterfuzzlite/build.sh $SRC/
|
||||
|
||||
WORKDIR $SRC/firmware
|
||||
COPY . $SRC/firmware/
|
||||
|
||||
# https://docs.platformio.org/en/latest/envvars.html
|
||||
ENV PLATFORMIO_CORE_DIR=$SRC/pio/core \
|
||||
PLATFORMIO_LIBDEPS_DIR=$SRC/pio/libdeps \
|
||||
PLATFORMIO_PACKAGES_DIR=$SRC/pio/packages \
|
||||
PLATFORMIO_SETTING_ENABLE_CACHE=No \
|
||||
PIO_ENV=buildroot
|
||||
RUN platformio pkg install --environment $PIO_ENV
|
||||
59
.clusterfuzzlite/README.md
Normal file
59
.clusterfuzzlite/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# ClusterFuzzLite for Meshtastic
|
||||
|
||||
This directory contains the fuzzer implementation for Meshtastic using the ClusterFuzzLite framework.
|
||||
See the [ClusterFuzzLite documentation](https://google.github.io/clusterfuzzlite/) for more details.
|
||||
|
||||
## Running locally
|
||||
|
||||
ClusterFuzzLite uses the OSS-Fuzz toolchain. To build the fuzzer manually, first grab a copy of OSS-Fuzz.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/google/oss-fuzz.git
|
||||
cd oss-fuzz
|
||||
```
|
||||
|
||||
To build the fuzzer, run:
|
||||
|
||||
```shell
|
||||
python3 infra/helper.py build_image --external $PATH_TO_MESHTASTIC_FIRMWARE_DIRECTORY
|
||||
python3 infra/helper.py build_fuzzers --external $PATH_TO_MESHTASTIC_FIRMWARE_DIRECTORY --sanitizer address
|
||||
```
|
||||
|
||||
To run the fuzzer, run:
|
||||
|
||||
```shell
|
||||
python3 infra/helper.py run_fuzzer --external --corpus-dir=<path-to-temp-corpus-dir> $PATH_TO_MESHTASTIC_FIRMWARE_DIRECTORY router_fuzzer
|
||||
```
|
||||
|
||||
More background on these commands can be found in the
|
||||
[ClusterFuzzLite documentation](https://google.github.io/clusterfuzzlite/build-integration/#testing-locally).
|
||||
|
||||
## router_fuzzer.cpp
|
||||
|
||||
This fuzzer submits MeshPacket protos to the `Router::enqueueReceivedMessage` method. It takes the binary
|
||||
data from the fuzzer and decodes that data to a MeshPacket using nanopb. A few fields in
|
||||
the MeshPacket are modified by the fuzzer.
|
||||
|
||||
- If the `to` field is 0, it will be replaced with the NodeID of the running node.
|
||||
- If the `from` field is 0, it will be replaced with the NodeID of the running node.
|
||||
- If the `id` field is 0, it will be replaced with an incrementing counter value.
|
||||
- If the `pki_encrypted` field is true, the `public_key` field will be populated with the first admin key.
|
||||
|
||||
The `router_fuzzer_seed_corpus.py` file contains a list of MeshPackets. It is run from inside build.sh and
|
||||
writes the binary MeshPacket protos to files. These files are use used by the fuzzer as its initial seed data,
|
||||
helping the fuzzer to start off with a few known inputs.
|
||||
|
||||
### Interpreting a fuzzer crash
|
||||
|
||||
If the fuzzer crashes, it'll write the input bytes used for the test case to a file and notify about the
|
||||
location of that file. The contents of the file are a binary serialized MeshPacket protobuf. The following
|
||||
snippet of Python code can be used to parse the file into a human readable form.
|
||||
|
||||
```python
|
||||
from meshtastic.protobuf import mesh_pb2
|
||||
|
||||
mesh_pb2.MeshPacket.FromString(open("crash-XXXX-file", "rb").read())
|
||||
```
|
||||
|
||||
Consider adding any such crash results to the `router_fuzzer_seed_corpus.py` file to ensure there a isn't
|
||||
a future regression for that crash test case.
|
||||
71
.clusterfuzzlite/build.sh
Normal file
71
.clusterfuzzlite/build.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Build Meshtastic and a few needed dependencies using clang++
|
||||
# and the OSS-Fuzz required build flags.
|
||||
|
||||
env
|
||||
|
||||
cd "$SRC"
|
||||
NPROC=$(nproc || echo 1)
|
||||
|
||||
LDFLAGS=-lpthread cmake -S "$SRC/yaml-cpp" -B "$WORK/yaml-cpp/$SANITIZER" \
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
cmake --build "$WORK/yaml-cpp/$SANITIZER" -j "$NPROC"
|
||||
cmake --install "$WORK/yaml-cpp/$SANITIZER" --prefix /usr
|
||||
|
||||
cmake -S "$SRC/orcania" -B "$WORK/orcania/$SANITIZER" \
|
||||
-DBUILD_STATIC=ON
|
||||
cmake --build "$WORK/orcania/$SANITIZER" -j "$NPROC"
|
||||
cmake --install "$WORK/orcania/$SANITIZER" --prefix /usr
|
||||
|
||||
cmake -S "$SRC/yder" -B "$WORK/yder/$SANITIZER" \
|
||||
-DBUILD_STATIC=ON -DWITH_JOURNALD=OFF
|
||||
cmake --build "$WORK/yder/$SANITIZER" -j "$NPROC"
|
||||
cmake --install "$WORK/yder/$SANITIZER" --prefix /usr
|
||||
|
||||
cmake -S "$SRC/ulfius" -B "$WORK/ulfius/$SANITIZER" \
|
||||
-DBUILD_STATIC=ON -DWITH_JANSSON=OFF -DWITH_CURL=OFF -DWITH_WEBSOCKET=OFF
|
||||
cmake --build "$WORK/ulfius/$SANITIZER" -j "$NPROC"
|
||||
cmake --install "$WORK/ulfius/$SANITIZER" --prefix /usr
|
||||
|
||||
cd "$SRC/firmware"
|
||||
|
||||
PLATFORMIO_EXTRA_SCRIPTS=$(echo -e "pre:.clusterfuzzlite/platformio-clusterfuzzlite-pre.py\npost:.clusterfuzzlite/platformio-clusterfuzzlite-post.py")
|
||||
STATIC_LIBS=$(pkg-config --libs --static libulfius openssl libgpiod yaml-cpp bluez --silence-errors)
|
||||
export PLATFORMIO_EXTRA_SCRIPTS
|
||||
export STATIC_LIBS
|
||||
export PLATFORMIO_WORKSPACE_DIR="$WORK/pio/$SANITIZER"
|
||||
export TARGET_CC=$CC
|
||||
export TARGET_CXX=$CXX
|
||||
export TARGET_LD=$CXX
|
||||
export TARGET_AR=llvm-ar
|
||||
export TARGET_AS=llvm-as
|
||||
export TARGET_OBJCOPY=llvm-objcopy
|
||||
export TARGET_RANLIB=llvm-ranlib
|
||||
|
||||
mkdir -p "$OUT/lib"
|
||||
|
||||
cp .clusterfuzzlite/*_fuzzer.options "$OUT/"
|
||||
|
||||
for f in .clusterfuzzlite/*_fuzzer.cpp; do
|
||||
fuzzer=$(basename "$f" .cpp)
|
||||
cp -f "$f" src/fuzzer.cpp
|
||||
pio run -vvv --environment "$PIO_ENV"
|
||||
program="$PLATFORMIO_WORKSPACE_DIR/build/$PIO_ENV/program"
|
||||
cp "$program" "$OUT/$fuzzer"
|
||||
|
||||
# Copy shared libraries used by the fuzzer.
|
||||
read -d '' -ra shared_libs < <(ldd "$program" | sed -n 's/[^=]\+=> \([^ ]\+\).*/\1/p') || true
|
||||
cp -f "${shared_libs[@]}" "$OUT/lib/"
|
||||
|
||||
# Build the initial fuzzer seed corpus.
|
||||
corpus_name="${fuzzer}_seed_corpus"
|
||||
corpus_generator="$PWD/.clusterfuzzlite/${corpus_name}.py"
|
||||
if [[ -f $corpus_generator ]]; then
|
||||
mkdir "$corpus_name"
|
||||
pushd "$corpus_name"
|
||||
python3 "$corpus_generator"
|
||||
popd
|
||||
zip -D "$OUT/${corpus_name}.zip" "$corpus_name"/*
|
||||
fi
|
||||
done
|
||||
35
.clusterfuzzlite/platformio-clusterfuzzlite-post.py
Normal file
35
.clusterfuzzlite/platformio-clusterfuzzlite-post.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""PlatformIO build script (post: runs after other Meshtastic scripts)."""
|
||||
|
||||
import os
|
||||
import shlex
|
||||
|
||||
from SCons.Script import DefaultEnvironment
|
||||
|
||||
env = DefaultEnvironment()
|
||||
|
||||
# Remove any static libraries from the LIBS environment. Static libraries are
|
||||
# handled in platformio-clusterfuzzlite-pre.py.
|
||||
static_libs = set(lib[2:] for lib in shlex.split(os.getenv("STATIC_LIBS")))
|
||||
env.Replace(
|
||||
LIBS=[
|
||||
lib for lib in env["LIBS"] if not (isinstance(lib, str) and lib in static_libs)
|
||||
],
|
||||
)
|
||||
|
||||
# FrameworkArduino/portduino/main.cpp contains the "main" function the binary.
|
||||
# The fuzzing framework also provides a "main" function and needs to be run
|
||||
# before Meshtastic is started. We rename the "main" function for Meshtastic to
|
||||
# "portduino_main" here so that it can be called inside the fuzzer.
|
||||
env.AddPostAction(
|
||||
"$BUILD_DIR/FrameworkArduino/portduino/main.cpp.o",
|
||||
env.VerboseAction(
|
||||
" ".join(
|
||||
[
|
||||
"$OBJCOPY",
|
||||
"--redefine-sym=main=portduino_main",
|
||||
"$BUILD_DIR/FrameworkArduino/portduino/main.cpp.o",
|
||||
]
|
||||
),
|
||||
"Renaming main symbol to portduino_main",
|
||||
),
|
||||
)
|
||||
52
.clusterfuzzlite/platformio-clusterfuzzlite-pre.py
Normal file
52
.clusterfuzzlite/platformio-clusterfuzzlite-pre.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""PlatformIO build script (pre: runs before other Meshtastic scripts).
|
||||
|
||||
ClusterFuzzLite executes in a different container from the build. During the build,
|
||||
attempt to link statically to as many dependencies as possible. For dependencies that
|
||||
do not have static libraries, the shared library files are copied to the output
|
||||
directory by the build.sh script.
|
||||
"""
|
||||
|
||||
import glob
|
||||
import os
|
||||
import shlex
|
||||
|
||||
from SCons.Script import DefaultEnvironment, Literal
|
||||
|
||||
env = DefaultEnvironment()
|
||||
|
||||
cxxflags = shlex.split(os.getenv("CXXFLAGS"))
|
||||
sanitizer_flags = shlex.split(os.getenv("SANITIZER_FLAGS"))
|
||||
lib_fuzzing_engine = shlex.split(os.getenv("LIB_FUZZING_ENGINE"))
|
||||
statics = glob.glob("/usr/lib/lib*.a") + glob.glob("/usr/lib/*/lib*.a")
|
||||
no_static = set(("-ldl",))
|
||||
|
||||
|
||||
def replaceStatic(lib):
|
||||
"""Replace -l<libname> with the static .a file for the library."""
|
||||
if not lib.startswith("-l") or lib in no_static:
|
||||
return lib
|
||||
static_name = f"/lib{lib[2:]}.a"
|
||||
static = [s for s in statics if s.endswith(static_name)]
|
||||
if len(static) == 1:
|
||||
return static[0]
|
||||
return lib
|
||||
|
||||
|
||||
# Setup the environment for building with Clang and the OSS-Fuzz required build flags.
|
||||
env.Append(
|
||||
CFLAGS=os.getenv("CFLAGS"),
|
||||
CXXFLAGS=cxxflags,
|
||||
LIBSOURCE_DIRS=["/usr/lib/x86_64-linux-gnu"],
|
||||
LINKFLAGS=cxxflags
|
||||
+ sanitizer_flags
|
||||
+ lib_fuzzing_engine
|
||||
+ ["-stdlib=libc++", "-std=c++17"],
|
||||
_LIBFLAGS=[replaceStatic(s) for s in shlex.split(os.getenv("STATIC_LIBS"))]
|
||||
+ [
|
||||
"/usr/lib/x86_64-linux-gnu/libunistring.a", # Needs to be at the end.
|
||||
# Find the shared libraries in a subdirectory named lib
|
||||
# within the same directory as the binary.
|
||||
Literal("-Wl,-rpath,$ORIGIN/lib"),
|
||||
"-Wl,-z,origin",
|
||||
],
|
||||
)
|
||||
1
.clusterfuzzlite/project.yaml
Normal file
1
.clusterfuzzlite/project.yaml
Normal file
@@ -0,0 +1 @@
|
||||
language: c++
|
||||
206
.clusterfuzzlite/router_fuzzer.cpp
Normal file
206
.clusterfuzzlite/router_fuzzer.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
// Fuzzer implementation that sends MeshPackets to Router::enqueueReceivedMessage.
|
||||
#include <condition_variable>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <pb_decode.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "PortduinoGPIO.h"
|
||||
#include "PortduinoGlue.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "mesh/MeshTypes.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
#include "mesh/Router.h"
|
||||
#include "mesh/TypeConversions.h"
|
||||
#include "mesh/mesh-pb-constants.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr uint32_t nodeId = 0x12345678;
|
||||
// Set to true when lateInitVariant finishes. Used to ensure lateInitVariant was called during startup.
|
||||
bool hasBeenConfigured = false;
|
||||
|
||||
// These are used to block the Arduino loop() function until a fuzzer input is ready. This is
|
||||
// an optimization that prevents a sleep from happening before the loop is run. The Arduino loop
|
||||
// function calls loopCanSleep() before sleeping. loopCanSleep is implemented here in the fuzzer
|
||||
// and blocks until runLoopOnce() is called to signal for the loop to run.
|
||||
bool fuzzerRunning = false; // Set to true once LLVMFuzzerTestOneInput has started running.
|
||||
bool loopCanRun = true; // The main Arduino loop() can run when this is true.
|
||||
bool loopIsWaiting = false; // The main Arduino loop() is waiting to be signaled to run.
|
||||
bool loopShouldExit = false; // Indicates that the main Arduino thread should exit by throwing ShouldExitException.
|
||||
std::mutex loopLock;
|
||||
std::condition_variable loopCV;
|
||||
std::thread meshtasticThread;
|
||||
|
||||
// This exception is thrown when the portuino main thread should exit.
|
||||
class ShouldExitException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
// Start the loop for one test case and wait till the loop has completed. This ensures fuzz
|
||||
// test cases do not overlap with one another. This helps the fuzzer attribute a crash to the
|
||||
// single, currently running, test case.
|
||||
void runLoopOnce()
|
||||
{
|
||||
realHardware = true; // Avoids delay(100) within portduino/main.cpp
|
||||
std::unique_lock<std::mutex> lck(loopLock);
|
||||
fuzzerRunning = true;
|
||||
loopCanRun = true;
|
||||
loopCV.notify_one();
|
||||
loopCV.wait(lck, [] { return !loopCanRun && loopIsWaiting; });
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Called in the main Arduino loop function to determine if the loop can delay/sleep before running again.
|
||||
// We use this as a way to block the loop from sleeping and to start the loop function immediately when a
|
||||
// fuzzer input is ready.
|
||||
bool loopCanSleep()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(loopLock);
|
||||
loopIsWaiting = true;
|
||||
loopCV.notify_one();
|
||||
loopCV.wait(lck, [] { return loopCanRun || loopShouldExit; });
|
||||
loopIsWaiting = false;
|
||||
if (loopShouldExit)
|
||||
throw ShouldExitException("exit");
|
||||
if (!fuzzerRunning)
|
||||
return true; // The loop can sleep before the fuzzer starts.
|
||||
loopCanRun = false; // Only run the loop once before waiting again.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Called just prior to starting Meshtastic. Allows for setting config values before startup.
|
||||
void lateInitVariant()
|
||||
{
|
||||
settingsMap[logoutputlevel] = level_error;
|
||||
channelFile.channels[0] = meshtastic_Channel{
|
||||
.has_settings = true,
|
||||
.settings =
|
||||
meshtastic_ChannelSettings{
|
||||
.psk = {.size = 1, .bytes = {/*defaultpskIndex=*/1}},
|
||||
.name = "LongFast",
|
||||
.uplink_enabled = true,
|
||||
.has_module_settings = true,
|
||||
.module_settings = {.position_precision = 16},
|
||||
},
|
||||
.role = meshtastic_Channel_Role_PRIMARY,
|
||||
};
|
||||
config.security.admin_key[0] = {
|
||||
.size = 32,
|
||||
.bytes = {0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a,
|
||||
0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c},
|
||||
};
|
||||
config.security.admin_key_count = 1;
|
||||
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US;
|
||||
moduleConfig.has_mqtt = true;
|
||||
moduleConfig.mqtt = meshtastic_ModuleConfig_MQTTConfig{
|
||||
.enabled = true,
|
||||
.proxy_to_client_enabled = true,
|
||||
};
|
||||
moduleConfig.has_store_forward = true;
|
||||
moduleConfig.store_forward = meshtastic_ModuleConfig_StoreForwardConfig{
|
||||
.enabled = true,
|
||||
.history_return_max = 4,
|
||||
.history_return_window = 600,
|
||||
.is_server = true,
|
||||
};
|
||||
meshtastic_Position fixedGPS = meshtastic_Position{
|
||||
.has_latitude_i = true,
|
||||
.latitude_i = static_cast<uint32_t>(1 * 1e7),
|
||||
.has_longitude_i = true,
|
||||
.longitude_i = static_cast<uint32_t>(3 * 1e7),
|
||||
.has_altitude = true,
|
||||
.altitude = 64,
|
||||
.location_source = meshtastic_Position_LocSource_LOC_MANUAL,
|
||||
};
|
||||
nodeDB->setLocalPosition(fixedGPS);
|
||||
config.has_position = true;
|
||||
config.position.fixed_position = true;
|
||||
meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
info->has_position = true;
|
||||
info->position = TypeConversions::ConvertToPositionLite(fixedGPS);
|
||||
hasBeenConfigured = true;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
int portduino_main(int argc, char **argv); // Renamed "main" function from Meshtastic binary.
|
||||
|
||||
// Start Meshtastic in a thread and wait till it has reached the ON state.
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
||||
{
|
||||
settingsMap[maxtophone] = 5;
|
||||
|
||||
meshtasticThread = std::thread([program = *argv[0]]() {
|
||||
char nodeIdStr[12];
|
||||
strcpy(nodeIdStr, std::to_string(nodeId).c_str());
|
||||
int argc = 7;
|
||||
char *argv[] = {program, "-d", "/tmp/meshtastic", "-h", nodeIdStr, "-p", "0", nullptr};
|
||||
try {
|
||||
portduino_main(argc, argv);
|
||||
} catch (const ShouldExitException &) {
|
||||
}
|
||||
});
|
||||
std::atexit([] {
|
||||
{
|
||||
const std::lock_guard<std::mutex> lck(loopLock);
|
||||
loopShouldExit = true;
|
||||
loopCV.notify_one();
|
||||
}
|
||||
meshtasticThread.join();
|
||||
});
|
||||
|
||||
// Wait for startup.
|
||||
for (int i = 1; i < 20; ++i) {
|
||||
if (powerFSM.getState() == &stateON) {
|
||||
assert(hasBeenConfigured);
|
||||
assert(router);
|
||||
assert(nodeDB);
|
||||
return 0;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This is the main entrypoint for the fuzzer (the fuzz target). The fuzzer will provide an array of bytes to be
|
||||
// interpreted by this method. To keep things simple, the bytes are interpreted as a binary serialized MeshPacket
|
||||
// proto. Any crashes discovered by the fuzzer will be written to a file. Unserialize that file to print the MeshPacket
|
||||
// that caused the failure.
|
||||
//
|
||||
// This guide provides best practices for writing a fuzzer target.
|
||||
// https://github.com/google/fuzzing/blob/master/docs/good-fuzz-target.md
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t length)
|
||||
{
|
||||
meshtastic_MeshPacket p = meshtastic_MeshPacket_init_default;
|
||||
pb_istream_t stream = pb_istream_from_buffer(data, length);
|
||||
// Ignore any inputs that fail to decode or have fields set that are not transmitted over LoRa.
|
||||
if (!pb_decode(&stream, &meshtastic_MeshPacket_msg, &p) || p.rx_time || p.rx_snr || p.priority || p.rx_rssi || p.delayed ||
|
||||
p.public_key.size || p.next_hop || p.relay_node || p.tx_after)
|
||||
return -1; // Reject: The input will not be added to the corpus.
|
||||
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
meshtastic_Data d;
|
||||
stream = pb_istream_from_buffer(p.decoded.payload.bytes, p.decoded.payload.size);
|
||||
if (!pb_decode(&stream, &meshtastic_Data_msg, &d))
|
||||
return -1; // Reject: The input will not be added to the corpus.
|
||||
}
|
||||
|
||||
// Provide default values for a few fields so the fuzzer doesn't need to guess them.
|
||||
if (p.from == 0)
|
||||
p.from = nodeDB->getNodeNum();
|
||||
if (p.to == 0)
|
||||
p.to = nodeDB->getNodeNum();
|
||||
static uint32_t packetId = 0;
|
||||
if (p.id == 0)
|
||||
p.id == ++packetId;
|
||||
if (p.pki_encrypted && config.security.admin_key_count)
|
||||
memcpy(&p.public_key, &config.security.admin_key[0], sizeof(p.public_key));
|
||||
|
||||
router->enqueueReceivedMessage(packetPool.allocCopy(p));
|
||||
runLoopOnce();
|
||||
return 0; // Accept: The input may be added to the corpus.
|
||||
}
|
||||
}
|
||||
2
.clusterfuzzlite/router_fuzzer.options
Normal file
2
.clusterfuzzlite/router_fuzzer.options
Normal file
@@ -0,0 +1,2 @@
|
||||
[libfuzzer]
|
||||
max_len=256
|
||||
168
.clusterfuzzlite/router_fuzzer_seed_corpus.py
Normal file
168
.clusterfuzzlite/router_fuzzer_seed_corpus.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""Generate an initial set of MeshPackets.
|
||||
|
||||
The fuzzer uses these MeshPackets as an initial seed of test candidates.
|
||||
|
||||
It's also good to add any previously discovered crash test cases to this list
|
||||
to avoid future regressions.
|
||||
|
||||
If left unset, the following values will be automatically set by the fuzzer.
|
||||
- to: automatically set to the running node's NodeID
|
||||
- from: automatically set to the running node's NodeID
|
||||
- id: automatically set to the value of an incrementing counter
|
||||
|
||||
Additionally, if `pki_encrypted` is populated in the packet, the first admin key
|
||||
will be copied into the `public_key` field.
|
||||
"""
|
||||
|
||||
import base64
|
||||
|
||||
from meshtastic import BROADCAST_NUM
|
||||
from meshtastic.protobuf import (
|
||||
admin_pb2,
|
||||
atak_pb2,
|
||||
mesh_pb2,
|
||||
portnums_pb2,
|
||||
telemetry_pb2,
|
||||
)
|
||||
|
||||
|
||||
def From(node: int = 9):
|
||||
"""Return a dict suitable for **kwargs for populating the 'from' field.
|
||||
|
||||
'from' is a reserved keyword in Python. It can't be used directly as an
|
||||
argument to the MeshPacket constructor. Rather **From() can be used as
|
||||
the final argument to provide the from node as a **kwarg.
|
||||
|
||||
Defaults to 9 if no value is provided.
|
||||
"""
|
||||
return {"from": node}
|
||||
|
||||
|
||||
packets = (
|
||||
(
|
||||
"position",
|
||||
mesh_pb2.MeshPacket(
|
||||
decoded=mesh_pb2.Data(
|
||||
portnum=portnums_pb2.PortNum.POSITION_APP,
|
||||
payload=mesh_pb2.Position(
|
||||
latitude_i=int(1 * 1e7),
|
||||
longitude_i=int(2 * 1e7),
|
||||
altitude=5,
|
||||
precision_bits=32,
|
||||
).SerializeToString(),
|
||||
),
|
||||
to=BROADCAST_NUM,
|
||||
**From(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"telemetry",
|
||||
mesh_pb2.MeshPacket(
|
||||
decoded=mesh_pb2.Data(
|
||||
portnum=portnums_pb2.PortNum.TELEMETRY_APP,
|
||||
payload=telemetry_pb2.Telemetry(
|
||||
time=1736192207,
|
||||
device_metrics=telemetry_pb2.DeviceMetrics(
|
||||
battery_level=101,
|
||||
channel_utilization=8,
|
||||
air_util_tx=2,
|
||||
uptime_seconds=42,
|
||||
),
|
||||
).SerializeToString(),
|
||||
),
|
||||
to=BROADCAST_NUM,
|
||||
**From(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"text",
|
||||
mesh_pb2.MeshPacket(
|
||||
decoded=mesh_pb2.Data(
|
||||
portnum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
|
||||
payload=b"Hello world",
|
||||
),
|
||||
to=BROADCAST_NUM,
|
||||
**From(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
mesh_pb2.MeshPacket(
|
||||
decoded=mesh_pb2.Data(
|
||||
portnum=portnums_pb2.PortNum.NODEINFO_APP,
|
||||
payload=mesh_pb2.User(
|
||||
id="!00000009",
|
||||
long_name="Node 9",
|
||||
short_name="N9",
|
||||
macaddr=b"\x00\x00\x00\x00\x00\x09",
|
||||
hw_model=mesh_pb2.HardwareModel.RAK4631,
|
||||
public_key=base64.b64decode(
|
||||
"L0ih/6F41itofdE8mYyHk1SdfOJ/QRM1KQ+pO4vEEjQ="
|
||||
),
|
||||
).SerializeToString(),
|
||||
),
|
||||
**From(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"traceroute",
|
||||
mesh_pb2.MeshPacket(
|
||||
decoded=mesh_pb2.Data(
|
||||
portnum=portnums_pb2.PortNum.TRACEROUTE_APP,
|
||||
payload=mesh_pb2.RouteDiscovery(
|
||||
route=[10],
|
||||
).SerializeToString(),
|
||||
),
|
||||
**From(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"routing",
|
||||
mesh_pb2.MeshPacket(
|
||||
decoded=mesh_pb2.Data(
|
||||
portnum=portnums_pb2.PortNum.ROUTING_APP,
|
||||
payload=mesh_pb2.Routing(
|
||||
error_reason=mesh_pb2.Routing.NO_RESPONSE,
|
||||
).SerializeToString(),
|
||||
),
|
||||
**From(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"admin",
|
||||
mesh_pb2.MeshPacket(
|
||||
decoded=mesh_pb2.Data(
|
||||
portnum=portnums_pb2.PortNum.ADMIN_APP,
|
||||
payload=admin_pb2.AdminMessage(
|
||||
get_owner_request=True,
|
||||
).SerializeToString(),
|
||||
),
|
||||
pki_encrypted=True,
|
||||
**From(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"atak",
|
||||
mesh_pb2.MeshPacket(
|
||||
decoded=mesh_pb2.Data(
|
||||
portnum=portnums_pb2.PortNum.ATAK_PLUGIN,
|
||||
payload=atak_pb2.TAKPacket(
|
||||
is_compressed=True,
|
||||
# Note, the strings are not valid for a compressed message, but will
|
||||
# give the fuzzer a starting point.
|
||||
contact=atak_pb2.Contact(
|
||||
callsign="callsign", device_callsign="device_callsign"
|
||||
),
|
||||
chat=atak_pb2.GeoChat(
|
||||
message="message", to="to", to_callsign="to_callsign"
|
||||
),
|
||||
).SerializeToString(),
|
||||
),
|
||||
**From(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
for name, packet in packets:
|
||||
with open(f"{name}.MeshPacket", "wb") as f:
|
||||
f.write(packet.SerializeToString())
|
||||
1
.dockerignore
Symbolic link
1
.dockerignore
Symbolic link
@@ -0,0 +1 @@
|
||||
.gitignore
|
||||
51
.github/workflows/build_docker.yml
vendored
51
.github/workflows/build_docker.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Build Docker
|
||||
|
||||
on: workflow_call
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-native:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Docker login
|
||||
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: meshtastic
|
||||
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
|
||||
|
||||
- name: Docker setup
|
||||
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker build and push tagged versions
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: meshtastic/meshtasticd:${{ steps.version.outputs.long }}
|
||||
|
||||
- name: Docker build and push
|
||||
if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: meshtastic/meshtasticd:latest
|
||||
38
.github/workflows/build_native.yml
vendored
38
.github/workflows/build_native.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Build Native
|
||||
|
||||
on: workflow_call
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-native:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Setup native build
|
||||
id: base
|
||||
uses: ./.github/actions/setup-native
|
||||
|
||||
- name: Build Native
|
||||
run: bin/build-native.sh
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-native-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
path: |
|
||||
release/meshtasticd_linux_x86_64
|
||||
bin/config-dist.yaml
|
||||
52
.github/workflows/build_raspbian.yml
vendored
52
.github/workflows/build_raspbian.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: Build Raspbian
|
||||
|
||||
on: workflow_call
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-raspbian:
|
||||
runs-on: [self-hosted, linux, ARM64]
|
||||
steps:
|
||||
- name: Install libbluetooth
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -y --fix-missing
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Upgrade python tools
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio adafruit-nrfutil
|
||||
pip install -U meshtastic --pre
|
||||
|
||||
- name: Upgrade platformio
|
||||
shell: bash
|
||||
run: |
|
||||
pio upgrade
|
||||
|
||||
- name: Build Raspbian
|
||||
run: bin/build-native.sh
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-raspbian-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
path: |
|
||||
release/meshtasticd_linux_aarch64
|
||||
bin/config-dist.yaml
|
||||
52
.github/workflows/build_raspbian_armv7l.yml
vendored
52
.github/workflows/build_raspbian_armv7l.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: Build Raspbian Arm
|
||||
|
||||
on: workflow_call
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-raspbian-armv7l:
|
||||
runs-on: [self-hosted, linux, ARM]
|
||||
steps:
|
||||
- name: Install libbluetooth
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -y --fix-missing
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Upgrade python tools
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio adafruit-nrfutil
|
||||
pip install -U meshtastic --pre
|
||||
|
||||
- name: Upgrade platformio
|
||||
shell: bash
|
||||
run: |
|
||||
pio upgrade
|
||||
|
||||
- name: Build Raspbian
|
||||
run: bin/build-native.sh
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-raspbian-armv7l-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
path: |
|
||||
release/meshtasticd_linux_armv7l
|
||||
bin/config-dist.yaml
|
||||
8
.github/workflows/daily_packaging.yml
vendored
8
.github/workflows/daily_packaging.yml
vendored
@@ -20,6 +20,12 @@ permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
docker-multiarch:
|
||||
uses: ./.github/workflows/docker_manifest.yml
|
||||
with:
|
||||
release_channel: daily
|
||||
secrets: inherit
|
||||
|
||||
package-ppa:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -34,7 +40,7 @@ jobs:
|
||||
package-obs:
|
||||
uses: ./.github/workflows/package_obs.yml
|
||||
with:
|
||||
obs_project: home:meshtastic:daily
|
||||
obs_project: network:Meshtastic:daily
|
||||
series: unstable
|
||||
secrets: inherit
|
||||
|
||||
|
||||
92
.github/workflows/docker_build.yml
vendored
Normal file
92
.github/workflows/docker_build.yml
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
name: Build Docker
|
||||
|
||||
# Build Docker image, push untagged (digest-only)
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
DOCKER_FIRMWARE_TOKEN:
|
||||
required: false # Only required for push
|
||||
inputs:
|
||||
distro:
|
||||
description: Distro to target
|
||||
required: true
|
||||
type: string
|
||||
# choices: [debian, alpine]
|
||||
platform:
|
||||
description: Platform to target
|
||||
required: true
|
||||
type: string
|
||||
runs-on:
|
||||
description: Runner to use
|
||||
required: true
|
||||
type: string
|
||||
push:
|
||||
description: Push images to registry
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
outputs:
|
||||
digest:
|
||||
description: Digest of built image
|
||||
value: ${{ jobs.docker-build.outputs.digest }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
outputs:
|
||||
digest: ${{ steps.docker_variant.outputs.digest }}
|
||||
runs-on: ${{ inputs.runs-on }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Get release version string
|
||||
run: |
|
||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Docker login
|
||||
if: ${{ inputs.push }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: meshtastic
|
||||
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Docker setup
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Sanitize platform string
|
||||
id: sanitize_platform
|
||||
# Replace slashes with underscores
|
||||
run: echo "cleaned_platform=${{ inputs.platform }}" | sed 's/\//_/g' >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Docker tag
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: meshtastic/meshtasticd
|
||||
tags: |
|
||||
GHA-${{ steps.version.outputs.long }}-${{ inputs.distro }}-${{ steps.sanitize_platform.outputs.cleaned_platform }}
|
||||
flavor: latest=false
|
||||
|
||||
- name: Docker build and push
|
||||
uses: docker/build-push-action@v6
|
||||
id: docker_variant
|
||||
with:
|
||||
context: .
|
||||
file: |
|
||||
${{ contains(inputs.distro, 'debian') && './Dockerfile' || contains(inputs.distro, 'alpine') && './alpine.Dockerfile' }}
|
||||
push: ${{ inputs.push }}
|
||||
tags: ${{ steps.meta.outputs.tags }} # Tag is only meant to be consumed by the "manifest" job
|
||||
platforms: ${{ inputs.platform }}
|
||||
186
.github/workflows/docker_manifest.yml
vendored
Normal file
186
.github/workflows/docker_manifest.yml
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
name: Build Docker Multi-Arch Manifest
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
DOCKER_FIRMWARE_TOKEN:
|
||||
required: true
|
||||
inputs:
|
||||
release_channel:
|
||||
description: Release channel to target
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
docker-debian-amd64:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: true
|
||||
secrets: inherit
|
||||
|
||||
docker-debian-arm64:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: true
|
||||
secrets: inherit
|
||||
|
||||
docker-debian-armv7:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm/v7
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: true
|
||||
secrets: inherit
|
||||
|
||||
docker-alpine-amd64:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: true
|
||||
secrets: inherit
|
||||
|
||||
docker-alpine-arm64:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: true
|
||||
secrets: inherit
|
||||
|
||||
docker-alpine-armv7:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/arm/v7
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: true
|
||||
secrets: inherit
|
||||
|
||||
docker-manifest:
|
||||
needs:
|
||||
# Debian
|
||||
- docker-debian-amd64
|
||||
- docker-debian-arm64
|
||||
- docker-debian-armv7
|
||||
# Alpine
|
||||
- docker-alpine-amd64
|
||||
- docker-alpine-arm64
|
||||
- docker-alpine-armv7
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Get release version string
|
||||
run: |
|
||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
echo "short=$(./bin/buildinfo.py short)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Enumerate tags
|
||||
shell: python
|
||||
run: |
|
||||
import os
|
||||
|
||||
short = "${{ steps.version.outputs.short }}"
|
||||
long = "${{ steps.version.outputs.long }}"
|
||||
release_channel = "${{ inputs.release_channel }}"
|
||||
tags = {
|
||||
"beta": {
|
||||
"debian": [
|
||||
f"{short}", f"{long}", f"{short}-beta", f"{long}-beta", "beta", "latest",
|
||||
f"{short}-debian", f"{long}-debian", f"{short}-beta-debian", f"{long}-beta-debian", "beta-debian"
|
||||
],
|
||||
"alpine": [
|
||||
f"{short}-alpine", f"{long}-alpine", f"{short}-beta-alpine", f"{long}-beta-alpine", "beta-alpine"
|
||||
]
|
||||
},
|
||||
"alpha": {
|
||||
"debian": [
|
||||
f"{short}-alpha", f"{long}-alpha", "alpha",
|
||||
f"{short}-alpha-debian", f"{long}-alpha-debian", "alpha-debian"
|
||||
],
|
||||
"alpine": [
|
||||
f"{short}-alpha-alpine", f"{long}-alpha-alpine", "alpha-alpine"
|
||||
]
|
||||
},
|
||||
"daily": {
|
||||
"debian": ["daily", "daily-debian"],
|
||||
"alpine": ["daily-alpine"]
|
||||
}
|
||||
}
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a") as fh:
|
||||
fh.write("debian<<EOF\n")
|
||||
fh.write("\n".join(tags[release_channel]["debian"]))
|
||||
fh.write("\nEOF\n")
|
||||
|
||||
fh.write("alpine<<EOF\n")
|
||||
fh.write("\n".join(tags[release_channel]["alpine"]))
|
||||
fh.write("\nEOF\n")
|
||||
id: tags
|
||||
|
||||
- name: Docker login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: meshtastic
|
||||
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
|
||||
|
||||
- name: Docker meta (Debian)
|
||||
id: meta_debian
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: meshtastic/meshtasticd
|
||||
tags: |
|
||||
${{ steps.tags.outputs.debian }}
|
||||
flavor: latest=false
|
||||
|
||||
- name: Create Docker manifest (Debian)
|
||||
id: manifest_debian
|
||||
uses: int128/docker-manifest-create-action@v2
|
||||
with:
|
||||
tags: |
|
||||
${{ steps.meta_debian.outputs.tags }}
|
||||
push: true
|
||||
sources: |
|
||||
meshtastic/meshtasticd@${{ needs.docker-debian-amd64.outputs.digest }}
|
||||
meshtastic/meshtasticd@${{ needs.docker-debian-arm64.outputs.digest }}
|
||||
meshtastic/meshtasticd@${{ needs.docker-debian-armv7.outputs.digest }}
|
||||
|
||||
- name: Docker meta (Alpine)
|
||||
id: meta_alpine
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: meshtastic/meshtasticd
|
||||
tags: |
|
||||
${{ steps.tags.outputs.alpine }}
|
||||
|
||||
- name: Create Docker manifest (Alpine)
|
||||
id: manifest_alpine
|
||||
uses: int128/docker-manifest-create-action@v2
|
||||
with:
|
||||
tags: |
|
||||
${{ steps.meta_alpine.outputs.tags }}
|
||||
push: true
|
||||
sources: |
|
||||
meshtastic/meshtasticd@${{ needs.docker-alpine-amd64.outputs.digest }}
|
||||
meshtastic/meshtasticd@${{ needs.docker-alpine-arm64.outputs.digest }}
|
||||
meshtastic/meshtasticd@${{ needs.docker-alpine-armv7.outputs.digest }}
|
||||
45
.github/workflows/hook_copr.yml
vendored
45
.github/workflows/hook_copr.yml
vendored
@@ -3,9 +3,7 @@ name: Trigger COPR build
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
COPR_HOOK_DAILY:
|
||||
COPR_HOOK_ALPHA:
|
||||
COPR_HOOK_BETA:
|
||||
COPR_API_CONFIG:
|
||||
inputs:
|
||||
copr_project:
|
||||
description: COPR project to target
|
||||
@@ -27,35 +25,14 @@ jobs:
|
||||
ref: ${{ github.ref }}
|
||||
repository: ${{ github.repository }}
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
pip install requests
|
||||
|
||||
- name: Trigger COPR build
|
||||
shell: python
|
||||
run: |
|
||||
import requests
|
||||
|
||||
project_name = "${{ inputs.copr_project }}"
|
||||
if project_name == "daily":
|
||||
hook_secret = "${{ secrets.COPR_HOOK_DAILY }}"
|
||||
project_id = 160277
|
||||
elif project_name == "alpha":
|
||||
hook_secret = "${{ secrets.COPR_HOOK_ALPHA }}"
|
||||
project_id = 160278
|
||||
elif project_name == "beta":
|
||||
hook_secret = "${{ secrets.COPR_HOOK_BETA }}"
|
||||
project_id = 160279
|
||||
else:
|
||||
raise ValueError(f"Unknown COPR project: {project_name}")
|
||||
|
||||
webhook_url = f"https://copr.fedorainfracloud.org/webhooks/github/{project_id}/{hook_secret}/meshtasticd/"
|
||||
copr_payload = {
|
||||
"ref": "${{ github.ref }}",
|
||||
"after": "${{ github.sha }}",
|
||||
"repository": {
|
||||
"clone_url": "${{ github.server_url }}/${{ github.repository }}.git",
|
||||
}
|
||||
}
|
||||
r = requests.post(webhook_url, json=copr_payload, headers={"X-GitHub-Event": "push"})
|
||||
r.raise_for_status()
|
||||
uses: vidplace7/copr-build@main
|
||||
id: copr_build
|
||||
env:
|
||||
COPR_API_TOKEN_CONFIG: ${{ secrets.COPR_API_CONFIG }}
|
||||
with:
|
||||
owner: "@meshtastic"
|
||||
package-name: meshtasticd
|
||||
project-name: ${{ inputs.copr_project }}
|
||||
git-remote: "${{ github.server_url }}/${{ github.repository }}.git"
|
||||
committish: ${{ github.sha }}
|
||||
|
||||
86
.github/workflows/main_matrix.yml
vendored
86
.github/workflows/main_matrix.yml
vendored
@@ -128,15 +128,6 @@ jobs:
|
||||
with:
|
||||
board: ${{ matrix.board }}
|
||||
|
||||
package-raspbian:
|
||||
uses: ./.github/workflows/package_raspbian.yml
|
||||
|
||||
package-raspbian-armv7l:
|
||||
uses: ./.github/workflows/package_raspbian_armv7l.yml
|
||||
|
||||
package-native:
|
||||
uses: ./.github/workflows/package_amd64.yml
|
||||
|
||||
build-debian-src:
|
||||
uses: ./.github/workflows/build_debian_src.yml
|
||||
with:
|
||||
@@ -144,13 +135,46 @@ jobs:
|
||||
build_location: local
|
||||
secrets: inherit
|
||||
|
||||
package-pio-deps-native:
|
||||
uses: ./.github/workflows/package_pio_deps.yml
|
||||
with:
|
||||
pio_env: native
|
||||
secrets: inherit
|
||||
|
||||
test-native:
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
|
||||
build-docker:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/build_docker.yml
|
||||
secrets: inherit
|
||||
docker-debian-amd64:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-alpine-amd64:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-debian-arm64:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: false
|
||||
|
||||
docker-debian-armv7:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm/v7
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: false
|
||||
|
||||
after-checks:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -262,13 +286,9 @@ jobs:
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
needs:
|
||||
[
|
||||
gather-artifacts,
|
||||
package-raspbian,
|
||||
package-raspbian-armv7l,
|
||||
package-native,
|
||||
build-debian-src,
|
||||
]
|
||||
- gather-artifacts
|
||||
- build-debian-src
|
||||
- package-pio-deps-native
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -297,13 +317,6 @@ jobs:
|
||||
body: |
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
|
||||
- name: Download deb files
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: meshtasticd_${{ steps.version.outputs.long }}_*.deb
|
||||
merge-multiple: true
|
||||
path: ./output
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -311,20 +324,27 @@ jobs:
|
||||
merge-multiple: true
|
||||
path: ./output/debian-src
|
||||
|
||||
- name: Zip source deb
|
||||
- name: Download native pio deps
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: platformio-deps-native-${{ steps.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./output/pio-deps-native
|
||||
|
||||
- name: Zip linux sources
|
||||
working-directory: output
|
||||
run: zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src
|
||||
run: |
|
||||
zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src
|
||||
zip -9 -r ./platformio-deps-native-${{ steps.version.outputs.long }}.zip ./pio-deps-native
|
||||
|
||||
# For diagnostics
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Add deb files to release
|
||||
- name: Add linux sources to release
|
||||
run: |
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_arm64.deb
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_armhf.deb
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_amd64.deb
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd-${{ steps.version.outputs.deb }}-src.zip
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-${{ steps.version.outputs.long }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
90
.github/workflows/package_amd64.yml
vendored
90
.github/workflows/package_amd64.yml
vendored
@@ -1,90 +0,0 @@
|
||||
name: Package Native
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-native:
|
||||
uses: ./.github/workflows/build_native.yml
|
||||
|
||||
package-native:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build-native
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Pull web ui
|
||||
uses: dsaltares/fetch-gh-release-asset@master
|
||||
with:
|
||||
repo: meshtastic/web
|
||||
file: build.tar
|
||||
target: build.tar
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: firmware-native-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
- name: build .debpkg
|
||||
run: |
|
||||
mkdir -p .debpkg/DEBIAN
|
||||
mkdir -p .debpkg/usr/share/meshtasticd/web
|
||||
mkdir -p .debpkg/usr/sbin
|
||||
mkdir -p .debpkg/etc/meshtasticd
|
||||
mkdir -p .debpkg/etc/meshtasticd/config.d
|
||||
mkdir -p .debpkg/etc/meshtasticd/available.d
|
||||
mkdir -p .debpkg/usr/lib/systemd/system/
|
||||
tar -xf build.tar -C .debpkg/usr/share/meshtasticd/web
|
||||
shopt -s dotglob nullglob
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then mv .debpkg/usr/share/meshtasticd/web/build/* .debpkg/usr/share/meshtasticd/web/; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then rmdir .debpkg/usr/share/meshtasticd/web/build; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/.DS_Store ]; then rm -f .debpkg/usr/share/meshtasticd/web/.DS_Store; fi
|
||||
gunzip .debpkg/usr/share/meshtasticd/web/ -r
|
||||
cp release/meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
|
||||
cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/ -r
|
||||
chmod +x .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
|
||||
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
|
||||
chmod +x .debpkg/DEBIAN/conffiles
|
||||
# Transition /usr/share/doc/meshtasticd to /usr/share/meshtasticd
|
||||
echo "rm -rf /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/preinst
|
||||
chmod +x .debpkg/DEBIAN/preinst
|
||||
echo "ln -sf /usr/share/meshtasticd /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/postinst
|
||||
chmod +x .debpkg/DEBIAN/postinst
|
||||
|
||||
- uses: jiro4989/build-deb-action@v3
|
||||
with:
|
||||
package: meshtasticd
|
||||
package_root: .debpkg
|
||||
maintainer: Jonathan Bennett
|
||||
version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.*
|
||||
arch: amd64
|
||||
depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
|
||||
desc: Native Linux Meshtastic binary.
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: meshtasticd_${{ steps.version.outputs.long }}_amd64.deb
|
||||
overwrite: true
|
||||
path: |
|
||||
./*.deb
|
||||
65
.github/workflows/package_pio_deps.yml
vendored
Normal file
65
.github/workflows/package_pio_deps.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Package PlatformIO Library Dependencies
|
||||
# trunk-ignore-all(checkov/CKV_GHA_7): Allow workflow_dispatch inputs for testing
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pio_env:
|
||||
description: PlatformIO environment to target
|
||||
required: true
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pio_env:
|
||||
description: PlatformIO environment to target
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
pkg-pio-libdeps:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
pip install platformio
|
||||
|
||||
- name: Get release version string
|
||||
run: |
|
||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Fetch libdeps
|
||||
shell: bash
|
||||
run: |-
|
||||
platformio pkg install -e ${{ inputs.pio_env }}
|
||||
platformio pkg install -e ${{ inputs.pio_env }} -t platformio/tool-scons@4.40502.0
|
||||
env:
|
||||
PLATFORMIO_LIBDEPS_DIR: pio/libdeps
|
||||
PLATFORMIO_PACKAGES_DIR: pio/packages
|
||||
PLATFORMIO_CORE_DIR: pio/core
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
|
||||
overwrite: true
|
||||
include-hidden-files: true
|
||||
path: |
|
||||
pio/*
|
||||
90
.github/workflows/package_raspbian.yml
vendored
90
.github/workflows/package_raspbian.yml
vendored
@@ -1,90 +0,0 @@
|
||||
name: Package Raspbian
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-raspbian:
|
||||
uses: ./.github/workflows/build_raspbian.yml
|
||||
|
||||
package-raspbian:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build-raspbian
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Pull web ui
|
||||
uses: dsaltares/fetch-gh-release-asset@master
|
||||
with:
|
||||
repo: meshtastic/web
|
||||
file: build.tar
|
||||
target: build.tar
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: firmware-raspbian-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
- name: build .debpkg
|
||||
run: |
|
||||
mkdir -p .debpkg/DEBIAN
|
||||
mkdir -p .debpkg/usr/share/meshtasticd/web
|
||||
mkdir -p .debpkg/usr/sbin
|
||||
mkdir -p .debpkg/etc/meshtasticd
|
||||
mkdir -p .debpkg/etc/meshtasticd/config.d
|
||||
mkdir -p .debpkg/etc/meshtasticd/available.d
|
||||
mkdir -p .debpkg/usr/lib/systemd/system/
|
||||
tar -xf build.tar -C .debpkg/usr/share/meshtasticd/web
|
||||
shopt -s dotglob nullglob
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then mv .debpkg/usr/share/meshtasticd/web/build/* .debpkg/usr/share/meshtasticd/web/; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then rmdir .debpkg/usr/share/meshtasticd/web/build; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/.DS_Store ]; then rm -f .debpkg/usr/share/meshtasticd/web/.DS_Store; fi
|
||||
gunzip .debpkg/usr/share/meshtasticd/web/ -r
|
||||
cp release/meshtasticd_linux_aarch64 .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
|
||||
cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/ -r
|
||||
chmod +x .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
|
||||
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
|
||||
chmod +x .debpkg/DEBIAN/conffiles
|
||||
# Transition /usr/share/doc/meshtasticd to /usr/share/meshtasticd
|
||||
echo "rm -rf /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/preinst
|
||||
chmod +x .debpkg/DEBIAN/preinst
|
||||
echo "ln -sf /usr/share/meshtasticd /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/postinst
|
||||
chmod +x .debpkg/DEBIAN/postinst
|
||||
|
||||
- uses: jiro4989/build-deb-action@v3
|
||||
with:
|
||||
package: meshtasticd
|
||||
package_root: .debpkg
|
||||
maintainer: Jonathan Bennett
|
||||
version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.*
|
||||
arch: arm64
|
||||
depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
|
||||
desc: Native Linux Meshtastic binary.
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: meshtasticd_${{ steps.version.outputs.long }}_arm64.deb
|
||||
overwrite: true
|
||||
path: |
|
||||
./*.deb
|
||||
90
.github/workflows/package_raspbian_armv7l.yml
vendored
90
.github/workflows/package_raspbian_armv7l.yml
vendored
@@ -1,90 +0,0 @@
|
||||
name: Package Raspbian
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-raspbian_armv7l:
|
||||
uses: ./.github/workflows/build_raspbian_armv7l.yml
|
||||
|
||||
package-raspbian_armv7l:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build-raspbian_armv7l
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Pull web ui
|
||||
uses: dsaltares/fetch-gh-release-asset@master
|
||||
with:
|
||||
repo: meshtastic/web
|
||||
file: build.tar
|
||||
target: build.tar
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: firmware-raspbian-armv7l-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
- name: build .debpkg
|
||||
run: |
|
||||
mkdir -p .debpkg/DEBIAN
|
||||
mkdir -p .debpkg/usr/share/meshtasticd/web
|
||||
mkdir -p .debpkg/usr/sbin
|
||||
mkdir -p .debpkg/etc/meshtasticd
|
||||
mkdir -p .debpkg/etc/meshtasticd/config.d
|
||||
mkdir -p .debpkg/etc/meshtasticd/available.d
|
||||
mkdir -p .debpkg/usr/lib/systemd/system/
|
||||
tar -xf build.tar -C .debpkg/usr/share/meshtasticd/web
|
||||
shopt -s dotglob nullglob
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then mv .debpkg/usr/share/meshtasticd/web/build/* .debpkg/usr/share/meshtasticd/web/; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then rmdir .debpkg/usr/share/meshtasticd/web/build; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/.DS_Store ]; then rm -f .debpkg/usr/share/meshtasticd/web/.DS_Store; fi
|
||||
gunzip .debpkg/usr/share/meshtasticd/web/ -r
|
||||
cp release/meshtasticd_linux_armv7l .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
|
||||
cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/ -r
|
||||
chmod +x .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
|
||||
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
|
||||
chmod +x .debpkg/DEBIAN/conffiles
|
||||
# Transition /usr/share/doc/meshtasticd to /usr/share/meshtasticd
|
||||
echo "rm -rf /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/preinst
|
||||
chmod +x .debpkg/DEBIAN/preinst
|
||||
echo "ln -sf /usr/share/meshtasticd /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/postinst
|
||||
chmod +x .debpkg/DEBIAN/postinst
|
||||
|
||||
- uses: jiro4989/build-deb-action@v3
|
||||
with:
|
||||
package: meshtasticd
|
||||
package_root: .debpkg
|
||||
maintainer: Jonathan Bennett
|
||||
version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.*
|
||||
arch: armhf
|
||||
depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
|
||||
desc: Native Linux Meshtastic binary.
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: meshtasticd_${{ steps.version.outputs.long }}_armhf.deb
|
||||
overwrite: true
|
||||
path: |
|
||||
./*.deb
|
||||
21
.github/workflows/release_channels.yml
vendored
21
.github/workflows/release_channels.yml
vendored
@@ -9,6 +9,13 @@ permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-docker:
|
||||
uses: ./.github/workflows/docker_manifest.yml
|
||||
with:
|
||||
release_channel: |-
|
||||
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
secrets: inherit
|
||||
|
||||
package-ppa:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -25,14 +32,14 @@ jobs:
|
||||
uses: ./.github/workflows/package_obs.yml
|
||||
with:
|
||||
obs_project: |-
|
||||
home:meshtastic:${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
network:Meshtastic:${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
series: |-
|
||||
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
secrets: inherit
|
||||
|
||||
# hook-copr:
|
||||
# uses: ./.github/workflows/hook_copr.yml
|
||||
# with:
|
||||
# copr_project: |-
|
||||
# ${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
# secrets: inherit
|
||||
hook-copr:
|
||||
uses: ./.github/workflows/hook_copr.yml
|
||||
with:
|
||||
copr_project: |-
|
||||
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
secrets: inherit
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,6 +12,9 @@ web.tar
|
||||
*.code-workspace
|
||||
|
||||
.idea
|
||||
.platformio
|
||||
.local
|
||||
.cache
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
10
.trunk/configs/.prettierrc
Normal file
10
.trunk/configs/.prettierrc
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"overrides": [
|
||||
{
|
||||
"files": "userPrefs.jsonc",
|
||||
"options": {
|
||||
"trailingComma": "none"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -38,7 +38,8 @@ USER root
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libulfius2.7 libusb-1.0-0-dev liborcania2.3 libssl3 && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
&& mkdir -p /etc/meshtasticd/ssl
|
||||
|
||||
# Fetch compiled binary from the builder
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
|
||||
|
||||
@@ -29,7 +29,8 @@ USER root
|
||||
|
||||
RUN apk add libstdc++ libgpiod yaml-cpp libusb i2c-tools \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
&& mkdir -p /etc/meshtasticd/ssl
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
|
||||
|
||||
WORKDIR /var/lib/meshtasticd
|
||||
|
||||
@@ -4,7 +4,7 @@ platform = platformio/nordicnrf52@^10.7.0
|
||||
extends = arduino_base
|
||||
platform_packages =
|
||||
; our custom Git version until they merge our PR
|
||||
framework-arduinoadafruitnrf52 @ https://github.com/geeksville/Adafruit_nRF52_Arduino.git
|
||||
framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino.git#e13f5820002a4fb2a5e6754b42ace185277e5adf
|
||||
toolchain-gccarmnoneeabi@~1.90301.0
|
||||
|
||||
build_type = debug
|
||||
|
||||
@@ -11,9 +11,15 @@ build_flags =
|
||||
${arduino_base.build_flags}
|
||||
-flto
|
||||
-Isrc/platform/stm32wl -g
|
||||
-DMESHTASTIC_MINIMIZE_BUILD
|
||||
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
-DMESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
-DMESHTASTIC_EXCLUDE_I2C
|
||||
-DMESHTASTIC_EXCLUDE_POWERMON
|
||||
-DMESHTASTIC_EXCLUDE_SCREEN
|
||||
-DMESHTASTIC_EXCLUDE_MQTT
|
||||
-DMESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
-DMESHTASTIC_EXCLUDE_PKI
|
||||
-DMESHTASTIC_EXCLUDE_GPS
|
||||
-DDEBUG_MUTE
|
||||
; -DVECT_TAB_OFFSET=0x08000000
|
||||
-DconfigUSE_CMSIS_RTOS_V2=1
|
||||
; -DSPI_MODE_0=SPI_MODE0
|
||||
|
||||
18
bin/build-firmware.sh
Normal file
18
bin/build-firmware.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
|
||||
|
||||
export PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
|
||||
if (echo $2 | grep -q "esp32"); then
|
||||
bin/build-esp32.sh $1
|
||||
elif (echo $2 | grep -q "nrf52"); then
|
||||
bin/build-nrf52.sh $1
|
||||
elif (echo $2 | grep -q "stm32"); then
|
||||
bin/build-stm32.sh $1
|
||||
elif (echo $2 | grep -q "rpi2040"); then
|
||||
bin/build-rpi2040.sh $1
|
||||
else
|
||||
echo "Unknown target $2"
|
||||
exit 1
|
||||
fi
|
||||
@@ -78,6 +78,8 @@ Lora:
|
||||
# TXen: x # TX and RX enable pins
|
||||
# RXen: x
|
||||
|
||||
# SX126X_MAX_POWER: 8 # Limit the output power to 8 dBm, useful for amped nodes
|
||||
|
||||
# spiSpeed: 2000000
|
||||
|
||||
### Set default/fallback gpio chip to use in /dev/. Defaults to 0.
|
||||
@@ -182,10 +184,12 @@ Logging:
|
||||
Webserver:
|
||||
# Port: 443 # Port for Webserver & Webservices
|
||||
# RootPath: /usr/share/meshtasticd/web # Root Dir of WebServer
|
||||
# SSLKey: /etc/meshtasticd/ssl/private_key.pem # Path to SSL Key, generated if not present
|
||||
# SSLCert: /etc/meshtasticd/ssl/certificate.pem # Path to SSL Certificate, generated if not present
|
||||
|
||||
General:
|
||||
MaxNodes: 200
|
||||
MaxMessageQueue: 100
|
||||
ConfigDirectory: /etc/meshtasticd/config.d/
|
||||
# MACAddress: AA:BB:CC:DD:EE:FF
|
||||
# MACAddressSource: eth0
|
||||
# MACAddressSource: eth0
|
||||
9
bin/config.d/OpenWRT/BananaPi-BPI-R4-sx1262.yaml
Normal file
9
bin/config.d/OpenWRT/BananaPi-BPI-R4-sx1262.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
Lora:
|
||||
Module: sx1262 # BananaPi-BPI-R4 SPI via 26p GPIO Header
|
||||
## CS: 28
|
||||
IRQ: 50
|
||||
Busy: 62
|
||||
Reset: 51
|
||||
spidev: spidev1.0
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
@@ -7,3 +7,6 @@ Lora:
|
||||
TXen: 13
|
||||
RXen: 12
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
# Only for E22-900M33S:
|
||||
# Limit the output power to 8 dBm
|
||||
# SX126X_MAX_POWER: 8
|
||||
@@ -102,7 +102,7 @@ pref_flags = []
|
||||
for pref in userPrefs:
|
||||
if userPrefs[pref].startswith("{"):
|
||||
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
|
||||
elif userPrefs[pref].replace(".", "").isdigit():
|
||||
elif userPrefs[pref].lstrip("-").replace(".", "").isdigit():
|
||||
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
|
||||
elif userPrefs[pref] == "true" or userPrefs[pref] == "false":
|
||||
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
|
||||
|
||||
52
boards/meshlink.json
Normal file
52
boards/meshlink.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DMESHLINK -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x00B3"],
|
||||
["0x239A", "0x8029"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x239A", "0x802A"]
|
||||
],
|
||||
"usb_product": "MeshLink",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "meshlink",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "MeshLink",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.loraitalia.it",
|
||||
"vendor": "LoraItalia"
|
||||
}
|
||||
7
debian/changelog
vendored
7
debian/changelog
vendored
@@ -1,6 +1,9 @@
|
||||
meshtasticd (2.5.20.0) UNRELEASED; urgency=medium
|
||||
meshtasticd (2.5.22.0) UNRELEASED; urgency=medium
|
||||
|
||||
* Initial packaging
|
||||
* GitHub Actions Automatic version bump
|
||||
* GitHub Actions Automatic version bump
|
||||
* GitHub Actions Automatic version bump
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
-- Austin Lane <github-actions[bot]@users.noreply.github.com> Mon, 13 Jan 2025 19:24:14 +0000
|
||||
-- Austin Lane <github-actions[bot]@users.noreply.github.com> Wed, 05 Feb 2025 01:10:33 +0000
|
||||
|
||||
2
debian/ci_pack_sdeb.sh
vendored
2
debian/ci_pack_sdeb.sh
vendored
@@ -11,7 +11,7 @@ platformio pkg install -e native -t platformio/tool-scons@4.40502.0
|
||||
tar -cf pio.tar pio/
|
||||
rm -rf pio
|
||||
# Download the latest meshtastic/web release build.tar to `web.tar`
|
||||
curl -L https://github.com/meshtastic/web/releases/download/latest/build.tar -o web.tar
|
||||
curl -L https://github.com/meshtastic/web/releases/latest/download/build.tar -o web.tar
|
||||
|
||||
package=$(dpkg-parsechangelog --show-field Source)
|
||||
|
||||
|
||||
3
debian/meshtasticd.dirs
vendored
3
debian/meshtasticd.dirs
vendored
@@ -1,4 +1,5 @@
|
||||
etc/meshtasticd
|
||||
etc/meshtasticd/config.d
|
||||
etc/meshtasticd/available.d
|
||||
usr/share/meshtasticd/web
|
||||
usr/share/meshtasticd/web
|
||||
etc/meshtasticd/ssl
|
||||
@@ -21,7 +21,7 @@ Summary: Meshtastic daemon for communicating with Meshtastic devices
|
||||
License: GPL-3.0
|
||||
URL: https://github.com/meshtastic/firmware
|
||||
Source0: {{{ git_dir_pack }}}
|
||||
Source1: https://github.com/meshtastic/web/releases/download/latest/build.tar
|
||||
Source1: https://github.com/meshtastic/web/releases/latest/download/build.tar
|
||||
|
||||
BuildRequires: systemd-rpm-macros
|
||||
BuildRequires: python3-devel
|
||||
@@ -72,6 +72,8 @@ install -D -m 0644 bin/meshtasticd.service %{buildroot}%{_unitdir}/meshtasticd.s
|
||||
# Install the web files under /usr/share/meshtasticd/web
|
||||
mkdir -p %{buildroot}%{_datadir}/meshtasticd/web
|
||||
cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
|
||||
# Install default SSL storage directory (for web)
|
||||
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/ssl
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
@@ -86,6 +88,7 @@ cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
|
||||
%dir %{_datadir}/meshtasticd
|
||||
%dir %{_datadir}/meshtasticd/web
|
||||
%{_datadir}/meshtasticd/web/*
|
||||
%dir %{_sysconfdir}/meshtasticd/ssl
|
||||
|
||||
%changelog
|
||||
%autochangelog
|
||||
@@ -20,7 +20,7 @@ extra_scripts = bin/platformio-custom.py
|
||||
build_flags = -Wno-missing-field-initializers
|
||||
|
||||
-Wno-format
|
||||
-Isrc -Isrc/mesh -Isrc/mesh/generated -Isrc/gps -Isrc/buzz -Wl,-Map,.pio/build/output.map
|
||||
-Isrc -Isrc/mesh -Isrc/mesh/generated -Isrc/gps -Isrc/buzz -Wl,-Map,"${platformio.build_dir}"/output.map
|
||||
-DUSE_THREAD_NAMES
|
||||
-DTINYGPS_OPTION_NO_CUSTOM_FIELDS
|
||||
-DPB_ENABLE_MALLOC=1
|
||||
@@ -126,6 +126,7 @@ lib_deps =
|
||||
mprograms/QMC5883LCompass@1.2.3
|
||||
dfrobot/DFRobot_RTU@1.0.3
|
||||
https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d
|
||||
https://github.com/DFRobot/DFRobot_RainfallSensor#38fea5e02b40a5430be6dab39a99a6f6347d667e
|
||||
robtillaart/INA226@0.6.0
|
||||
|
||||
; Health Sensor Libraries
|
||||
|
||||
Submodule protobufs updated: 76f806e1bb...068646653e
@@ -153,7 +153,7 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1);
|
||||
#endif
|
||||
#if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX)
|
||||
pixels.fill(BUTTON2_COLOR, BUTTON1_COLOR_INDEX, 1);
|
||||
pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1);
|
||||
#endif
|
||||
#endif
|
||||
pixels.show();
|
||||
|
||||
@@ -190,6 +190,20 @@ int32_t ButtonThread::runOnce()
|
||||
case 4:
|
||||
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
|
||||
break;
|
||||
#endif
|
||||
#if defined(RAK_4631)
|
||||
// 5 clicks: start accelerometer/magenetometer calibration for 30 seconds
|
||||
case 5:
|
||||
if (accelerometerThread) {
|
||||
accelerometerThread->calibrate(30);
|
||||
}
|
||||
break;
|
||||
// 6 clicks: start accelerometer/magenetometer calibration for 60 seconds
|
||||
case 6:
|
||||
if (accelerometerThread) {
|
||||
accelerometerThread->calibrate(60);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
// No valid multipress action
|
||||
default:
|
||||
|
||||
@@ -49,24 +49,6 @@ void OSFS::writeNBytes(uint16_t address, unsigned int num, const byte *input)
|
||||
}
|
||||
#endif
|
||||
|
||||
bool lfs_assert_failed =
|
||||
false; // Note: we use this global on all platforms, though it can only be set true on nrf52 (in our modified lfs_util.h)
|
||||
|
||||
extern "C" void lfs_assert(const char *reason)
|
||||
{
|
||||
LOG_ERROR("LFS assert: %s", reason);
|
||||
lfs_assert_failed = true;
|
||||
|
||||
#ifndef ARCH_PORTDUINO
|
||||
#ifdef FSCom
|
||||
// CORRUPTED FILESYSTEM. This causes bootloop so
|
||||
// might as well try formatting now.
|
||||
LOG_ERROR("Trying FSCom.format()");
|
||||
FSCom.format();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copies a file from one location to another.
|
||||
*
|
||||
@@ -203,7 +185,7 @@ std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels)
|
||||
file.close();
|
||||
}
|
||||
} else {
|
||||
meshtastic_FileInfo fileInfo = {"", file.size()};
|
||||
meshtastic_FileInfo fileInfo = {"", static_cast<uint32_t>(file.size())};
|
||||
#ifdef ARCH_ESP32
|
||||
strcpy(fileInfo.file_name, file.path());
|
||||
#else
|
||||
@@ -348,10 +330,16 @@ void rmDir(const char *dirname)
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Some platforms (nrf52) might need to do an extra step before FSBegin().
|
||||
*/
|
||||
__attribute__((weak, noinline)) void preFSBegin() {}
|
||||
|
||||
void fsInit()
|
||||
{
|
||||
#ifdef FSCom
|
||||
spiLock->lock();
|
||||
concurrency::LockGuard g(spiLock);
|
||||
preFSBegin();
|
||||
if (!FSBegin()) {
|
||||
LOG_ERROR("Filesystem mount failed");
|
||||
// assert(0); This auto-formats the partition, so no need to fail here.
|
||||
@@ -362,7 +350,6 @@ void fsInit()
|
||||
LOG_DEBUG("Filesystem files:");
|
||||
#endif
|
||||
listDir("/", 10);
|
||||
spiLock->unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -400,4 +387,4 @@ void setupSDCard()
|
||||
LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024)));
|
||||
LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024)));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,4 @@ bool renameFile(const char *pathFrom, const char *pathTo);
|
||||
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
|
||||
void listDir(const char *dirname, uint8_t levels, bool del = false);
|
||||
void rmDir(const char *dirname);
|
||||
void setupSDCard();
|
||||
|
||||
extern bool lfs_assert_failed; // Note: we use this global on all platforms, though it can only be set true on nrf52 (in our
|
||||
// modified lfs_util.h)
|
||||
void setupSDCard();
|
||||
@@ -87,7 +87,7 @@ MAX17048Sensor max17048Sensor;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if HAS_RAKPROT && !defined(ARCH_PORTDUINO)
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && HAS_RAKPROT && !defined(ARCH_PORTDUINO)
|
||||
RAK9154Sensor rak9154Sensor;
|
||||
#endif
|
||||
|
||||
@@ -243,7 +243,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
virtual uint16_t getBattVoltage() override
|
||||
{
|
||||
|
||||
#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
|
||||
#if HAS_TELEMETRY && defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) && \
|
||||
!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
if (hasRAK()) {
|
||||
return getRAKVoltage();
|
||||
}
|
||||
@@ -406,7 +407,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
/// we can't be smart enough to say 'full'?
|
||||
virtual bool isCharging() override
|
||||
{
|
||||
#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && \
|
||||
!defined(HAS_PMU)
|
||||
if (hasRAK()) {
|
||||
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
|
||||
}
|
||||
@@ -447,7 +449,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
|
||||
uint32_t last_read_time_ms = 0;
|
||||
|
||||
#if defined(HAS_RAKPROT)
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT)
|
||||
|
||||
uint16_t getRAKVoltage() { return rak9154Sensor.getBusVoltageMv(); }
|
||||
|
||||
|
||||
@@ -79,17 +79,17 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
|
||||
}
|
||||
if (color && logLevel != nullptr) {
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
|
||||
Print::write("\u001b[34m", 6);
|
||||
Print::write("\u001b[34m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
|
||||
Print::write("\u001b[32m", 6);
|
||||
Print::write("\u001b[32m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
|
||||
Print::write("\u001b[33m", 6);
|
||||
Print::write("\u001b[33m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
|
||||
Print::write("\u001b[31m", 6);
|
||||
Print::write("\u001b[31m", 5);
|
||||
}
|
||||
len = Print::write(printBuf, len);
|
||||
if (color && logLevel != nullptr) {
|
||||
Print::write("\u001b[0m", 5);
|
||||
Print::write("\u001b[0m", 4);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
@@ -107,15 +107,15 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
|
||||
// include the header
|
||||
if (color) {
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
|
||||
Print::write("\u001b[34m", 6);
|
||||
Print::write("\u001b[34m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
|
||||
Print::write("\u001b[32m", 6);
|
||||
Print::write("\u001b[32m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
|
||||
Print::write("\u001b[33m", 6);
|
||||
Print::write("\u001b[33m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
|
||||
Print::write("\u001b[31m", 6);
|
||||
Print::write("\u001b[31m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0)
|
||||
Print::write("\u001b[35m", 6);
|
||||
Print::write("\u001b[35m", 5);
|
||||
}
|
||||
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
|
||||
@@ -393,4 +393,4 @@ std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
|
||||
break;
|
||||
}
|
||||
return std::string(formatted.get());
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,8 @@ static File openFile(const char *filename, bool fullAtomic)
|
||||
concurrency::LockGuard g(spiLock);
|
||||
LOG_DEBUG("Opening %s, fullAtomic=%d", filename, fullAtomic);
|
||||
#ifdef ARCH_NRF52
|
||||
lfs_assert_failed = false;
|
||||
File file = FSCom.open(filename, FILE_O_WRITE);
|
||||
file.seek(0);
|
||||
return file;
|
||||
FSCom.remove(filename);
|
||||
return FSCom.open(filename, FILE_O_WRITE);
|
||||
#endif
|
||||
if (!fullAtomic)
|
||||
FSCom.remove(filename); // Nuke the old file to make space (ignore if it !exists)
|
||||
@@ -20,7 +18,6 @@ static File openFile(const char *filename, bool fullAtomic)
|
||||
filenameTmp += ".tmp";
|
||||
|
||||
// clear any previous LFS errors
|
||||
lfs_assert_failed = false;
|
||||
return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE);
|
||||
}
|
||||
|
||||
@@ -61,9 +58,6 @@ bool SafeFile::close()
|
||||
return false;
|
||||
|
||||
spiLock->lock();
|
||||
#ifdef ARCH_NRF52
|
||||
f.truncate();
|
||||
#endif
|
||||
f.close();
|
||||
spiLock->unlock();
|
||||
|
||||
@@ -96,8 +90,6 @@ bool SafeFile::close()
|
||||
bool SafeFile::testReadback()
|
||||
{
|
||||
concurrency::LockGuard g(spiLock);
|
||||
bool lfs_failed = lfs_assert_failed;
|
||||
lfs_assert_failed = false;
|
||||
|
||||
String filenameTmp = filename;
|
||||
filenameTmp += ".tmp";
|
||||
@@ -119,7 +111,7 @@ bool SafeFile::testReadback()
|
||||
return false;
|
||||
}
|
||||
|
||||
return !lfs_failed;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -145,6 +145,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define OPT3001_ADDR_ALT 0x44
|
||||
#define MLX90632_ADDR 0x3A
|
||||
#define DFROBOT_LARK_ADDR 0x42
|
||||
#define DFROBOT_RAIN_ADDR 0x1d
|
||||
#define NAU7802_ADDR 0x2A
|
||||
#define MAX30102_ADDR 0x57
|
||||
#define MLX90614_ADDR_DEF 0x5A
|
||||
|
||||
@@ -66,6 +66,7 @@ class ScanI2C
|
||||
CGRADSENS,
|
||||
INA226,
|
||||
NXP_SE050,
|
||||
DFROBOT_RAIN,
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -84,23 +84,33 @@ ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const
|
||||
return o_probe;
|
||||
}
|
||||
uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation ®isterLocation,
|
||||
ScanI2CTwoWire::ResponseWidth responseWidth) const
|
||||
ScanI2CTwoWire::ResponseWidth responseWidth, bool zeropad = false) const
|
||||
{
|
||||
uint16_t value = 0x00;
|
||||
TwoWire *i2cBus = fetchI2CBus(registerLocation.i2cAddress);
|
||||
|
||||
i2cBus->beginTransmission(registerLocation.i2cAddress.address);
|
||||
i2cBus->write(registerLocation.registerAddress);
|
||||
if (zeropad) {
|
||||
// Lark Commands need the argument list length in 2 bytes.
|
||||
i2cBus->write((int)0);
|
||||
i2cBus->write((int)0);
|
||||
}
|
||||
i2cBus->endTransmission();
|
||||
delay(20);
|
||||
i2cBus->requestFrom(registerLocation.i2cAddress.address, responseWidth);
|
||||
if (i2cBus->available() == 2) {
|
||||
if (i2cBus->available() > 1) {
|
||||
// Read MSB, then LSB
|
||||
value = (uint16_t)i2cBus->read() << 8;
|
||||
value |= i2cBus->read();
|
||||
} else if (i2cBus->available()) {
|
||||
value = i2cBus->read();
|
||||
}
|
||||
// Drain excess bytes
|
||||
for (uint8_t i = 0; i < responseWidth - 1; i++) {
|
||||
if (i2cBus->available())
|
||||
i2cBus->read();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -286,7 +296,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
RESPONSE_PAYLOAD 0x01
|
||||
RESPONSE_PAYLOAD+1 0x00
|
||||
*/
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x05), 2);
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x05), 6, true);
|
||||
LOG_DEBUG("Register MFG_UID 05: 0x%x", registerValue);
|
||||
if (registerValue == 0x5305) {
|
||||
logFoundDevice("DFRobot Lark", (uint8_t)addr.address);
|
||||
@@ -402,6 +412,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
|
||||
#ifdef HAS_TPS65233
|
||||
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
|
||||
#endif
|
||||
|
||||
@@ -53,7 +53,7 @@ class ScanI2CTwoWire : public ScanI2C
|
||||
|
||||
concurrency::Lock lock;
|
||||
|
||||
uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const;
|
||||
uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth, bool) const;
|
||||
|
||||
DeviceType probeOLED(ScanI2C::DeviceAddress) const;
|
||||
|
||||
|
||||
@@ -6,28 +6,28 @@
|
||||
|
||||
void d_writeCommand(uint8_t c)
|
||||
{
|
||||
SPI1.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
if (PIN_EINK_DC >= 0)
|
||||
digitalWrite(PIN_EINK_DC, LOW);
|
||||
if (PIN_EINK_CS >= 0)
|
||||
digitalWrite(PIN_EINK_CS, LOW);
|
||||
SPI1.transfer(c);
|
||||
SPI.transfer(c);
|
||||
if (PIN_EINK_CS >= 0)
|
||||
digitalWrite(PIN_EINK_CS, HIGH);
|
||||
if (PIN_EINK_DC >= 0)
|
||||
digitalWrite(PIN_EINK_DC, HIGH);
|
||||
SPI1.endTransaction();
|
||||
SPI.endTransaction();
|
||||
}
|
||||
|
||||
void d_writeData(uint8_t d)
|
||||
{
|
||||
SPI1.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
if (PIN_EINK_CS >= 0)
|
||||
digitalWrite(PIN_EINK_CS, LOW);
|
||||
SPI1.transfer(d);
|
||||
SPI.transfer(d);
|
||||
if (PIN_EINK_CS >= 0)
|
||||
digitalWrite(PIN_EINK_CS, HIGH);
|
||||
SPI1.endTransaction();
|
||||
SPI.endTransaction();
|
||||
}
|
||||
|
||||
unsigned long d_waitWhileBusy(uint16_t busy_time)
|
||||
@@ -53,7 +53,7 @@ unsigned long d_waitWhileBusy(uint16_t busy_time)
|
||||
|
||||
void scanEInkDevice(void)
|
||||
{
|
||||
SPI1.begin();
|
||||
SPI.begin();
|
||||
d_writeCommand(0x22);
|
||||
d_writeData(0x83);
|
||||
d_writeCommand(0x20);
|
||||
@@ -62,6 +62,6 @@ void scanEInkDevice(void)
|
||||
LOG_DEBUG("EInk display found");
|
||||
else
|
||||
LOG_DEBUG("EInk display not found");
|
||||
SPI1.end();
|
||||
SPI.end();
|
||||
}
|
||||
#endif
|
||||
@@ -449,7 +449,22 @@ bool GPS::setup()
|
||||
if (!didSerialInit) {
|
||||
int msglen = 0;
|
||||
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
#ifdef TRACKER_T1000_E
|
||||
// add power up/down strategy, improve ag3335 detection success
|
||||
digitalWrite(PIN_GPS_EN, LOW);
|
||||
delay(500);
|
||||
digitalWrite(GPS_VRTC_EN, LOW);
|
||||
delay(1000);
|
||||
digitalWrite(GPS_VRTC_EN, HIGH);
|
||||
delay(500);
|
||||
digitalWrite(PIN_GPS_EN, HIGH);
|
||||
delay(1000);
|
||||
#endif
|
||||
#ifdef TRACKER_T1000_E
|
||||
if (probeTries < 5) {
|
||||
#else
|
||||
if (probeTries < 2) {
|
||||
#endif
|
||||
LOG_DEBUG("Probe for GPS at %d", serialSpeeds[speedSelect]);
|
||||
gnssModel = probe(serialSpeeds[speedSelect]);
|
||||
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
@@ -460,7 +475,11 @@ bool GPS::setup()
|
||||
}
|
||||
}
|
||||
// Rare Serial Speeds
|
||||
#ifdef TRACKER_T1000_E
|
||||
if (probeTries == 5) {
|
||||
#else
|
||||
if (probeTries == 2) {
|
||||
#endif
|
||||
LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]);
|
||||
gnssModel = probe(rareSerialSpeeds[speedSelect]);
|
||||
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
@@ -772,6 +791,9 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
|
||||
setPowerPMU(true); // Power (PMU): on
|
||||
writePinStandby(false); // Standby (pin): awake (not standby)
|
||||
setPowerUBLOX(true); // Standby (UBLOX): awake
|
||||
#ifdef GNSS_AIROHA
|
||||
lastFixStartMsec = 0;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case GPS_SOFTSLEEP:
|
||||
|
||||
@@ -140,6 +140,15 @@ bool EInkDisplay::connect()
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
||||
}
|
||||
#elif defined(MESHLINK)
|
||||
{
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
|
||||
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay->init();
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
||||
}
|
||||
#elif defined(RAK4630) || defined(MAKERPYTHON)
|
||||
{
|
||||
if (eink_found) {
|
||||
|
||||
@@ -238,7 +238,7 @@ void EInkDynamicDisplay::checkRateLimiting()
|
||||
|
||||
// Skip update: too soon for BACKGROUND
|
||||
if (frameFlags == BACKGROUND) {
|
||||
if (Throttle::isWithinTimespanMs(previousRunMs, EINK_LIMIT_RATE_BACKGROUND_SEC * 1000)) {
|
||||
if (Throttle::isWithinTimespanMs(previousRunMs, 30000)) {
|
||||
refresh = SKIPPED;
|
||||
reason = EXCEEDED_RATELIMIT_FULL;
|
||||
return;
|
||||
@@ -251,7 +251,7 @@ void EInkDynamicDisplay::checkRateLimiting()
|
||||
|
||||
// Skip update: too soon for RESPONSIVE
|
||||
if (frameFlags & RESPONSIVE) {
|
||||
if (Throttle::isWithinTimespanMs(previousRunMs, EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000)) {
|
||||
if (Throttle::isWithinTimespanMs(previousRunMs, 1000)) {
|
||||
refresh = SKIPPED;
|
||||
reason = EXCEEDED_RATELIMIT_FAST;
|
||||
LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x", frameFlags);
|
||||
|
||||
@@ -123,7 +123,7 @@ static bool heartbeat = false;
|
||||
|
||||
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||
|
||||
/// Check if the display can render a string (detect special chars; emoji)
|
||||
// Check if the display can render a string (detect special chars; emoji)
|
||||
static bool haveGlyphs(const char *str)
|
||||
{
|
||||
#if defined(OLED_PL) || defined(OLED_UA) || defined(OLED_RU) || defined(OLED_CS)
|
||||
@@ -162,11 +162,7 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
|
||||
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
#ifdef USERPREFS_SPLASH_TITLE
|
||||
const char *title = USERPREFS_SPLASH_TITLE;
|
||||
#else
|
||||
const char *title = "meshtastic.org";
|
||||
#endif
|
||||
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
@@ -185,6 +181,56 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
|
||||
}
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
|
||||
static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
static const uint8_t xbm[] = USERPREFS_OEM_IMAGE_DATA;
|
||||
display->drawXbm(x + (SCREEN_WIDTH - USERPREFS_OEM_IMAGE_WIDTH) / 2,
|
||||
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - USERPREFS_OEM_IMAGE_HEIGHT) / 2 + 2, USERPREFS_OEM_IMAGE_WIDTH,
|
||||
USERPREFS_OEM_IMAGE_HEIGHT, xbm);
|
||||
|
||||
switch (USERPREFS_OEM_FONT_SIZE) {
|
||||
case 0:
|
||||
display->setFont(FONT_SMALL);
|
||||
break;
|
||||
case 2:
|
||||
display->setFont(FONT_LARGE);
|
||||
break;
|
||||
default:
|
||||
display->setFont(FONT_MEDIUM);
|
||||
break;
|
||||
}
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
const char *title = USERPREFS_OEM_TEXT;
|
||||
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// Draw region in upper left
|
||||
if (upperMsg)
|
||||
display->drawString(x + 0, y + 0, upperMsg);
|
||||
|
||||
// Draw version and shortname in upper right
|
||||
char buf[25];
|
||||
snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : "");
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display->drawString(x + SCREEN_WIDTH, y + 0, buf);
|
||||
screen->forceDisplay();
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
|
||||
}
|
||||
|
||||
static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// Draw region in upper left
|
||||
const char *region = myRegion ? myRegion->name : NULL;
|
||||
drawOEMIconScreen(region, display, state, x, y);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message)
|
||||
{
|
||||
uint16_t x_offset = display->width() / 2;
|
||||
@@ -1400,9 +1446,9 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
|
||||
static char distStr[20];
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
strncpy(distStr, "? mi", sizeof(distStr)); // might not have location data
|
||||
strncpy(distStr, "? mi ?°", sizeof(distStr)); // might not have location data
|
||||
} else {
|
||||
strncpy(distStr, "? km", sizeof(distStr));
|
||||
strncpy(distStr, "? km ?°", sizeof(distStr));
|
||||
}
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
const char *fields[] = {username, lastStr, signalStr, distStr, NULL};
|
||||
@@ -1435,18 +1481,6 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
float d =
|
||||
GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
if (d < (2 * MILES_TO_FEET))
|
||||
snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET);
|
||||
} else {
|
||||
if (d < 2000)
|
||||
snprintf(distStr, sizeof(distStr), "%.0f m", d);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
||||
}
|
||||
|
||||
float bearingToOther =
|
||||
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
|
||||
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
|
||||
@@ -1454,6 +1488,22 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
if (!config.display.compass_north_top)
|
||||
bearingToOther -= myHeading;
|
||||
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
|
||||
|
||||
float bearingToOtherDegrees = (bearingToOther < 0) ? bearingToOther + 2 * PI : bearingToOther;
|
||||
bearingToOtherDegrees = bearingToOtherDegrees * 180 / PI;
|
||||
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
if (d < (2 * MILES_TO_FEET))
|
||||
snprintf(distStr, sizeof(distStr), "%.0fft %.0f°", d * METERS_TO_FEET, bearingToOtherDegrees);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1fmi %.0f°", d * METERS_TO_FEET / MILES_TO_FEET,
|
||||
bearingToOtherDegrees);
|
||||
} else {
|
||||
if (d < 2000)
|
||||
snprintf(distStr, sizeof(distStr), "%.0fm %.0f°", d, bearingToOtherDegrees);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1fkm %.0f°", d / 1000, bearingToOtherDegrees);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasNodeHeading) {
|
||||
@@ -1658,6 +1708,10 @@ void Screen::setup()
|
||||
// Set the utf8 conversion function
|
||||
dispdev->setFontTableLookupFunction(customFontTableLookup);
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
logo_timeout *= 2; // Double the time if we have a custom logo
|
||||
#endif
|
||||
|
||||
// Add frames.
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
|
||||
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||
@@ -1803,6 +1857,22 @@ int32_t Screen::runOnce()
|
||||
showingBootScreen = false;
|
||||
}
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
static bool showingOEMBootScreen = true;
|
||||
if (showingOEMBootScreen && (millis() > ((logo_timeout / 2) + serialSinceMsec))) {
|
||||
LOG_INFO("Switch to OEM screen...");
|
||||
// Change frames.
|
||||
static FrameCallback bootOEMFrames[] = {drawOEMBootScreen};
|
||||
static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]);
|
||||
ui->setFrames(bootOEMFrames, bootOEMFrameCount);
|
||||
ui->update();
|
||||
#ifndef USE_EINK
|
||||
ui->update();
|
||||
#endif
|
||||
showingOEMBootScreen = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_WELCOME_UNSET
|
||||
if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||
setWelcomeFrames();
|
||||
@@ -2578,13 +2648,12 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
display->drawString(x + 1, y, String("USB"));
|
||||
}
|
||||
|
||||
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
|
||||
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
|
||||
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
|
||||
if (config.display.heading_bold)
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
|
||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
|
||||
// if (config.display.heading_bold)
|
||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
|
||||
|
||||
// Line 2
|
||||
uint32_t currentMillis = millis();
|
||||
uint32_t seconds = currentMillis / 1000;
|
||||
uint32_t minutes = seconds / 60;
|
||||
@@ -2597,6 +2666,9 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
|
||||
display->setColor(WHITE);
|
||||
|
||||
// Setup string to assemble analogClock string
|
||||
std::string analogClock = "";
|
||||
|
||||
// Show uptime as days, hours, minutes OR seconds
|
||||
std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds);
|
||||
|
||||
@@ -2613,17 +2685,36 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||||
|
||||
char timebuf[10];
|
||||
snprintf(timebuf, sizeof(timebuf), " %02d:%02d:%02d", hour, min, sec);
|
||||
uptime += timebuf;
|
||||
char timebuf[12];
|
||||
|
||||
if (config.display.use_12h_clock) {
|
||||
std::string meridiem = "am";
|
||||
if (hour >= 12) {
|
||||
if (hour > 12)
|
||||
hour -= 12;
|
||||
meridiem = "pm";
|
||||
}
|
||||
if (hour == 00) {
|
||||
hour = 12;
|
||||
}
|
||||
snprintf(timebuf, sizeof(timebuf), "%d:%02d:%02d%s", hour, min, sec, meridiem.c_str());
|
||||
} else {
|
||||
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", hour, min, sec);
|
||||
}
|
||||
analogClock += timebuf;
|
||||
}
|
||||
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, uptime.c_str());
|
||||
// Line 1
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
||||
|
||||
// Line 2
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, analogClock.c_str());
|
||||
|
||||
// Display Channel Utilization
|
||||
char chUtil[13];
|
||||
snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent());
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil);
|
||||
|
||||
#if HAS_GPS
|
||||
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||
// Line 3
|
||||
@@ -2756,4 +2847,4 @@ int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg)
|
||||
} // namespace graphics
|
||||
#else
|
||||
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
|
||||
#endif // HAS_SCREEN
|
||||
#endif // HAS_SCREEN
|
||||
@@ -278,6 +278,10 @@ class Screen : public concurrency::OSThread
|
||||
bool hasHeading() { return hasCompass; }
|
||||
|
||||
long getHeading() { return compassHeading; }
|
||||
|
||||
void setEndCalibration(uint32_t _endCalibrationAt) { endCalibrationAt = _endCalibrationAt; }
|
||||
uint32_t getEndCalibration() { return endCalibrationAt; }
|
||||
|
||||
// functions for display brightness
|
||||
void increaseBrightness();
|
||||
void decreaseBrightness();
|
||||
@@ -673,6 +677,8 @@ class Screen : public concurrency::OSThread
|
||||
|
||||
bool hasCompass = false;
|
||||
float compassHeading;
|
||||
uint32_t endCalibrationAt;
|
||||
|
||||
/// Holds state for debug information
|
||||
DebugInfo debugInfo;
|
||||
|
||||
|
||||
@@ -16,63 +16,61 @@
|
||||
#include "graphics/fonts/OLEDDisplayFontsCS.h"
|
||||
#endif
|
||||
|
||||
#ifdef OLED_PL
|
||||
#define FONT_SMALL_LOCAL ArialMT_Plain_10_PL
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_SMALL_LOCAL ArialMT_Plain_10_RU
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_SMALL_LOCAL ArialMT_Plain_10_UA // Height: 13
|
||||
#else
|
||||
#ifdef OLED_CS
|
||||
#define FONT_SMALL_LOCAL ArialMT_Plain_10_CS
|
||||
#else
|
||||
#define FONT_SMALL_LOCAL ArialMT_Plain_10 // Height: 13
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#ifdef OLED_PL
|
||||
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_PL // Height: 19
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_UA // Height: 19
|
||||
#else
|
||||
#ifdef OLED_CS
|
||||
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_CS
|
||||
#else
|
||||
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16 // Height: 19
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#ifdef OLED_PL
|
||||
#define FONT_LARGE_LOCAL ArialMT_Plain_24_PL // Height: 28
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_LARGE_LOCAL ArialMT_Plain_24_UA // Height: 28
|
||||
#else
|
||||
#ifdef OLED_CS
|
||||
#define FONT_LARGE_LOCAL ArialMT_Plain_24_CS // Height: 28
|
||||
#else
|
||||
#define FONT_LARGE_LOCAL ArialMT_Plain_24 // Height: 28
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
// The screen is bigger so use bigger fonts
|
||||
#ifdef OLED_PL
|
||||
#define FONT_SMALL ArialMT_Plain_16_PL // Height: 19
|
||||
#define FONT_MEDIUM ArialMT_Plain_24_PL // Height: 28
|
||||
#define FONT_LARGE ArialMT_Plain_24_PL // Height: 28
|
||||
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
|
||||
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
|
||||
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_16 // Height: 19
|
||||
#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28
|
||||
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
|
||||
#endif
|
||||
#else
|
||||
#ifdef OLED_PL
|
||||
#define FONT_SMALL ArialMT_Plain_10_PL
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_SMALL ArialMT_Plain_10_RU
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_SMALL ArialMT_Plain_10_UA // Height: 13
|
||||
#else
|
||||
#ifdef OLED_CS
|
||||
#define FONT_SMALL ArialMT_Plain_10_CS
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10 // Height: 13
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#ifdef OLED_PL
|
||||
#define FONT_MEDIUM ArialMT_Plain_16_PL // Height: 19
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_MEDIUM ArialMT_Plain_16_UA // Height: 19
|
||||
#else
|
||||
#ifdef OLED_CS
|
||||
#define FONT_MEDIUM ArialMT_Plain_16_CS
|
||||
#else
|
||||
#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#ifdef OLED_PL
|
||||
#define FONT_LARGE ArialMT_Plain_24_PL // Height: 28
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_LARGE ArialMT_Plain_24_UA // Height: 28
|
||||
#else
|
||||
#ifdef OLED_CS
|
||||
#define FONT_LARGE ArialMT_Plain_24_CS // Height: 28
|
||||
#else
|
||||
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
|
||||
#define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19
|
||||
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
|
||||
#endif
|
||||
|
||||
#define _fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
#include "ScanAndSelect.h"
|
||||
#include "modules/CannedMessageModule.h"
|
||||
#include <Throttle.h>
|
||||
#ifdef ARCH_PORTDUINO // Only to check for pin conflict with user button
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
// Config
|
||||
static const char name[] = "scanAndSelect"; // should match "allow input source" string
|
||||
@@ -30,7 +33,9 @@ bool ScanAndSelectInput::init()
|
||||
if (strcasecmp(moduleConfig.canned_message.allow_input_source, name) != 0)
|
||||
return false;
|
||||
|
||||
// Use any available inputbroker pin as the button
|
||||
// Determine which pin to use for the single scan-and-select button
|
||||
// User can specify this by setting any of the inputbroker pins
|
||||
// If all values are zero, we'll assume the user *does* want GPIO0
|
||||
if (moduleConfig.canned_message.inputbroker_pin_press)
|
||||
pin = moduleConfig.canned_message.inputbroker_pin_press;
|
||||
else if (moduleConfig.canned_message.inputbroker_pin_a)
|
||||
@@ -38,7 +43,25 @@ bool ScanAndSelectInput::init()
|
||||
else if (moduleConfig.canned_message.inputbroker_pin_b)
|
||||
pin = moduleConfig.canned_message.inputbroker_pin_b;
|
||||
else
|
||||
return false; // Short circuit: no button found
|
||||
pin = 0; // GPIO 0 then
|
||||
|
||||
// Short circuit: if selected pin conficts with the user button
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
int pinUserButton = 0;
|
||||
if (settingsMap.count(user) != 0) {
|
||||
pinUserButton = settingsMap[user];
|
||||
}
|
||||
#elif defined(USERPREFS_BUTTON_PIN)
|
||||
int pinUserButton = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN;
|
||||
#elif defined(BUTTON_PIN)
|
||||
int pinUserButton = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
|
||||
#else
|
||||
int pinUserButton = config.device.button_gpio;
|
||||
#endif
|
||||
if (pin == pinUserButton) {
|
||||
LOG_ERROR("ScanAndSelect conflict with user button");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set-up the button
|
||||
pinMode(pin, INPUT_PULLUP);
|
||||
|
||||
43
src/main.cpp
43
src/main.cpp
@@ -115,10 +115,6 @@ AccelerometerThread *accelerometerThread = nullptr;
|
||||
AudioThread *audioThread = nullptr;
|
||||
#endif
|
||||
|
||||
#if defined(TCXO_OPTIONAL)
|
||||
float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if TCXO is optional, put this here so it can be changed further down.
|
||||
#endif
|
||||
|
||||
using namespace concurrency;
|
||||
|
||||
volatile static const char slipstreamTZString[] = USERPREFS_TZ_STRING;
|
||||
@@ -610,6 +606,7 @@ void setup()
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::CGRADSENS, meshtastic_TelemetrySensorType_RADSENS);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN, meshtastic_TelemetrySensorType_DFROBOT_RAIN);
|
||||
|
||||
i2cScanner.reset();
|
||||
#endif
|
||||
@@ -647,9 +644,9 @@ void setup()
|
||||
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
|
||||
nodeDB = new NodeDB;
|
||||
|
||||
// If we're taking on the repeater role, use flood router and turn off 3V3_S rail because peripherals are not needed
|
||||
// If we're taking on the repeater role, use NextHopRouter and turn off 3V3_S rail because peripherals are not needed
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
|
||||
router = new FloodingRouter();
|
||||
router = new NextHopRouter();
|
||||
#ifdef PIN_3V3_EN
|
||||
digitalWrite(PIN_3V3_EN, LOW);
|
||||
#endif
|
||||
@@ -927,13 +924,16 @@ void setup()
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
#endif
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
delete sxIf;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success");
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
@@ -941,29 +941,28 @@ void setup()
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// Try using the specified TCXO voltage
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with TCXO, Vref %f V", tcxoVoltage);
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
tcxoVoltage = 0; // if it fails, set the TCXO voltage to zero for the next attempt
|
||||
// try using the specified TCXO voltage
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
delete sxIf;
|
||||
} else {
|
||||
LOG_WARN("SX1262 init success, TCXO, Vref %f V", tcxoVoltage);
|
||||
LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instea
|
||||
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instead
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with XTAL, Vref %f V", tcxoVoltage);
|
||||
LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if it fails, set the TCXO voltage back for the next radio search
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success, XTAL, Vref %f V", tcxoVoltage);
|
||||
LOG_INFO("SX1262 init success, XTAL, Vref 0.0V");
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
|
||||
auth = bytesOut + numBytes;
|
||||
memcpy((uint8_t *)(auth + 8), &extraNonceTmp,
|
||||
sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp;
|
||||
LOG_INFO("Random nonce value: %d", extraNonceTmp);
|
||||
LOG_DEBUG("Random nonce value: %d", extraNonceTmp);
|
||||
if (remotePublic.size == 0) {
|
||||
LOG_DEBUG("Node %d or their public_key not found", toNode);
|
||||
return false;
|
||||
|
||||
@@ -13,7 +13,8 @@ FloodingRouter::FloodingRouter() {}
|
||||
ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p)
|
||||
{
|
||||
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
|
||||
wasSeenRecently(p); // FIXME, move this to a sniffSent method
|
||||
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
|
||||
wasSeenRecently(p); // FIXME, move this to a sniffSent method
|
||||
|
||||
return Router::send(p);
|
||||
}
|
||||
@@ -23,26 +24,17 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
|
||||
printPacket("Ignore dupe incoming msg", p);
|
||||
rxDupe++;
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
|
||||
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
|
||||
if (Router::cancelSending(p->from, p->id))
|
||||
txRelayCanceled++;
|
||||
}
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
|
||||
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
|
||||
}
|
||||
|
||||
/* If the original transmitter is doing retransmissions (hopStart equals hopLimit) for a reliable transmission, e.g., when
|
||||
the ACK got lost, we will handle the packet again to make sure it gets an ACK to its packet. */
|
||||
the ACK got lost, we will handle the packet again to make sure it gets an implicit ACK. */
|
||||
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
|
||||
if (isRepeated) {
|
||||
LOG_DEBUG("Repeated reliable tx");
|
||||
if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
|
||||
// FIXME - channel index should be used, but the packet is still encrypted here
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, 0, 0);
|
||||
}
|
||||
// Check if it's still in the Tx queue, if not, we have to relay it again
|
||||
if (!findInTxQueue(p->from, p->id))
|
||||
perhapsRebroadcast(p);
|
||||
} else {
|
||||
perhapsCancelDupe(p);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -51,13 +43,27 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
return Router::shouldFilterReceived(p);
|
||||
}
|
||||
|
||||
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
|
||||
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
|
||||
if (Router::cancelSending(p->from, p->id))
|
||||
txRelayCanceled++;
|
||||
}
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
|
||||
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
|
||||
}
|
||||
}
|
||||
|
||||
bool FloodingRouter::isRebroadcaster()
|
||||
{
|
||||
return config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE &&
|
||||
config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
|
||||
}
|
||||
|
||||
bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
|
||||
if (p->id != 0) {
|
||||
@@ -72,13 +78,12 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
tosend->hop_limit = 2;
|
||||
}
|
||||
#endif
|
||||
tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
|
||||
|
||||
LOG_INFO("Rebroadcast received floodmsg");
|
||||
// Note: we are careful to resend using the original senders node id
|
||||
// We are careful not to call our hooked version of send() - because we don't want to check this again
|
||||
Router::send(tosend);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
|
||||
}
|
||||
@@ -86,13 +91,12 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
LOG_DEBUG("Ignore 0 id broadcast");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
||||
{
|
||||
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0);
|
||||
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
|
||||
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
|
||||
if (isAckorReply && !isToUs(p) && !isBroadcast(p->to)) {
|
||||
// do not flood direct message that is ACKed or replied to
|
||||
LOG_DEBUG("Rxd an ACK/reply not for me, cancel rebroadcast");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "PacketHistory.h"
|
||||
#include "Router.h"
|
||||
|
||||
/**
|
||||
@@ -26,14 +25,11 @@
|
||||
Any entries in recentBroadcasts that are older than X seconds (longer than the
|
||||
max time a flood can take) will be discarded.
|
||||
*/
|
||||
class FloodingRouter : public Router, protected PacketHistory
|
||||
class FloodingRouter : public Router
|
||||
{
|
||||
private:
|
||||
bool isRebroadcaster();
|
||||
|
||||
/** Check if we should rebroadcast this packet, and do so if needed
|
||||
* @return true if rebroadcasted */
|
||||
bool perhapsRebroadcast(const meshtastic_MeshPacket *p);
|
||||
/* Check if we should rebroadcast this packet, and do so if needed */
|
||||
void perhapsRebroadcast(const meshtastic_MeshPacket *p);
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -62,4 +58,10 @@ class FloodingRouter : public Router, protected PacketHistory
|
||||
* Look for broadcasts we need to rebroadcast
|
||||
*/
|
||||
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
|
||||
|
||||
/* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */
|
||||
void perhapsCancelDupe(const meshtastic_MeshPacket *p);
|
||||
|
||||
// Return true if we are a rebroadcaster
|
||||
bool isRebroadcaster();
|
||||
};
|
||||
@@ -20,12 +20,18 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
|
||||
// specified (may be dangerous if using external PA and LR11x0 power config forgotten)
|
||||
#if ARCH_PORTDUINO
|
||||
#define LR1110_MAX_POWER settingsMap[lr1110_max_power]
|
||||
#endif
|
||||
#ifndef LR1110_MAX_POWER
|
||||
#define LR1110_MAX_POWER 22
|
||||
#endif
|
||||
|
||||
// the 2.4G part maxes at 13dBm
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
#define LR1120_MAX_POWER settingsMap[lr1120_max_power]
|
||||
#endif
|
||||
#ifndef LR1120_MAX_POWER
|
||||
#define LR1120_MAX_POWER 13
|
||||
#endif
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
std::vector<MeshModule *> *MeshModule::modules;
|
||||
|
||||
const meshtastic_MeshPacket *MeshModule::currentRequest;
|
||||
uint8_t MeshModule::numPeriodicModules = 0;
|
||||
|
||||
/**
|
||||
* If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow
|
||||
@@ -35,6 +36,15 @@ MeshModule::~MeshModule()
|
||||
modules->erase(it);
|
||||
}
|
||||
|
||||
// ⚠️ **Only call once** to set the initial delay before a module starts broadcasting periodically
|
||||
int32_t MeshModule::setStartDelay()
|
||||
{
|
||||
int32_t startDelay = MESHMODULE_MIN_BROADCAST_DELAY_MS + numPeriodicModules * MESHMODULE_BROADCAST_SPACING_MS;
|
||||
numPeriodicModules++;
|
||||
|
||||
return startDelay;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
|
||||
uint8_t hopLimit)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#include <OLEDDisplayUi.h>
|
||||
#endif
|
||||
|
||||
#define MESHMODULE_MIN_BROADCAST_DELAY_MS 30 * 1000 // Min. delay after boot before sending first broadcast by any module
|
||||
#define MESHMODULE_BROADCAST_SPACING_MS 15 * 1000 // Initial spacing between broadcasts of different modules
|
||||
|
||||
/** handleReceived return enumeration
|
||||
*
|
||||
* Use ProcessMessage::CONTINUE to allows other modules to process a message.
|
||||
@@ -119,6 +122,12 @@ class MeshModule
|
||||
*/
|
||||
static const meshtastic_MeshPacket *currentRequest;
|
||||
|
||||
// We keep track of the number of modules that send a periodic broadcast to schedule them spaced out over time
|
||||
static uint8_t numPeriodicModules;
|
||||
|
||||
// Set the start delay for module that broadcasts periodically
|
||||
int32_t setStartDelay();
|
||||
|
||||
/**
|
||||
* If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling.
|
||||
*/
|
||||
|
||||
@@ -117,6 +117,19 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool t
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Attempt to find a packet from this queue. Return true if it was found. */
|
||||
bool MeshPacketQueue::find(NodeNum from, PacketId id)
|
||||
{
|
||||
for (auto it = queue.begin(); it != queue.end(); it++) {
|
||||
auto p = (*it);
|
||||
if (getFrom(p) == from && p->id == id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find a lower-priority packet in the queue and replace it with the provided one.
|
||||
* @return True if the replacement succeeded, false otherwise
|
||||
|
||||
@@ -37,4 +37,7 @@ class MeshPacketQueue
|
||||
|
||||
/** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */
|
||||
meshtastic_MeshPacket *remove(NodeNum from, PacketId id, bool tx_normal = true, bool tx_late = true);
|
||||
|
||||
/* Attempt to find a packet from this queue. Return true if it was found. */
|
||||
bool find(NodeNum from, PacketId id);
|
||||
};
|
||||
@@ -173,7 +173,9 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
p.from = 0; // We don't let phones assign nodenums to their sent messages
|
||||
p.from = 0; // We don't let clients assign nodenums to their sent messages
|
||||
p.next_hop = NO_NEXT_HOP_PREFERENCE; // We don't let clients assign next_hop to their sent messages
|
||||
p.relay_node = NO_RELAY_NODE; // We don't let clients assign relay_node to their sent messages
|
||||
|
||||
if (p.id == 0)
|
||||
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
|
||||
|
||||
@@ -64,7 +64,8 @@ class MeshService
|
||||
return true;
|
||||
}
|
||||
return p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP ||
|
||||
p->decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP;
|
||||
p->decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP ||
|
||||
p->decoded.portnum == meshtastic_PortNum_ALERT_APP;
|
||||
}
|
||||
/// Called when some new packets have arrived from one of the radios
|
||||
Observable<uint32_t> fromNumChanged;
|
||||
|
||||
@@ -40,6 +40,11 @@ enum RxSource {
|
||||
/// We normally just use max 3 hops for sending reliable messages
|
||||
#define HOP_RELIABLE 3
|
||||
|
||||
// For old firmware or when falling back to flooding, there is no next-hop preference
|
||||
#define NO_NEXT_HOP_PREFERENCE 0
|
||||
// For old firmware there is no relay node set
|
||||
#define NO_RELAY_NODE 0
|
||||
|
||||
typedef int ErrorCode;
|
||||
|
||||
/// Alloc and free packets to our global, ISR safe pool
|
||||
|
||||
272
src/mesh/NextHopRouter.cpp
Normal file
272
src/mesh/NextHopRouter.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
#include "NextHopRouter.h"
|
||||
|
||||
NextHopRouter::NextHopRouter() {}
|
||||
|
||||
PendingPacket::PendingPacket(meshtastic_MeshPacket *p, uint8_t numRetransmissions)
|
||||
{
|
||||
packet = p;
|
||||
this->numRetransmissions = numRetransmissions - 1; // We subtract one, because we assume the user just did the first send
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet
|
||||
*/
|
||||
ErrorCode NextHopRouter::send(meshtastic_MeshPacket *p)
|
||||
{
|
||||
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
|
||||
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
|
||||
wasSeenRecently(p); // FIXME, move this to a sniffSent method
|
||||
|
||||
p->next_hop = getNextHop(p->to, p->relay_node); // set the next hop
|
||||
LOG_DEBUG("Setting next hop for packet with dest %x to %x", p->to, p->next_hop);
|
||||
|
||||
// If it's from us, ReliableRouter already handles retransmissions if want_ack is set. If a next hop is set and hop limit is
|
||||
// not 0 or want_ack is set, start retransmissions
|
||||
if ((!isFromUs(p) || !p->want_ack) && p->next_hop != NO_NEXT_HOP_PREFERENCE && (p->hop_limit > 0 || p->want_ack))
|
||||
startRetransmission(packetPool.allocCopy(*p)); // start retransmission for relayed packet
|
||||
|
||||
return Router::send(p);
|
||||
}
|
||||
|
||||
bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
bool wasFallback = false;
|
||||
bool weWereNextHop = false;
|
||||
if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
|
||||
printPacket("Ignore dupe incoming msg", p);
|
||||
rxDupe++;
|
||||
stopRetransmission(p->from, p->id);
|
||||
|
||||
// If it was a fallback to flooding, try to relay again
|
||||
if (wasFallback) {
|
||||
LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
|
||||
// Check if it's still in the Tx queue, if not, we have to relay it again
|
||||
if (!findInTxQueue(p->from, p->id))
|
||||
perhapsRelay(p);
|
||||
} else {
|
||||
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
|
||||
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
|
||||
if (isRepeated) {
|
||||
if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
||||
} else if (!weWereNextHop) {
|
||||
perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return Router::shouldFilterReceived(p);
|
||||
}
|
||||
|
||||
void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
||||
{
|
||||
NodeNum ourNodeNum = getNodeNum();
|
||||
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(ourNodeNum);
|
||||
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
|
||||
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
|
||||
if (isAckorReply) {
|
||||
// Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
|
||||
// not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
|
||||
if (p->from != 0) {
|
||||
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
|
||||
if (origTx) {
|
||||
// Either relayer of ACK was also a relayer of the packet, or we were the relayer and the ACK came directly from
|
||||
// the destination
|
||||
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
|
||||
(wasRelayer(ourRelayID, p->decoded.request_id, p->to) && p->hop_start != 0 && p->hop_start == p->hop_limit)) {
|
||||
if (origTx->next_hop != p->relay_node) { // Not already set
|
||||
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
|
||||
origTx->next_hop = p->relay_node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isToUs(p)) {
|
||||
Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM
|
||||
// stop retransmission for the original packet
|
||||
stopRetransmission(p->to, p->decoded.request_id); // for original packet, from = to and id = request_id
|
||||
}
|
||||
}
|
||||
|
||||
perhapsRelay(p);
|
||||
|
||||
// handle the packet as normal
|
||||
Router::sniffReceived(p, c);
|
||||
}
|
||||
|
||||
/* Check if we should be relaying this packet if so, do so. */
|
||||
bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
|
||||
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
|
||||
if (isRebroadcaster()) {
|
||||
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
|
||||
LOG_INFO("Relaying received message coming from %x", p->relay_node);
|
||||
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
NextHopRouter::send(tosend);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next hop for a destination, given the relay node
|
||||
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
|
||||
*/
|
||||
uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
|
||||
{
|
||||
// When we're a repeater router->sniffReceived will call NextHopRouter directly without checking for broadcast
|
||||
if (isBroadcast(to))
|
||||
return NO_NEXT_HOP_PREFERENCE;
|
||||
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(to);
|
||||
if (node && node->next_hop) {
|
||||
// We are careful not to return the relay node as the next hop
|
||||
if (node->next_hop != relay_node) {
|
||||
// LOG_DEBUG("Next hop for 0x%x is 0x%x", to, node->next_hop);
|
||||
return node->next_hop;
|
||||
} else
|
||||
LOG_WARN("Next hop for 0x%x is 0x%x, same as relayer; set no pref", to, node->next_hop);
|
||||
}
|
||||
return NO_NEXT_HOP_PREFERENCE;
|
||||
}
|
||||
|
||||
PendingPacket *NextHopRouter::findPendingPacket(GlobalPacketId key)
|
||||
{
|
||||
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
|
||||
if (old != pending.end()) {
|
||||
return &old->second;
|
||||
} else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any retransmissions we are doing of the specified node/packet ID pair
|
||||
*/
|
||||
bool NextHopRouter::stopRetransmission(NodeNum from, PacketId id)
|
||||
{
|
||||
auto key = GlobalPacketId(from, id);
|
||||
return stopRetransmission(key);
|
||||
}
|
||||
|
||||
bool NextHopRouter::stopRetransmission(GlobalPacketId key)
|
||||
{
|
||||
auto old = findPendingPacket(key);
|
||||
if (old) {
|
||||
auto p = old->packet;
|
||||
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
|
||||
to avoid canceling a transmission if it was ACKed super fast via MQTT */
|
||||
if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
|
||||
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
|
||||
cancelSending(getFrom(p), p->id);
|
||||
// now free the pooled copy for retransmission too
|
||||
packetPool.release(p);
|
||||
}
|
||||
auto numErased = pending.erase(key);
|
||||
assert(numErased == 1);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
||||
*/
|
||||
PendingPacket *NextHopRouter::startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx)
|
||||
{
|
||||
auto id = GlobalPacketId(p);
|
||||
auto rec = PendingPacket(p, numReTx);
|
||||
|
||||
stopRetransmission(getFrom(p), p->id);
|
||||
|
||||
setNextTx(&rec);
|
||||
pending[id] = rec;
|
||||
|
||||
return &pending[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||
*/
|
||||
int32_t NextHopRouter::doRetransmissions()
|
||||
{
|
||||
uint32_t now = millis();
|
||||
int32_t d = INT32_MAX;
|
||||
|
||||
// FIXME, we should use a better datastructure rather than walking through this map.
|
||||
// for(auto el: pending) {
|
||||
for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
|
||||
++nextIt; // we use this odd pattern because we might be deleting it...
|
||||
auto &p = it->second;
|
||||
|
||||
bool stillValid = true; // assume we'll keep this record around
|
||||
|
||||
// FIXME, handle 51 day rolloever here!!!
|
||||
if (p.nextTxMsec <= now) {
|
||||
if (p.numRetransmissions == 0) {
|
||||
if (isFromUs(p.packet)) {
|
||||
LOG_DEBUG("Reliable send failed, returning a nak for fr=0x%x,to=0x%x,id=0x%x", p.packet->from, p.packet->to,
|
||||
p.packet->id);
|
||||
sendAckNak(meshtastic_Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
|
||||
}
|
||||
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
|
||||
stopRetransmission(it->first);
|
||||
stillValid = false; // just deleted it
|
||||
} else {
|
||||
LOG_DEBUG("Sending retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d", p.packet->from, p.packet->to,
|
||||
p.packet->id, p.numRetransmissions);
|
||||
|
||||
if (!isBroadcast(p.packet->to)) {
|
||||
if (p.numRetransmissions == 1) {
|
||||
// Last retransmission, reset next_hop (fallback to FloodingRouter)
|
||||
p.packet->next_hop = NO_NEXT_HOP_PREFERENCE;
|
||||
// Also reset it in the nodeDB
|
||||
meshtastic_NodeInfoLite *sentTo = nodeDB->getMeshNode(p.packet->to);
|
||||
if (sentTo) {
|
||||
LOG_INFO("Resetting next hop for packet with dest 0x%x\n", p.packet->to);
|
||||
sentTo->next_hop = NO_NEXT_HOP_PREFERENCE;
|
||||
}
|
||||
FloodingRouter::send(packetPool.allocCopy(*p.packet));
|
||||
} else {
|
||||
NextHopRouter::send(packetPool.allocCopy(*p.packet));
|
||||
}
|
||||
} else {
|
||||
// Note: we call the superclass version because we don't want to have our version of send() add a new
|
||||
// retransmission record
|
||||
FloodingRouter::send(packetPool.allocCopy(*p.packet));
|
||||
}
|
||||
|
||||
// Queue again
|
||||
--p.numRetransmissions;
|
||||
setNextTx(&p);
|
||||
}
|
||||
}
|
||||
|
||||
if (stillValid) {
|
||||
// Update our desired sleep delay
|
||||
int32_t t = p.nextTxMsec - now;
|
||||
|
||||
d = min(t, d);
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void NextHopRouter::setNextTx(PendingPacket *pending)
|
||||
{
|
||||
assert(iface);
|
||||
auto d = iface->getRetransmissionMsec(pending->packet);
|
||||
pending->nextTxMsec = millis() + d;
|
||||
LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
|
||||
printPacket("", pending->packet);
|
||||
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
|
||||
}
|
||||
151
src/mesh/NextHopRouter.h
Normal file
151
src/mesh/NextHopRouter.h
Normal file
@@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include "FloodingRouter.h"
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* An identifier for a globally unique message - a pair of the sending nodenum and the packet id assigned
|
||||
* to that message
|
||||
*/
|
||||
struct GlobalPacketId {
|
||||
NodeNum node;
|
||||
PacketId id;
|
||||
|
||||
bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; }
|
||||
|
||||
explicit GlobalPacketId(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
node = getFrom(p);
|
||||
id = p->id;
|
||||
}
|
||||
|
||||
GlobalPacketId(NodeNum _from, PacketId _id)
|
||||
{
|
||||
node = _from;
|
||||
id = _id;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A packet queued for retransmission
|
||||
*/
|
||||
struct PendingPacket {
|
||||
meshtastic_MeshPacket *packet;
|
||||
|
||||
/** The next time we should try to retransmit this packet */
|
||||
uint32_t nextTxMsec = 0;
|
||||
|
||||
/** Starts at NUM_RETRANSMISSIONS -1 and counts down. Once zero it will be removed from the list */
|
||||
uint8_t numRetransmissions = 0;
|
||||
|
||||
PendingPacket() {}
|
||||
explicit PendingPacket(meshtastic_MeshPacket *p, uint8_t numRetransmissions);
|
||||
};
|
||||
|
||||
class GlobalPacketIdHashFunction
|
||||
{
|
||||
public:
|
||||
size_t operator()(const GlobalPacketId &p) const { return (std::hash<NodeNum>()(p.node)) ^ (std::hash<PacketId>()(p.id)); }
|
||||
};
|
||||
|
||||
/*
|
||||
Router for direct messages, which only relays if it is the next hop for a packet. The next hop is set by the current
|
||||
relayer of a packet, which bases this on information from a previous successful delivery to the destination via flooding.
|
||||
Namely, in the PacketHistory, we keep track of (up to 3) relayers of a packet. When the ACK is delivered back to us via a node
|
||||
that also relayed the original packet, we use that node as next hop for the destination from then on. This makes sure that only
|
||||
when there’s a two-way connection, we assign a next hop. Both the ReliableRouter and NextHopRouter will do retransmissions (the
|
||||
NextHopRouter only 1 time). For the final retry, if no one actually relayed the packet, it will reset the next hop in order to
|
||||
fall back to the FloodingRouter again. Note that thus also intermediate hops will do a single retransmission if the intended
|
||||
next-hop didn’t relay, in order to fix changes in the middle of the route.
|
||||
*/
|
||||
class NextHopRouter : public FloodingRouter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
*/
|
||||
NextHopRouter();
|
||||
|
||||
/**
|
||||
* Send a packet
|
||||
* @return an error code
|
||||
*/
|
||||
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
|
||||
|
||||
/** Do our retransmission handling */
|
||||
virtual int32_t runOnce() override
|
||||
{
|
||||
// Note: We must doRetransmissions FIRST, because it might queue up work for the base class runOnce implementation
|
||||
doRetransmissions();
|
||||
|
||||
int32_t r = FloodingRouter::runOnce();
|
||||
|
||||
// Also after calling runOnce there might be new packets to retransmit
|
||||
auto d = doRetransmissions();
|
||||
return min(d, r);
|
||||
}
|
||||
|
||||
// The number of retransmissions intermediate nodes will do (actually 1 less than this)
|
||||
constexpr static uint8_t NUM_INTERMEDIATE_RETX = 2;
|
||||
// The number of retransmissions the original sender will do
|
||||
constexpr static uint8_t NUM_RELIABLE_RETX = 3;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Pending retransmissions
|
||||
*/
|
||||
std::unordered_map<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> pending;
|
||||
|
||||
/**
|
||||
* Should this incoming filter be dropped?
|
||||
*
|
||||
* Called immediately on reception, before any further processing.
|
||||
* @return true to abandon the packet
|
||||
*/
|
||||
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
|
||||
|
||||
/**
|
||||
* Look for packets we need to relay
|
||||
*/
|
||||
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
|
||||
|
||||
/**
|
||||
* Try to find the pending packet record for this ID (or NULL if not found)
|
||||
*/
|
||||
PendingPacket *findPendingPacket(NodeNum from, PacketId id) { return findPendingPacket(GlobalPacketId(from, id)); }
|
||||
PendingPacket *findPendingPacket(GlobalPacketId p);
|
||||
|
||||
/**
|
||||
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
||||
*/
|
||||
PendingPacket *startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx = NUM_INTERMEDIATE_RETX);
|
||||
|
||||
/**
|
||||
* Stop any retransmissions we are doing of the specified node/packet ID pair
|
||||
*
|
||||
* @return true if we found and removed a transmission with this ID
|
||||
*/
|
||||
bool stopRetransmission(NodeNum from, PacketId id);
|
||||
bool stopRetransmission(GlobalPacketId p);
|
||||
|
||||
/**
|
||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||
*
|
||||
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
|
||||
*/
|
||||
int32_t doRetransmissions();
|
||||
|
||||
void setNextTx(PendingPacket *pending);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Get the next hop for a destination, given the relay node
|
||||
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
|
||||
*/
|
||||
uint8_t getNextHop(NodeNum to, uint8_t relay_node);
|
||||
|
||||
/** Check if we should be relaying this packet if so, do so.
|
||||
* @return true if we did relay */
|
||||
bool perhapsRelay(const meshtastic_MeshPacket *p);
|
||||
};
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "modules/NeighborInfoModule.h"
|
||||
#include <ErriezCRC32.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
#include <vector>
|
||||
@@ -197,9 +196,8 @@ NodeDB::NodeDB()
|
||||
uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile));
|
||||
|
||||
int saveWhat = 0;
|
||||
// bool hasUniqueId = false;
|
||||
// Get device unique id
|
||||
#if defined(ARCH_ESP32) && defined(ESP_EFUSE_OPTIONAL_UNIQUE_ID)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
uint32_t unique_id[4];
|
||||
// ESP32 factory burns a unique id in efuse for S2+ series and evidently C3+ series
|
||||
// This is used for HMACs in the esp-rainmaker AIOT platform and seems to be a good choice for us
|
||||
@@ -207,7 +205,6 @@ NodeDB::NodeDB()
|
||||
if (err == ESP_OK) {
|
||||
memcpy(myNodeInfo.device_id.bytes, unique_id, sizeof(unique_id));
|
||||
myNodeInfo.device_id.size = 16;
|
||||
hasUniqueId = true;
|
||||
} else {
|
||||
LOG_WARN("Failed to read unique id from efuse");
|
||||
}
|
||||
@@ -221,12 +218,12 @@ NodeDB::NodeDB()
|
||||
memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end));
|
||||
myNodeInfo.device_id.size = 16;
|
||||
// Uncomment below to print the device id
|
||||
// hasUniqueId = true;
|
||||
|
||||
#else
|
||||
// FIXME - implement for other platforms
|
||||
#endif
|
||||
|
||||
// if (hasUniqueId) {
|
||||
// if (myNodeInfo.device_id.size == 16) {
|
||||
// std::string deviceIdHex;
|
||||
// for (size_t i = 0; i < myNodeInfo.device_id.size; ++i) {
|
||||
// char buf[3];
|
||||
@@ -410,7 +407,7 @@ bool NodeDB::resetRadioConfig(bool factory_reset)
|
||||
rebootAtMsec = millis() + (5 * 1000);
|
||||
}
|
||||
|
||||
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3)) && defined(HAS_TFT)
|
||||
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3)) && HAS_TFT
|
||||
// as long as PhoneAPI shares BT and TFT app switch BT off
|
||||
config.bluetooth.enabled = false;
|
||||
if (moduleConfig.external_notification.nag_timeout == 60)
|
||||
@@ -1531,4 +1528,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co
|
||||
LOG_ERROR("A critical failure occurred, portduino is exiting");
|
||||
exit(2);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,9 @@ class NodeDB
|
||||
/// @return our node number
|
||||
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
|
||||
|
||||
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
|
||||
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }
|
||||
|
||||
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use
|
||||
// bool handleWantNodeNum(NodeNum n);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ PacketHistory::PacketHistory()
|
||||
/**
|
||||
* Update recentBroadcasts and return true if we have already seen this packet
|
||||
*/
|
||||
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate)
|
||||
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate, bool *wasFallback, bool *weWereNextHop)
|
||||
{
|
||||
if (p->id == 0) {
|
||||
LOG_DEBUG("Ignore message with zero id");
|
||||
@@ -27,6 +27,9 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
|
||||
r.id = p->id;
|
||||
r.sender = getFrom(p);
|
||||
r.rxTimeMsec = millis();
|
||||
r.next_hop = p->next_hop;
|
||||
r.relayed_by[0] = p->relay_node;
|
||||
// LOG_INFO("Add relayed_by 0x%x for id=0x%x", p->relay_node, r.id);
|
||||
|
||||
auto found = recentPackets.find(r);
|
||||
bool seenRecently = (found != recentPackets.end()); // found not equal to .end() means packet was seen recently
|
||||
@@ -40,14 +43,36 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
|
||||
|
||||
if (seenRecently) {
|
||||
LOG_DEBUG("Found existing packet record for fr=0x%x,to=0x%x,id=0x%x", p->from, p->to, p->id);
|
||||
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum());
|
||||
if (wasFallback) {
|
||||
// If it was seen with a next-hop not set to us and now it's NO_NEXT_HOP_PREFERENCE, and the relayer relayed already
|
||||
// before, it's a fallback to flooding. If we didn't already relay and the next-hop neither, we might need to handle
|
||||
// it now.
|
||||
if (found->sender != nodeDB->getNodeNum() && found->next_hop != NO_NEXT_HOP_PREFERENCE &&
|
||||
found->next_hop != ourRelayID && p->next_hop == NO_NEXT_HOP_PREFERENCE && wasRelayer(p->relay_node, found) &&
|
||||
!wasRelayer(ourRelayID, found) && !wasRelayer(found->next_hop, found)) {
|
||||
*wasFallback = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we were the next hop for this packet
|
||||
if (weWereNextHop) {
|
||||
*weWereNextHop = found->next_hop == ourRelayID;
|
||||
}
|
||||
}
|
||||
|
||||
if (withUpdate) {
|
||||
if (found != recentPackets.end()) { // delete existing to updated timestamp (re-insert)
|
||||
recentPackets.erase(found); // as unsorted_set::iterator is const (can't update timestamp - so re-insert..)
|
||||
if (found != recentPackets.end()) { // delete existing to updated timestamp and relayed_by (re-insert)
|
||||
// Add the existing relayed_by to the new record
|
||||
for (uint8_t i = 0; i < NUM_RELAYERS - 1; i++) {
|
||||
if (found->relayed_by[i])
|
||||
r.relayed_by[i + 1] = found->relayed_by[i];
|
||||
}
|
||||
r.next_hop = found->next_hop; // keep the original next_hop (such that we check whether we were originally asked)
|
||||
recentPackets.erase(found); // as unsorted_set::iterator is const (can't update - so re-insert..)
|
||||
}
|
||||
recentPackets.insert(r);
|
||||
printPacket("Add packet record", p);
|
||||
LOG_DEBUG("Add packet record fr=0x%x, id=0x%x", p->from, p->id);
|
||||
}
|
||||
|
||||
// Capacity is reerved, so only purge expired packets if recentPackets fills past 90% capacity
|
||||
@@ -75,4 +100,59 @@ void PacketHistory::clearExpiredRecentPackets()
|
||||
}
|
||||
|
||||
LOG_DEBUG("recentPackets size=%ld (after clearing expired packets)", recentPackets.size());
|
||||
}
|
||||
|
||||
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
|
||||
* @return true if node was indeed a relayer, false if not */
|
||||
bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
|
||||
{
|
||||
if (relayer == 0)
|
||||
return false;
|
||||
|
||||
PacketRecord r = {.sender = sender, .id = id, .rxTimeMsec = 0, .next_hop = 0};
|
||||
auto found = recentPackets.find(r);
|
||||
|
||||
if (found == recentPackets.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return wasRelayer(relayer, found);
|
||||
}
|
||||
|
||||
/* Check if a certain node was a relayer of a packet in the history given iterator
|
||||
* @return true if node was indeed a relayer, false if not */
|
||||
bool PacketHistory::wasRelayer(const uint8_t relayer, std::unordered_set<PacketRecord, PacketRecordHashFunction>::iterator r)
|
||||
{
|
||||
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
|
||||
if (r->relayed_by[i] == relayer) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
|
||||
void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
|
||||
{
|
||||
PacketRecord r = {.sender = sender, .id = id, .rxTimeMsec = 0, .next_hop = 0};
|
||||
auto found = recentPackets.find(r);
|
||||
|
||||
if (found == recentPackets.end()) {
|
||||
return;
|
||||
}
|
||||
// Make a copy of the found record
|
||||
r.next_hop = found->next_hop;
|
||||
r.rxTimeMsec = found->rxTimeMsec;
|
||||
|
||||
// Only add the relayers that are not the one we want to remove
|
||||
uint8_t j = 0;
|
||||
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
|
||||
if (found->relayed_by[i] != relayer) {
|
||||
r.relayed_by[j] = found->relayed_by[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
recentPackets.erase(found);
|
||||
recentPackets.insert(r);
|
||||
}
|
||||
@@ -1,10 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "Router.h"
|
||||
#include "NodeDB.h"
|
||||
#include <unordered_set>
|
||||
|
||||
/// We clear our old flood record 10 minutes after we see the last of it
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
#define FLOOD_EXPIRE_TIME (5 * 1000L) // Don't allow too many packets to accumulate when fuzzing.
|
||||
#else
|
||||
#define FLOOD_EXPIRE_TIME (10 * 60 * 1000L)
|
||||
#endif
|
||||
|
||||
#define NUM_RELAYERS \
|
||||
3 // Number of relayer we keep track of. Use 3 to be efficient with memory alignment of PacketRecord to 16 bytes
|
||||
|
||||
/**
|
||||
* A record of a recent message broadcast
|
||||
@@ -12,7 +19,9 @@
|
||||
struct PacketRecord {
|
||||
NodeNum sender;
|
||||
PacketId id;
|
||||
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it
|
||||
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it
|
||||
uint8_t next_hop; // The next hop asked for this packet
|
||||
uint8_t relayed_by[NUM_RELAYERS]; // Array of nodes that relayed this packet
|
||||
|
||||
bool operator==(const PacketRecord &p) const { return sender == p.sender && id == p.id; }
|
||||
};
|
||||
@@ -40,6 +49,20 @@ class PacketHistory
|
||||
* Update recentBroadcasts and return true if we have already seen this packet
|
||||
*
|
||||
* @param withUpdate if true and not found we add an entry to recentPackets
|
||||
* @param wasFallback if not nullptr, packet will be checked for fallback to flooding and value will be set to true if so
|
||||
* @param weWereNextHop if not nullptr, packet will be checked for us being the next hop and value will be set to true if so
|
||||
*/
|
||||
bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true);
|
||||
};
|
||||
bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true, bool *wasFallback = nullptr,
|
||||
bool *weWereNextHop = nullptr);
|
||||
|
||||
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
|
||||
* @return true if node was indeed a relayer, false if not */
|
||||
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
|
||||
|
||||
/* Check if a certain node was a relayer of a packet in the history given iterator
|
||||
* @return true if node was indeed a relayer, false if not */
|
||||
bool wasRelayer(const uint8_t relayer, std::unordered_set<PacketRecord, PacketRecordHashFunction>::iterator r);
|
||||
|
||||
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
|
||||
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "PhoneAPI.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "Router.h"
|
||||
#include "SPILock.h"
|
||||
#include "TypeConversions.h"
|
||||
#include "main.h"
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#include "PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
#define RF95_MAX_POWER settingsMap[rf95_max_power]
|
||||
#endif
|
||||
#ifndef RF95_MAX_POWER
|
||||
#define RF95_MAX_POWER 20
|
||||
#endif
|
||||
@@ -337,4 +340,4 @@ bool RF95Interface::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -340,6 +340,10 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
||||
out += DEBUG_PORT.mt_sprintf(" via MQTT");
|
||||
if (p->hop_start != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" hopStart=%d", p->hop_start);
|
||||
if (p->next_hop != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" nextHop=0x%x", p->next_hop);
|
||||
if (p->relay_node != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" relay=0x%x", p->relay_node);
|
||||
if (p->priority != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" priority=%d", p->priority);
|
||||
|
||||
@@ -620,8 +624,8 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
|
||||
radioBuffer.header.to = p->to;
|
||||
radioBuffer.header.id = p->id;
|
||||
radioBuffer.header.channel = p->channel;
|
||||
radioBuffer.header.next_hop = 0; // *** For future use ***
|
||||
radioBuffer.header.relay_node = 0; // *** For future use ***
|
||||
radioBuffer.header.next_hop = p->next_hop;
|
||||
radioBuffer.header.relay_node = p->relay_node;
|
||||
if (p->hop_limit > HOP_MAX) {
|
||||
LOG_WARN("hop limit %d is too high, setting to %d", p->hop_limit, HOP_RELIABLE);
|
||||
p->hop_limit = HOP_RELIABLE;
|
||||
|
||||
@@ -38,10 +38,10 @@ typedef struct {
|
||||
/** The channel hash - used as a hint for the decoder to limit which channels we consider */
|
||||
uint8_t channel;
|
||||
|
||||
// ***For future use*** Last byte of the NodeNum of the next-hop for this packet
|
||||
// Last byte of the NodeNum of the next-hop for this packet
|
||||
uint8_t next_hop;
|
||||
|
||||
// ***For future use*** Last byte of the NodeNum of the node that will relay/relayed this packet
|
||||
// Last byte of the NodeNum of the node that will relay/relayed this packet
|
||||
uint8_t relay_node;
|
||||
} PacketHeader;
|
||||
|
||||
@@ -155,6 +155,9 @@ class RadioInterface
|
||||
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
|
||||
virtual bool cancelSending(NodeNum from, PacketId id) { return false; }
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
virtual bool findInTxQueue(NodeNum from, PacketId id) { return false; }
|
||||
|
||||
// methods from radiohead
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
|
||||
@@ -222,6 +222,12 @@ bool RadioLibInterface::cancelSending(NodeNum from, PacketId id)
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id)
|
||||
{
|
||||
return txQueue.find(from, id);
|
||||
}
|
||||
|
||||
/** radio helper thread callback.
|
||||
We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of
|
||||
'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision.
|
||||
@@ -340,8 +346,11 @@ void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id)
|
||||
meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false);
|
||||
if (p) {
|
||||
p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr);
|
||||
txQueue.enqueue(p);
|
||||
LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
|
||||
if (txQueue.enqueue(p)) {
|
||||
LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
|
||||
} else {
|
||||
packetPool.release(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,6 +451,9 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
mp->hop_start = (radioBuffer.header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
|
||||
mp->want_ack = !!(radioBuffer.header.flags & PACKET_FLAGS_WANT_ACK_MASK);
|
||||
mp->via_mqtt = !!(radioBuffer.header.flags & PACKET_FLAGS_VIA_MQTT_MASK);
|
||||
// If hop_start is not set, next_hop and relay_node are invalid (firmware <2.3)
|
||||
mp->next_hop = mp->hop_start == 0 ? NO_NEXT_HOP_PREFERENCE : radioBuffer.header.next_hop;
|
||||
mp->relay_node = mp->hop_start == 0 ? NO_RELAY_NODE : radioBuffer.header.relay_node;
|
||||
|
||||
addReceiveMetadata(mp);
|
||||
|
||||
|
||||
@@ -135,6 +135,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
|
||||
virtual bool cancelSending(NodeNum from, PacketId id) override;
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
|
||||
|
||||
private:
|
||||
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
|
||||
* doing the transmit */
|
||||
|
||||
@@ -23,7 +23,7 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
|
||||
}
|
||||
|
||||
auto copy = packetPool.allocCopy(*p);
|
||||
startRetransmission(copy);
|
||||
startRetransmission(copy, NUM_RELIABLE_RETX);
|
||||
}
|
||||
|
||||
/* If we have pending retransmissions, add the airtime of this packet to it, because during that time we cannot receive an
|
||||
@@ -35,7 +35,7 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
|
||||
}
|
||||
}
|
||||
|
||||
return FloodingRouter::send(p);
|
||||
return isBroadcast(p->to) ? FloodingRouter::send(p) : NextHopRouter::send(p);
|
||||
}
|
||||
|
||||
bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
@@ -73,7 +73,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
i->second.nextTxMsec += iface->getPacketTime(p);
|
||||
}
|
||||
|
||||
return FloodingRouter::shouldFilterReceived(p);
|
||||
return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,126 +138,5 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
||||
}
|
||||
|
||||
// handle the packet as normal
|
||||
FloodingRouter::sniffReceived(p, c);
|
||||
}
|
||||
|
||||
#define NUM_RETRANSMISSIONS 3
|
||||
|
||||
PendingPacket::PendingPacket(meshtastic_MeshPacket *p)
|
||||
{
|
||||
packet = p;
|
||||
numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send
|
||||
}
|
||||
|
||||
PendingPacket *ReliableRouter::findPendingPacket(GlobalPacketId key)
|
||||
{
|
||||
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
|
||||
if (old != pending.end()) {
|
||||
return &old->second;
|
||||
} else
|
||||
return NULL;
|
||||
}
|
||||
/**
|
||||
* Stop any retransmissions we are doing of the specified node/packet ID pair
|
||||
*/
|
||||
bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id)
|
||||
{
|
||||
auto key = GlobalPacketId(from, id);
|
||||
return stopRetransmission(key);
|
||||
}
|
||||
|
||||
bool ReliableRouter::stopRetransmission(GlobalPacketId key)
|
||||
{
|
||||
auto old = findPendingPacket(key);
|
||||
if (old) {
|
||||
auto p = old->packet;
|
||||
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
|
||||
to avoid canceling a transmission if it was ACKed super fast via MQTT */
|
||||
if (old->numRetransmissions < NUM_RETRANSMISSIONS - 1) {
|
||||
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
|
||||
cancelSending(getFrom(p), p->id);
|
||||
}
|
||||
// now free the pooled copy for retransmission too
|
||||
packetPool.release(p);
|
||||
auto numErased = pending.erase(key);
|
||||
assert(numErased == 1);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
||||
*/
|
||||
PendingPacket *ReliableRouter::startRetransmission(meshtastic_MeshPacket *p)
|
||||
{
|
||||
auto id = GlobalPacketId(p);
|
||||
auto rec = PendingPacket(p);
|
||||
|
||||
stopRetransmission(getFrom(p), p->id);
|
||||
|
||||
setNextTx(&rec);
|
||||
pending[id] = rec;
|
||||
|
||||
return &pending[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||
*/
|
||||
int32_t ReliableRouter::doRetransmissions()
|
||||
{
|
||||
uint32_t now = millis();
|
||||
int32_t d = INT32_MAX;
|
||||
|
||||
// FIXME, we should use a better datastructure rather than walking through this map.
|
||||
// for(auto el: pending) {
|
||||
for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
|
||||
++nextIt; // we use this odd pattern because we might be deleting it...
|
||||
auto &p = it->second;
|
||||
|
||||
bool stillValid = true; // assume we'll keep this record around
|
||||
|
||||
// FIXME, handle 51 day rollover here!!!
|
||||
if (p.nextTxMsec <= now) {
|
||||
if (p.numRetransmissions == 0) {
|
||||
LOG_DEBUG("Reliable send failed, return a nak for fr=0x%x,to=0x%x,id=0x%x", p.packet->from, p.packet->to,
|
||||
p.packet->id);
|
||||
sendAckNak(meshtastic_Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
|
||||
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
|
||||
stopRetransmission(it->first);
|
||||
stillValid = false; // just deleted it
|
||||
} else {
|
||||
LOG_DEBUG("Send reliable retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d", p.packet->from, p.packet->to,
|
||||
p.packet->id, p.numRetransmissions);
|
||||
|
||||
// Note: we call the superclass version because we don't want to have our version of send() add a new
|
||||
// retransmission record
|
||||
FloodingRouter::send(packetPool.allocCopy(*p.packet));
|
||||
|
||||
// Queue again
|
||||
--p.numRetransmissions;
|
||||
setNextTx(&p);
|
||||
}
|
||||
}
|
||||
|
||||
if (stillValid) {
|
||||
// Update our desired sleep delay
|
||||
int32_t t = p.nextTxMsec - now;
|
||||
|
||||
d = min(t, d);
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void ReliableRouter::setNextTx(PendingPacket *pending)
|
||||
{
|
||||
assert(iface);
|
||||
auto d = iface->getRetransmissionMsec(pending->packet);
|
||||
pending->nextTxMsec = millis() + d;
|
||||
LOG_DEBUG("Set next retransmission in %u msecs: ", d);
|
||||
printPacket("", pending->packet);
|
||||
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
|
||||
isBroadcast(p->to) ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c);
|
||||
}
|
||||
@@ -1,61 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "FloodingRouter.h"
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* An identifier for a globally unique message - a pair of the sending nodenum and the packet id assigned
|
||||
* to that message
|
||||
*/
|
||||
struct GlobalPacketId {
|
||||
NodeNum node;
|
||||
PacketId id;
|
||||
|
||||
bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; }
|
||||
|
||||
explicit GlobalPacketId(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
node = getFrom(p);
|
||||
id = p->id;
|
||||
}
|
||||
|
||||
GlobalPacketId(NodeNum _from, PacketId _id)
|
||||
{
|
||||
node = _from;
|
||||
id = _id;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A packet queued for retransmission
|
||||
*/
|
||||
struct PendingPacket {
|
||||
meshtastic_MeshPacket *packet;
|
||||
|
||||
/** The next time we should try to retransmit this packet */
|
||||
uint32_t nextTxMsec = 0;
|
||||
|
||||
/** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */
|
||||
uint8_t numRetransmissions = 0;
|
||||
|
||||
PendingPacket() {}
|
||||
explicit PendingPacket(meshtastic_MeshPacket *p);
|
||||
};
|
||||
|
||||
class GlobalPacketIdHashFunction
|
||||
{
|
||||
public:
|
||||
size_t operator()(const GlobalPacketId &p) const { return (std::hash<NodeNum>()(p.node)) ^ (std::hash<PacketId>()(p.id)); }
|
||||
};
|
||||
#include "NextHopRouter.h"
|
||||
|
||||
/**
|
||||
* This is a mixin that extends Router with the ability to do (one hop only) reliable message sends.
|
||||
*/
|
||||
class ReliableRouter : public FloodingRouter
|
||||
class ReliableRouter : public NextHopRouter
|
||||
{
|
||||
private:
|
||||
std::unordered_map<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> pending;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
@@ -70,54 +21,14 @@ class ReliableRouter : public FloodingRouter
|
||||
*/
|
||||
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
|
||||
|
||||
/** Do our retransmission handling */
|
||||
virtual int32_t runOnce() override
|
||||
{
|
||||
// Note: We must doRetransmissions FIRST, because it might queue up work for the base class runOnce implementation
|
||||
auto d = doRetransmissions();
|
||||
|
||||
int32_t r = FloodingRouter::runOnce();
|
||||
|
||||
return min(d, r);
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Look for acks/naks or someone retransmitting us
|
||||
*/
|
||||
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
|
||||
|
||||
/**
|
||||
* Try to find the pending packet record for this ID (or NULL if not found)
|
||||
*/
|
||||
PendingPacket *findPendingPacket(NodeNum from, PacketId id) { return findPendingPacket(GlobalPacketId(from, id)); }
|
||||
PendingPacket *findPendingPacket(GlobalPacketId p);
|
||||
|
||||
/**
|
||||
* We hook this method so we can see packets before FloodingRouter says they should be discarded
|
||||
*/
|
||||
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
|
||||
|
||||
/**
|
||||
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
||||
*/
|
||||
PendingPacket *startRetransmission(meshtastic_MeshPacket *p);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Stop any retransmissions we are doing of the specified node/packet ID pair
|
||||
*
|
||||
* @return true if we found and removed a transmission with this ID
|
||||
*/
|
||||
bool stopRetransmission(NodeNum from, PacketId id);
|
||||
bool stopRetransmission(GlobalPacketId p);
|
||||
|
||||
/**
|
||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||
*
|
||||
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
|
||||
*/
|
||||
int32_t doRetransmissions();
|
||||
|
||||
void setNextTx(PendingPacket *pending);
|
||||
};
|
||||
};
|
||||
@@ -249,6 +249,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
||||
// the lora we need to make sure we have replaced it with our local address
|
||||
p->from = getFrom(p);
|
||||
|
||||
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // set the relayer to us
|
||||
// If we are the original transmitter, set the hop limit with which we start
|
||||
if (isFromUs(p))
|
||||
p->hop_start = p->hop_limit;
|
||||
@@ -290,7 +291,18 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
||||
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
|
||||
bool Router::cancelSending(NodeNum from, PacketId id)
|
||||
{
|
||||
return iface ? iface->cancelSending(from, id) : false;
|
||||
if (iface && iface->cancelSending(from, id)) {
|
||||
// We are not a relayer of this packet anymore
|
||||
removeRelayer(nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum()), id, from);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
bool Router::findInTxQueue(NodeNum from, PacketId id)
|
||||
{
|
||||
return iface->findInTxQueue(from, id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "Observer.h"
|
||||
#include "PacketHistory.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
@@ -11,7 +12,7 @@
|
||||
/**
|
||||
* A mesh aware router that supports multiple interfaces.
|
||||
*/
|
||||
class Router : protected concurrency::OSThread
|
||||
class Router : protected concurrency::OSThread, protected PacketHistory
|
||||
{
|
||||
private:
|
||||
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
|
||||
@@ -50,6 +51,9 @@ class Router : protected concurrency::OSThread
|
||||
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
|
||||
bool cancelSending(NodeNum from, PacketId id);
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
bool findInTxQueue(NodeNum from, PacketId id);
|
||||
|
||||
/** Allocate and return a meshpacket which defaults as send to broadcast from the current node.
|
||||
* The returned packet is guaranteed to have a unique packet ID already assigned
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,9 @@ bool STM32WLE5JCInterface::init()
|
||||
{
|
||||
RadioLibInterface::init();
|
||||
|
||||
// https://github.com/Seeed-Studio/LoRaWan-E5-Node/blob/main/Middlewares/Third_Party/SubGHz_Phy/stm32_radio_driver/radio_driver.c
|
||||
setTCXOVoltage(1.7);
|
||||
|
||||
lora.setRfSwitchTable(rfswitch_pins, rfswitch_table);
|
||||
|
||||
if (power > STM32WLx_MAX_POWER) // This chip has lower power limits than some
|
||||
@@ -39,4 +42,4 @@ bool STM32WLE5JCInterface::init()
|
||||
return res == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
#endif // ARCH_STM32WL
|
||||
#endif // ARCH_STM32WL
|
||||
@@ -16,9 +16,6 @@ class STM32WLE5JCInterface : public SX126xInterface<STM32WLx>
|
||||
virtual bool init() override;
|
||||
};
|
||||
|
||||
// https://github.com/Seeed-Studio/LoRaWan-E5-Node/blob/main/Middlewares/Third_Party/SubGHz_Phy/stm32_radio_driver/radio_driver.c
|
||||
static const float tcxoVoltage = 1.7;
|
||||
|
||||
/* https://wiki.seeedstudio.com/LoRa-E5_STM32WLE5JC_Module/
|
||||
* Wio-E5 module ONLY transmits through RFO_HP
|
||||
* Receive: PA4=1, PA5=0
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
|
||||
// specified (may be dangerous if using external PA and SX126x power config forgotten)
|
||||
#if ARCH_PORTDUINO
|
||||
#define SX126X_MAX_POWER settingsMap[sx126x_max_power]
|
||||
#endif
|
||||
#ifndef SX126X_MAX_POWER
|
||||
#define SX126X_MAX_POWER 22
|
||||
#endif
|
||||
@@ -50,22 +53,13 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
#endif
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
float tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
|
||||
tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
|
||||
if (settingsMap[sx126x_ant_sw_pin] != RADIOLIB_NC) {
|
||||
digitalWrite(settingsMap[sx126x_ant_sw_pin], HIGH);
|
||||
pinMode(settingsMap[sx126x_ant_sw_pin], OUTPUT);
|
||||
}
|
||||
// FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE
|
||||
#elif !defined(SX126X_DIO3_TCXO_VOLTAGE)
|
||||
float tcxoVoltage =
|
||||
0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per
|
||||
// https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/SX126x/SX126x.h#L471C26-L471C104
|
||||
// (DIO3 is free to be used as an IRQ)
|
||||
#elif !defined(TCXO_OPTIONAL)
|
||||
float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE;
|
||||
// (DIO3 is not free to be used as an IRQ)
|
||||
#endif
|
||||
if (tcxoVoltage == 0)
|
||||
if (tcxoVoltage == 0.0)
|
||||
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
|
||||
else
|
||||
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
|
||||
@@ -83,7 +77,7 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
|
||||
// \todo Display actual typename of the adapter, not just `SX126x`
|
||||
LOG_INFO("SX126x init result %d", res);
|
||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND)
|
||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
|
||||
return false;
|
||||
|
||||
LOG_INFO("Frequency set to %f", getFreq());
|
||||
@@ -342,4 +336,4 @@ template <typename T> bool SX126xInterface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -28,8 +28,11 @@ template <class T> class SX126xInterface : public RadioLibInterface
|
||||
|
||||
bool isIRQPending() override { return lora.getIrqFlags() != 0; }
|
||||
|
||||
void setTCXOVoltage(float voltage) { tcxoVoltage = voltage; }
|
||||
|
||||
protected:
|
||||
float currentLimit = 140; // Higher OCP limit for SX126x PA
|
||||
float tcxoVoltage = 0.0;
|
||||
|
||||
/**
|
||||
* Specific module instance
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#endif
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do
|
||||
#if ARCH_PORTDUINO
|
||||
#define SX128X_MAX_POWER settingsMap[sx128x_max_power]
|
||||
#endif
|
||||
#ifndef SX128X_MAX_POWER
|
||||
#define SX128X_MAX_POWER 13
|
||||
#endif
|
||||
@@ -315,4 +318,4 @@ template <typename T> bool SX128xInterface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -74,11 +74,17 @@ template <class T> class TypedQueue
|
||||
{
|
||||
std::queue<T> q;
|
||||
concurrency::OSThread *reader = NULL;
|
||||
int maxElements;
|
||||
|
||||
public:
|
||||
explicit TypedQueue(int maxElements) {}
|
||||
explicit TypedQueue(int _maxElements) : maxElements(_maxElements) {}
|
||||
|
||||
int numFree() { return 1; } // Always claim 1 free, because we can grow to any size
|
||||
int numFree()
|
||||
{
|
||||
if (maxElements <= 0)
|
||||
return 1; // Always claim 1 free, because we can grow to any size
|
||||
return maxElements - numUsed();
|
||||
}
|
||||
|
||||
bool isEmpty() { return q.empty(); }
|
||||
|
||||
@@ -86,6 +92,9 @@ template <class T> class TypedQueue
|
||||
|
||||
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
||||
{
|
||||
if (numFree() <= 0)
|
||||
return false;
|
||||
|
||||
if (reader) {
|
||||
reader->setInterval(0);
|
||||
concurrency::mainDelay.interrupt();
|
||||
@@ -112,4 +121,4 @@ template <class T> class TypedQueue
|
||||
|
||||
void setReader(concurrency::OSThread *t) { reader = t; }
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
@@ -5,9 +5,6 @@
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "mesh/api/ethServerAPI.h"
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
#include "target_specific.h"
|
||||
#include <RAK13800_W5100S.h>
|
||||
#include <SPI.h>
|
||||
@@ -72,12 +69,6 @@ static int32_t reconnectETH()
|
||||
|
||||
ethStartupComplete = true;
|
||||
}
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
// FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected'
|
||||
if (mqtt && !moduleConfig.mqtt.proxy_to_client_enabled && !mqtt->isConnectedDirectly()) {
|
||||
mqtt->reconnect();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef DISABLE_NTP
|
||||
|
||||
@@ -468,6 +468,9 @@ typedef struct _meshtastic_Config_DisplayConfig {
|
||||
bool wake_on_tap_or_motion;
|
||||
/* Indicates how to rotate or invert the compass output to accurate display on the display. */
|
||||
meshtastic_Config_DisplayConfig_CompassOrientation compass_orientation;
|
||||
/* If false (default), the device will display the time in 24-hour format on screen.
|
||||
If true, the device will display the time in 12-hour format on screen. */
|
||||
bool use_12h_clock;
|
||||
} meshtastic_Config_DisplayConfig;
|
||||
|
||||
/* Lora Config */
|
||||
@@ -690,7 +693,7 @@ extern "C" {
|
||||
#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0}
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
|
||||
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN}
|
||||
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
|
||||
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
|
||||
#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
|
||||
#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
|
||||
@@ -701,7 +704,7 @@ extern "C" {
|
||||
#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0}
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
|
||||
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN}
|
||||
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
|
||||
#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
|
||||
#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
|
||||
#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
|
||||
@@ -765,6 +768,7 @@ extern "C" {
|
||||
#define meshtastic_Config_DisplayConfig_heading_bold_tag 9
|
||||
#define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
|
||||
#define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
|
||||
#define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
|
||||
#define meshtastic_Config_LoRaConfig_use_preset_tag 1
|
||||
#define meshtastic_Config_LoRaConfig_modem_preset_tag 2
|
||||
#define meshtastic_Config_LoRaConfig_bandwidth_tag 3
|
||||
@@ -907,7 +911,8 @@ X(a, STATIC, SINGULAR, UENUM, oled, 7) \
|
||||
X(a, STATIC, SINGULAR, UENUM, displaymode, 8) \
|
||||
X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \
|
||||
X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \
|
||||
X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11)
|
||||
X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11) \
|
||||
X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12)
|
||||
#define meshtastic_Config_DisplayConfig_CALLBACK NULL
|
||||
#define meshtastic_Config_DisplayConfig_DEFAULT NULL
|
||||
|
||||
@@ -985,7 +990,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
|
||||
#define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
|
||||
#define meshtastic_Config_BluetoothConfig_size 10
|
||||
#define meshtastic_Config_DeviceConfig_size 98
|
||||
#define meshtastic_Config_DisplayConfig_size 30
|
||||
#define meshtastic_Config_DisplayConfig_size 32
|
||||
#define meshtastic_Config_LoRaConfig_size 85
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_size 20
|
||||
#define meshtastic_Config_NetworkConfig_size 202
|
||||
|
||||
@@ -187,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
|
||||
#define meshtastic_LocalConfig_size 741
|
||||
#define meshtastic_LocalConfig_size 743
|
||||
#define meshtastic_LocalModuleConfig_size 699
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user