mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-24 11:40:27 +00:00
Compare commits
37 Commits
v2.5.19.d5
...
native-dfu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96a119cdd8 | ||
|
|
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 | ||
|
|
4cd2ba5479 | ||
|
|
f9876cfe9c | ||
|
|
85de193845 | ||
|
|
fb2c008c89 | ||
|
|
dd9ab7f0e1 |
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
|
||||
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 }}
|
||||
59
.github/workflows/hook_copr.yml
vendored
59
.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,49 +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 = {
|
||||
"event": "push",
|
||||
"payload": {
|
||||
"ref": "${{ github.ref }}",
|
||||
"after": "${{ github.sha }}",
|
||||
"repository": {
|
||||
"id": "${{ github.repository_id }}",
|
||||
"full_name": "${{ github.repository }}",
|
||||
"git_url": "${{ github.repositoryUrl }}",
|
||||
"owner": {
|
||||
"name": "${{ github.repository_owner }}"
|
||||
}
|
||||
},
|
||||
"pusher": {
|
||||
"name": "${{ github.actor }}"
|
||||
},
|
||||
"sender": {
|
||||
"login": "github-actions[bot]"
|
||||
}
|
||||
}
|
||||
}
|
||||
r = requests.post(webhook_url, json=copr_payload)
|
||||
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 }}
|
||||
|
||||
35
.github/workflows/main_matrix.yml
vendored
35
.github/workflows/main_matrix.yml
vendored
@@ -147,10 +147,37 @@ jobs:
|
||||
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: debian
|
||||
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
|
||||
|
||||
13
.github/workflows/release_channels.yml
vendored
13
.github/workflows/release_channels.yml
vendored
@@ -4,9 +4,18 @@ on:
|
||||
release:
|
||||
types: [published, released]
|
||||
|
||||
permissions: read-all
|
||||
permissions:
|
||||
contents: write
|
||||
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
|
||||
@@ -23,7 +32,7 @@ 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
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,6 +12,9 @@ web.tar
|
||||
*.code-workspace
|
||||
|
||||
.idea
|
||||
.platformio
|
||||
.local
|
||||
.cache
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
3
debian/changelog
vendored
3
debian/changelog
vendored
@@ -2,5 +2,6 @@ meshtasticd (2.5.20.0) UNRELEASED; urgency=medium
|
||||
|
||||
* Initial packaging
|
||||
* 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, 15 Jan 2025 14:08:54 +0000
|
||||
|
||||
@@ -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...7f13df0e5f
@@ -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(); }
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ 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;
|
||||
FSCom.remove(filename);
|
||||
return FSCom.open(filename, FILE_O_WRITE);
|
||||
#endif
|
||||
if (!fullAtomic)
|
||||
@@ -18,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);
|
||||
}
|
||||
|
||||
@@ -91,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";
|
||||
@@ -114,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;
|
||||
|
||||
|
||||
@@ -2662,14 +2662,13 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
|
||||
int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
||||
{
|
||||
if (showingNormalScreen) {
|
||||
// Outgoing message
|
||||
if (packet->from == 0)
|
||||
setFrames(FOCUS_PRESERVE); // Return to same frame (quietly hiding the rx text message frame)
|
||||
// If auto carousel is disabled -> return 0 and skip new messages handling
|
||||
if (config.display.auto_screen_carousel_secs == 0)
|
||||
return 0;
|
||||
|
||||
// Incoming message
|
||||
else
|
||||
setFrames(FOCUS_TEXTMESSAGE); // Focus on the new message
|
||||
// Handle focus change based on message type
|
||||
if (showingNormalScreen) {
|
||||
setFrames(packet->from == 0 ? FOCUS_PRESERVE : FOCUS_TEXTMESSAGE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -2756,4 +2755,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);
|
||||
|
||||
@@ -610,6 +610,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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -197,9 +197,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 +206,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 +219,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];
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
#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
|
||||
|
||||
/**
|
||||
* A record of a recent message broadcast
|
||||
|
||||
@@ -340,8 +340,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -223,6 +223,9 @@ typedef enum _meshtastic_HardwareModel {
|
||||
/* Mesh-Tab, esp32 based
|
||||
https://github.com/valzzu/Mesh-Tab */
|
||||
meshtastic_HardwareModel_MESH_TAB = 86,
|
||||
/* MeshLink board developed by LoraItalia. NRF52840, eByte E22900M22S (Will also come with other frequencies), 25w MPPT solar charger (5v,12v,18v selectable), support for gps, buzzer, oled or e-ink display, 10 gpios, hardware watchdog
|
||||
https://www.loraitalia.it */
|
||||
meshtastic_HardwareModel_MESHLINK = 87,
|
||||
/* ------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
------------------------------------------------------------------------------------------------------------------------------------------ */
|
||||
|
||||
@@ -81,7 +81,9 @@ typedef enum _meshtastic_TelemetrySensorType {
|
||||
/* ClimateGuard RadSens, radiation, Geiger-Muller Tube */
|
||||
meshtastic_TelemetrySensorType_RADSENS = 33,
|
||||
/* High accuracy current and voltage */
|
||||
meshtastic_TelemetrySensorType_INA226 = 34
|
||||
meshtastic_TelemetrySensorType_INA226 = 34,
|
||||
/* DFRobot Gravity tipping bucket rain gauge */
|
||||
meshtastic_TelemetrySensorType_DFROBOT_RAIN = 35
|
||||
} meshtastic_TelemetrySensorType;
|
||||
|
||||
/* Struct definitions */
|
||||
@@ -162,6 +164,12 @@ typedef struct _meshtastic_EnvironmentMetrics {
|
||||
/* Radiation in µR/h */
|
||||
bool has_radiation;
|
||||
float radiation;
|
||||
/* Rainfall in the last hour in mm */
|
||||
bool has_rainfall_1h;
|
||||
float rainfall_1h;
|
||||
/* Rainfall in the last 24 hours in mm */
|
||||
bool has_rainfall_24h;
|
||||
float rainfall_24h;
|
||||
} meshtastic_EnvironmentMetrics;
|
||||
|
||||
/* Power Metrics (voltage / current / etc) */
|
||||
@@ -306,8 +314,8 @@ extern "C" {
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_INA226
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_INA226+1))
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_DFROBOT_RAIN
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_DFROBOT_RAIN+1))
|
||||
|
||||
|
||||
|
||||
@@ -320,7 +328,7 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
@@ -328,7 +336,7 @@ extern "C" {
|
||||
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
|
||||
#define meshtastic_Nau7802Config_init_default {0, 0}
|
||||
#define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
@@ -360,6 +368,8 @@ extern "C" {
|
||||
#define meshtastic_EnvironmentMetrics_wind_gust_tag 16
|
||||
#define meshtastic_EnvironmentMetrics_wind_lull_tag 17
|
||||
#define meshtastic_EnvironmentMetrics_radiation_tag 18
|
||||
#define meshtastic_EnvironmentMetrics_rainfall_1h_tag 19
|
||||
#define meshtastic_EnvironmentMetrics_rainfall_24h_tag 20
|
||||
#define meshtastic_PowerMetrics_ch1_voltage_tag 1
|
||||
#define meshtastic_PowerMetrics_ch1_current_tag 2
|
||||
#define meshtastic_PowerMetrics_ch2_voltage_tag 3
|
||||
@@ -431,7 +441,9 @@ X(a, STATIC, OPTIONAL, FLOAT, wind_speed, 14) \
|
||||
X(a, STATIC, OPTIONAL, FLOAT, weight, 15) \
|
||||
X(a, STATIC, OPTIONAL, FLOAT, wind_gust, 16) \
|
||||
X(a, STATIC, OPTIONAL, FLOAT, wind_lull, 17) \
|
||||
X(a, STATIC, OPTIONAL, FLOAT, radiation, 18)
|
||||
X(a, STATIC, OPTIONAL, FLOAT, radiation, 18) \
|
||||
X(a, STATIC, OPTIONAL, FLOAT, rainfall_1h, 19) \
|
||||
X(a, STATIC, OPTIONAL, FLOAT, rainfall_24h, 20)
|
||||
#define meshtastic_EnvironmentMetrics_CALLBACK NULL
|
||||
#define meshtastic_EnvironmentMetrics_DEFAULT NULL
|
||||
|
||||
@@ -530,12 +542,12 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
|
||||
#define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size
|
||||
#define meshtastic_AirQualityMetrics_size 78
|
||||
#define meshtastic_DeviceMetrics_size 27
|
||||
#define meshtastic_EnvironmentMetrics_size 91
|
||||
#define meshtastic_EnvironmentMetrics_size 103
|
||||
#define meshtastic_HealthMetrics_size 11
|
||||
#define meshtastic_LocalStats_size 60
|
||||
#define meshtastic_Nau7802Config_size 16
|
||||
#define meshtastic_PowerMetrics_size 30
|
||||
#define meshtastic_Telemetry_size 98
|
||||
#define meshtastic_Telemetry_size 110
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -225,7 +225,7 @@ bool initWifi()
|
||||
#if !MESHTASTIC_EXCLUDE_WEBSERVER
|
||||
createSSLCert(); // For WebServer
|
||||
#endif
|
||||
esp_wifi_set_storage(WIFI_STORAGE_RAM); // Disable flash storage for WiFi credentials
|
||||
WiFi.persistent(false); // Disable flash storage for WiFi credentials
|
||||
#endif
|
||||
if (!*wifiPsw) // Treat empty password as no password
|
||||
wifiPsw = NULL;
|
||||
|
||||
@@ -352,7 +352,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
}
|
||||
case meshtastic_AdminMessage_enter_dfu_mode_request_tag: {
|
||||
LOG_INFO("Client requesting to enter DFU mode");
|
||||
#if defined(ARCH_NRF52) || defined(ARCH_RP2040)
|
||||
#if defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARDUINO_USB_CDC_ON_BOOT)
|
||||
enterDfuMode();
|
||||
#endif
|
||||
break;
|
||||
@@ -1160,4 +1160,4 @@ void disableBluetooth()
|
||||
nrf52Bluetooth->shutdown();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "Sensor/BMP280Sensor.h"
|
||||
#include "Sensor/BMP3XXSensor.h"
|
||||
#include "Sensor/CGRadSensSensor.h"
|
||||
#include "Sensor/DFRobotGravitySensor.h"
|
||||
#include "Sensor/DFRobotLarkSensor.h"
|
||||
#include "Sensor/LPS22HBSensor.h"
|
||||
#include "Sensor/MCP9808Sensor.h"
|
||||
@@ -56,6 +57,7 @@ RCWL9620Sensor rcwl9620Sensor;
|
||||
AHT10Sensor aht10Sensor;
|
||||
MLX90632Sensor mlx90632Sensor;
|
||||
DFRobotLarkSensor dfRobotLarkSensor;
|
||||
DFRobotGravitySensor dfRobotGravitySensor;
|
||||
NAU7802Sensor nau7802Sensor;
|
||||
BMP3XXSensor bmp3xxSensor;
|
||||
CGRadSensSensor cgRadSens;
|
||||
@@ -115,6 +117,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
#elif !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
|
||||
if (dfRobotLarkSensor.hasSensor())
|
||||
result = dfRobotLarkSensor.runOnce();
|
||||
if (dfRobotGravitySensor.hasSensor())
|
||||
result = dfRobotGravitySensor.runOnce();
|
||||
if (bmp085Sensor.hasSensor())
|
||||
result = bmp085Sensor.runOnce();
|
||||
if (bmp280Sensor.hasSensor())
|
||||
@@ -159,6 +163,12 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
result = max17048Sensor.runOnce();
|
||||
if (cgRadSens.hasSensor())
|
||||
result = cgRadSens.runOnce();
|
||||
// this only works on the wismesh hub with the solar option. This is not an I2C sensor, so we don't need the
|
||||
// sensormap here.
|
||||
#ifdef HAS_RAKPROT
|
||||
|
||||
result = rak9154Sensor.runOnce();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
@@ -278,8 +288,18 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
|
||||
static bool scrollingDown = true;
|
||||
static uint32_t lastScrollTime = millis();
|
||||
|
||||
// Draw up to 3 sensor data lines
|
||||
int linesToShow = min(3, sensorCount);
|
||||
// Determine how many lines we can fit on display
|
||||
// Calculated once only: display dimensions don't change during runtime.
|
||||
static int maxLines = 0;
|
||||
if (!maxLines) {
|
||||
const int16_t paddingTop = _fontHeight(FONT_SMALL); // Heading text
|
||||
const int16_t paddingBottom = 8; // Indicator dots
|
||||
maxLines = (display->getHeight() - paddingTop - paddingBottom) / _fontHeight(FONT_SMALL);
|
||||
assert(maxLines > 0);
|
||||
}
|
||||
|
||||
// Draw as many lines of data as we can fit
|
||||
int linesToShow = min(maxLines, sensorCount);
|
||||
for (int i = 0; i < linesToShow; i++) {
|
||||
int index = (scrollOffset + i) % sensorCount;
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), sensorData[index]);
|
||||
@@ -358,6 +378,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
|
||||
valid = valid && dfRobotLarkSensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (dfRobotGravitySensor.hasSensor()) {
|
||||
valid = valid && dfRobotGravitySensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (sht31Sensor.hasSensor()) {
|
||||
valid = valid && sht31Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
@@ -462,6 +486,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
|
||||
valid = valid && cgRadSens.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
#ifdef HAS_RAKPROT
|
||||
valid = valid && rak9154Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
#endif
|
||||
#endif
|
||||
return valid && hasSensor;
|
||||
}
|
||||
@@ -559,6 +587,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
|
||||
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||
return result;
|
||||
}
|
||||
if (dfRobotGravitySensor.hasSensor()) {
|
||||
result = dfRobotGravitySensor.handleAdminMessage(mp, request, response);
|
||||
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||
return result;
|
||||
}
|
||||
if (sht31Sensor.hasSensor()) {
|
||||
result = sht31Sensor.handleAdminMessage(mp, request, response);
|
||||
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||
|
||||
@@ -100,7 +100,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
|
||||
{
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
|
||||
if (lastMeasurementPacket == nullptr) {
|
||||
// In case of no valid packet, display "Power Telemetry", "No measurement"
|
||||
display->drawString(x, y, "Power Telemetry");
|
||||
@@ -121,23 +121,23 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
|
||||
}
|
||||
|
||||
// Display "Pow. From: ..."
|
||||
display->drawString(x, y, "Pow. From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
|
||||
display->drawString(x, y, "Pow. From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
|
||||
|
||||
// Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags
|
||||
if (lastMeasurement.variant.power_metrics.has_ch1_voltage || lastMeasurement.variant.power_metrics.has_ch1_current) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch1: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) +
|
||||
"V " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA");
|
||||
"Ch1: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) + "V " +
|
||||
String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA");
|
||||
}
|
||||
if (lastMeasurement.variant.power_metrics.has_ch2_voltage || lastMeasurement.variant.power_metrics.has_ch2_current) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch2: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) +
|
||||
"V " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA");
|
||||
"Ch2: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) + "V " +
|
||||
String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA");
|
||||
}
|
||||
if (lastMeasurement.variant.power_metrics.has_ch3_voltage || lastMeasurement.variant.power_metrics.has_ch3_current) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch3: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) +
|
||||
"V " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA");
|
||||
"Ch3: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) + "V " +
|
||||
String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
44
src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp
Normal file
44
src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "DFRobotGravitySensor.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include <DFRobot_RainfallSensor.h>
|
||||
#include <string>
|
||||
|
||||
DFRobotGravitySensor::DFRobotGravitySensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DFROBOT_RAIN, "DFROBOT_RAIN") {}
|
||||
|
||||
int32_t DFRobotGravitySensor::runOnce()
|
||||
{
|
||||
LOG_INFO("Init sensor: %s", sensorName);
|
||||
if (!hasSensor()) {
|
||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
|
||||
gravity = DFRobot_RainfallSensor_I2C(nodeTelemetrySensorsMap[sensorType].second);
|
||||
status = gravity.begin();
|
||||
|
||||
return initI2CSensor();
|
||||
}
|
||||
|
||||
void DFRobotGravitySensor::setup()
|
||||
{
|
||||
LOG_DEBUG("%s VID: %x, PID: %x, Version: %s", sensorName, gravity.vid, gravity.pid, gravity.getFirmwareVersion().c_str());
|
||||
}
|
||||
|
||||
bool DFRobotGravitySensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
measurement->variant.environment_metrics.has_rainfall_1h = true;
|
||||
measurement->variant.environment_metrics.has_rainfall_24h = true;
|
||||
|
||||
measurement->variant.environment_metrics.rainfall_1h = gravity.getRainfall(1);
|
||||
measurement->variant.environment_metrics.rainfall_24h = gravity.getRainfall(24);
|
||||
|
||||
LOG_INFO("Rain 1h: %f mm", measurement->variant.environment_metrics.rainfall_1h);
|
||||
LOG_INFO("Rain 24h: %f mm", measurement->variant.environment_metrics.rainfall_24h);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
29
src/modules/Telemetry/Sensor/DFRobotGravitySensor.h
Normal file
29
src/modules/Telemetry/Sensor/DFRobotGravitySensor.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef _MT_DFROBOTGRAVITYSENSOR_H
|
||||
#define _MT_DFROBOTGRAVITYSENSOR_H
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include <DFRobot_RainfallSensor.h>
|
||||
#include <string>
|
||||
|
||||
class DFRobotGravitySensor : public TelemetrySensor
|
||||
{
|
||||
private:
|
||||
DFRobot_RainfallSensor_I2C gravity = DFRobot_RainfallSensor_I2C(nodeTelemetrySensorsMap[sensorType].second);
|
||||
|
||||
protected:
|
||||
virtual void setup() override;
|
||||
|
||||
public:
|
||||
DFRobotGravitySensor();
|
||||
virtual int32_t runOnce() override;
|
||||
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifdef HAS_RAKPROT
|
||||
#include "../variants/rak2560/RAK9154Sensor.h"
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "../modules/Telemetry/Sensor/TelemetrySensor.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT)
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "RAK9154Sensor.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include "concurrency/Periodic.h"
|
||||
#include <RAK-OneWireSerial.h>
|
||||
|
||||
@@ -25,6 +26,8 @@ static uint16_t dc_vol = 0;
|
||||
static uint8_t dc_prec = 0;
|
||||
static uint8_t provision = 0;
|
||||
|
||||
extern RAK9154Sensor rak9154Sensor;
|
||||
|
||||
static void onewire_evt(const uint8_t pid, const uint8_t sid, const SNHUBAPI_EVT_E eid, uint8_t *msg, uint16_t len)
|
||||
{
|
||||
switch (eid) {
|
||||
@@ -78,6 +81,7 @@ static void onewire_evt(const uint8_t pid, const uint8_t sid, const SNHUBAPI_EVT
|
||||
default:
|
||||
break;
|
||||
}
|
||||
rak9154Sensor.setLastRead(millis());
|
||||
|
||||
break;
|
||||
case SNHUBAPI_EVT_REPORT:
|
||||
@@ -106,6 +110,7 @@ static void onewire_evt(const uint8_t pid, const uint8_t sid, const SNHUBAPI_EVT
|
||||
default:
|
||||
break;
|
||||
}
|
||||
rak9154Sensor.setLastRead(millis());
|
||||
|
||||
break;
|
||||
|
||||
@@ -145,15 +150,18 @@ static int32_t onewireHandle()
|
||||
|
||||
int32_t RAK9154Sensor::runOnce()
|
||||
{
|
||||
onewirePeriodic = new Periodic("onewireHandle", onewireHandle);
|
||||
if (!rak9154Sensor.isInitialized()) {
|
||||
onewirePeriodic = new Periodic("onewireHandle", onewireHandle);
|
||||
|
||||
mySerial.begin(9600);
|
||||
mySerial.begin(9600);
|
||||
|
||||
RakSNHub_Protocl_API.init(onewire_evt);
|
||||
RakSNHub_Protocl_API.init(onewire_evt);
|
||||
|
||||
status = true;
|
||||
initialized = true;
|
||||
return 0;
|
||||
status = true;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
|
||||
void RAK9154Sensor::setup()
|
||||
@@ -163,7 +171,16 @@ void RAK9154Sensor::setup()
|
||||
|
||||
bool RAK9154Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
return true;
|
||||
if (getBusVoltageMv() > 0) {
|
||||
measurement->variant.environment_metrics.has_voltage = true;
|
||||
measurement->variant.environment_metrics.has_current = true;
|
||||
|
||||
measurement->variant.environment_metrics.voltage = (float)getBusVoltageMv() / 1000;
|
||||
measurement->variant.environment_metrics.current = (float)getCurrentMa() / 1000;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t RAK9154Sensor::getBusVoltageMv()
|
||||
@@ -171,6 +188,11 @@ uint16_t RAK9154Sensor::getBusVoltageMv()
|
||||
return dc_vol;
|
||||
}
|
||||
|
||||
int16_t RAK9154Sensor::getCurrentMa()
|
||||
{
|
||||
return dc_cur;
|
||||
}
|
||||
|
||||
int RAK9154Sensor::getBusBatteryPercent()
|
||||
{
|
||||
return (int)dc_prec;
|
||||
@@ -180,4 +202,8 @@ bool RAK9154Sensor::isCharging()
|
||||
{
|
||||
return (dc_cur > 0) ? true : false;
|
||||
}
|
||||
void RAK9154Sensor::setLastRead(uint32_t lastRead)
|
||||
{
|
||||
this->lastRead = lastRead;
|
||||
}
|
||||
#endif // HAS_RAKPROT
|
||||
@@ -1,23 +1,30 @@
|
||||
#ifdef HAS_RAKPROT
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT)
|
||||
|
||||
#ifndef _RAK9154SENSOR_H
|
||||
#define _RAK9154SENSOR_H 1
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "../modules/Telemetry/Sensor/TelemetrySensor.h"
|
||||
#include "../modules/Telemetry/Sensor/VoltageSensor.h"
|
||||
#include "CurrentSensor.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include "VoltageSensor.h"
|
||||
|
||||
class RAK9154Sensor : public TelemetrySensor, VoltageSensor
|
||||
class RAK9154Sensor : public TelemetrySensor, VoltageSensor, CurrentSensor
|
||||
{
|
||||
private:
|
||||
protected:
|
||||
virtual void setup() override;
|
||||
uint32_t lastRead = 0;
|
||||
|
||||
public:
|
||||
RAK9154Sensor();
|
||||
virtual int32_t runOnce() override;
|
||||
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
|
||||
virtual uint16_t getBusVoltageMv() override;
|
||||
virtual int16_t getCurrentMa() override;
|
||||
int getBusBatteryPercent();
|
||||
bool isCharging();
|
||||
void setLastRead(uint32_t lastRead);
|
||||
};
|
||||
#endif // _RAK9154SENSOR_H
|
||||
#endif // HAS_RAKPROT
|
||||
@@ -48,6 +48,13 @@ class AccelerometerThread : public concurrency::OSThread
|
||||
setIntervalFromNow(0);
|
||||
};
|
||||
|
||||
void calibrate(uint16_t forSeconds)
|
||||
{
|
||||
if (sensor) {
|
||||
sensor->calibrate(forSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
int32_t runOnce() override
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
BMX160Sensor::BMX160Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {}
|
||||
|
||||
#ifdef RAK_4631
|
||||
#if defined(RAK_4631) && !defined(RAK2560)
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
|
||||
// screen is defined in main.cpp
|
||||
@@ -25,19 +25,21 @@ bool BMX160Sensor::init()
|
||||
|
||||
int32_t BMX160Sensor::runOnce()
|
||||
{
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
sBmx160SensorData_t magAccel;
|
||||
sBmx160SensorData_t gAccel;
|
||||
|
||||
/* Get a new sensor event */
|
||||
sensor.getAllData(&magAccel, NULL, &gAccel);
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
// experimental calibrate routine. Limited to between 10 and 30 seconds after boot
|
||||
if (millis() > 12 * 1000 && millis() < 30 * 1000) {
|
||||
if (doCalibration) {
|
||||
|
||||
if (!showingScreen) {
|
||||
powerFSM.trigger(EVENT_PRESS); // keep screen alive during calibration
|
||||
showingScreen = true;
|
||||
screen->startAlert((FrameCallback)drawFrameCalibration);
|
||||
}
|
||||
|
||||
if (magAccel.x > highestX)
|
||||
highestX = magAccel.x;
|
||||
if (magAccel.x < lowestX)
|
||||
@@ -50,9 +52,17 @@ int32_t BMX160Sensor::runOnce()
|
||||
highestZ = magAccel.z;
|
||||
if (magAccel.z < lowestZ)
|
||||
lowestZ = magAccel.z;
|
||||
} else if (showingScreen && millis() >= 30 * 1000) {
|
||||
showingScreen = false;
|
||||
screen->endAlert();
|
||||
|
||||
uint32_t now = millis();
|
||||
if (now > endCalibrationAt) {
|
||||
doCalibration = false;
|
||||
endCalibrationAt = 0;
|
||||
showingScreen = false;
|
||||
screen->endAlert();
|
||||
}
|
||||
|
||||
// LOG_DEBUG("BMX160 min_x: %.4f, max_X: %.4f, min_Y: %.4f, max_Y: %.4f, min_Z: %.4f, max_Z: %.4f", lowestX, highestX,
|
||||
// lowestY, highestY, lowestZ, highestZ);
|
||||
}
|
||||
|
||||
int highestRealX = highestX - (highestX + lowestX) / 2;
|
||||
@@ -93,12 +103,25 @@ int32_t BMX160Sensor::runOnce()
|
||||
heading += 270;
|
||||
break;
|
||||
}
|
||||
|
||||
screen->setHeading(heading);
|
||||
#endif
|
||||
|
||||
return MOTION_SENSOR_CHECK_INTERVAL_MS;
|
||||
}
|
||||
|
||||
void BMX160Sensor::calibrate(uint16_t forSeconds)
|
||||
{
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
LOG_DEBUG("BMX160 calibration started for %is", forSeconds);
|
||||
|
||||
doCalibration = true;
|
||||
uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided
|
||||
endCalibrationAt = millis() + calibrateFor;
|
||||
screen->setEndCalibration(endCalibrationAt);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
|
||||
|
||||
#ifdef RAK_4631
|
||||
#if defined(RAK_4631) && !defined(RAK2560)
|
||||
|
||||
#include "Fusion/Fusion.h"
|
||||
#include <Rak_BMX160.h>
|
||||
@@ -23,6 +23,7 @@ class BMX160Sensor : public MotionSensor
|
||||
explicit BMX160Sensor(ScanI2C::FoundDevice foundDevice);
|
||||
virtual bool init() override;
|
||||
virtual int32_t runOnce() override;
|
||||
virtual void calibrate(uint16_t forSeconds) override;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
|
||||
|
||||
char timeRemainingBuffer[12];
|
||||
|
||||
// screen is defined in main.cpp
|
||||
extern graphics::Screen *screen;
|
||||
|
||||
@@ -37,6 +39,12 @@ void MotionSensor::drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->drawString(x, y, "Calibrating\nCompass");
|
||||
|
||||
uint8_t timeRemaining = (screen->getEndCalibration() - millis()) / 1000;
|
||||
sprintf(timeRemainingBuffer, "( %02d )", timeRemaining);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(x, y + 40, timeRemainingBuffer);
|
||||
|
||||
int16_t compassX = 0, compassY = 0;
|
||||
uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight());
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ class MotionSensor
|
||||
// Refer to /src/concurrency/OSThread.h for more information
|
||||
inline virtual int32_t runOnce() { return MOTION_SENSOR_CHECK_INTERVAL_MS; };
|
||||
|
||||
virtual void calibrate(uint16_t forSeconds){};
|
||||
|
||||
protected:
|
||||
// Turn on the screen when a tap or motion is detected
|
||||
virtual void wakeScreen();
|
||||
@@ -53,6 +55,10 @@ class MotionSensor
|
||||
#endif
|
||||
|
||||
ScanI2C::FoundDevice device;
|
||||
|
||||
// Do calibration if true
|
||||
bool doCalibration = false;
|
||||
uint32_t endCalibrationAt = 0;
|
||||
};
|
||||
|
||||
namespace MotionSensorI2C
|
||||
|
||||
@@ -248,3 +248,12 @@ void cpuDeepSleep(uint32_t msecToWake)
|
||||
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
|
||||
esp_deep_sleep_start(); // TBD mA sleep current (battery)
|
||||
}
|
||||
|
||||
void enterDfuMode()
|
||||
{
|
||||
#if defined(ARDUINO_USB_CDC_ON_BOOT)
|
||||
// This is a no-op on the ESP32 normally, but on the ESP32-S3/C3 it will reboot to USB/JTAG mode
|
||||
REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
|
||||
esp_restart();
|
||||
#endif
|
||||
}
|
||||
@@ -210,7 +210,10 @@ void NRF52Bluetooth::shutdown()
|
||||
LOG_INFO("NRF52 bluetooth disconnecting handle %d", i);
|
||||
Bluefruit.disconnect(i);
|
||||
}
|
||||
delay(100); // wait for ondisconnect;
|
||||
// Wait for disconnection
|
||||
while (Bluefruit.connected())
|
||||
yield();
|
||||
LOG_INFO("All bluetooth connections ended");
|
||||
}
|
||||
Bluefruit.Advertising.stop();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "configuration.h"
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
#include <Adafruit_nRFCrypto.h>
|
||||
#include <InternalFileSystem.h>
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <assert.h>
|
||||
@@ -130,6 +131,54 @@ int printf(const char *fmt, ...)
|
||||
return res;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr uint8_t NRF52_MAGIC_LFS_IS_CORRUPT = 0xF5;
|
||||
constexpr uint32_t MULTIPLE_CORRUPTION_DELAY_MILLIS = 20 * 60 * 1000;
|
||||
static unsigned long millis_until_formatting_again = 0;
|
||||
|
||||
// Report the critical error from loop(), giving a chance for the screen to be initialized first.
|
||||
inline void reportLittleFSCorruptionOnce()
|
||||
{
|
||||
static bool report_corruption = !!millis_until_formatting_again;
|
||||
if (report_corruption) {
|
||||
report_corruption = false;
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void preFSBegin()
|
||||
{
|
||||
// The GPREGRET register keeps its value across warm boots. Check that this is a warm boot and, if GPREGRET
|
||||
// is set to NRF52_MAGIC_LFS_IS_CORRUPT, format LittleFS.
|
||||
if (!(NRF_POWER->RESETREAS == 0 && NRF_POWER->GPREGRET == NRF52_MAGIC_LFS_IS_CORRUPT))
|
||||
return;
|
||||
NRF_POWER->GPREGRET = 0;
|
||||
millis_until_formatting_again = millis() + MULTIPLE_CORRUPTION_DELAY_MILLIS;
|
||||
InternalFS.format();
|
||||
LOG_INFO("LittleFS format complete; restoring default settings");
|
||||
}
|
||||
|
||||
extern "C" void lfs_assert(const char *reason)
|
||||
{
|
||||
LOG_ERROR("LittleFS corruption detected: %s", reason);
|
||||
if (millis_until_formatting_again > millis()) {
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE);
|
||||
const long millis_remain = millis_until_formatting_again - millis();
|
||||
LOG_WARN("Pausing %d seconds to avoid wear on flash storage", millis_remain / 1000);
|
||||
delay(millis_remain);
|
||||
}
|
||||
LOG_INFO("Rebooting to format LittleFS");
|
||||
delay(500); // Give the serial port a bit of time to output that last message.
|
||||
// Try setting GPREGRET with the SoftDevice first. If that fails (perhaps because the SD hasn't been initialize yet) then set
|
||||
// NRF_POWER->GPREGRET directly.
|
||||
if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) {
|
||||
NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT;
|
||||
}
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
void checkSDEvents()
|
||||
{
|
||||
if (useSoftDevice) {
|
||||
@@ -154,6 +203,7 @@ void checkSDEvents()
|
||||
void nrf52Loop()
|
||||
{
|
||||
checkSDEvents();
|
||||
reportLittleFSCorruptionOnce();
|
||||
}
|
||||
|
||||
#ifdef USE_SEMIHOSTING
|
||||
@@ -309,4 +359,4 @@ void enterDfuMode()
|
||||
#else
|
||||
enterUf2Dfu();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,10 @@
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef PORTDUINO_LINUX_HARDWARE
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include "platform/portduino/USBHal.h"
|
||||
|
||||
std::map<configNames, int> settingsMap;
|
||||
@@ -196,15 +200,12 @@ void portduinoSetup()
|
||||
// if we're using a usermode driver, we need to initialize it here, to get a serial number back for mac address
|
||||
uint8_t dmac[6] = {0};
|
||||
if (settingsStrings[spidev] == "ch341") {
|
||||
ch341Hal = new Ch341Hal(0);
|
||||
if (settingsStrings[lora_usb_serial_num] != "") {
|
||||
ch341Hal->serial = settingsStrings[lora_usb_serial_num];
|
||||
}
|
||||
ch341Hal->vid = settingsMap[lora_usb_vid];
|
||||
ch341Hal->pid = settingsMap[lora_usb_pid];
|
||||
ch341Hal->init();
|
||||
if (!ch341Hal->isInit()) {
|
||||
std::cout << "Could not initialize CH341 device!" << std::endl;
|
||||
try {
|
||||
ch341Hal =
|
||||
new Ch341Hal(0, settingsStrings[lora_usb_serial_num], settingsMap[lora_usb_vid], settingsMap[lora_usb_pid]);
|
||||
} catch (std::exception &e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
std::cerr << "Could not initialize CH341 device!" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
char serial[9] = {0};
|
||||
@@ -318,8 +319,8 @@ int initGPIOPin(int pinNum, const std::string gpioChipName, int line)
|
||||
gpioBind(csPin);
|
||||
return ERRNO_OK;
|
||||
} catch (...) {
|
||||
std::exception_ptr p = std::current_exception();
|
||||
std::cout << "Warning, cannot claim pin " << gpio_name << (p ? p.__cxa_exception_type()->name() : "null") << std::endl;
|
||||
const std::type_info *t = abi::__cxa_current_exception_type();
|
||||
std::cout << "Warning, cannot claim pin " << gpio_name << (t ? t->name() : "null") << std::endl;
|
||||
return ERRNO_DISABLED;
|
||||
}
|
||||
#else
|
||||
@@ -568,4 +569,4 @@ bool MAC_from_string(std::string mac_str, uint8_t *dmac)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#include <RadioLib.h>
|
||||
#include <csignal>
|
||||
#include <iostream>
|
||||
#include <libpinedio-usb.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -26,30 +27,42 @@ class Ch341Hal : public RadioLibHal
|
||||
{
|
||||
public:
|
||||
// default constructor - initializes the base HAL and any needed private members
|
||||
explicit Ch341Hal(uint8_t spiChannel, uint32_t spiSpeed = 2000000, uint8_t spiDevice = 0, uint8_t gpioDevice = 0)
|
||||
explicit Ch341Hal(uint8_t spiChannel, std::string serial = "", uint32_t vid = 0x1A86, uint32_t pid = 0x5512,
|
||||
uint32_t spiSpeed = 2000000, uint8_t spiDevice = 0, uint8_t gpioDevice = 0)
|
||||
: RadioLibHal(PI_INPUT, PI_OUTPUT, PI_LOW, PI_HIGH, PI_RISING, PI_FALLING)
|
||||
{
|
||||
if (serial != "") {
|
||||
strncpy(pinedio.serial_number, serial.c_str(), 8);
|
||||
pinedio_set_option(&pinedio, PINEDIO_OPTION_SEARCH_SERIAL, 1);
|
||||
}
|
||||
// LOG_INFO("USB Serial: %s", pinedio.serial_number);
|
||||
|
||||
// There is no vendor with 0x0 -> so check
|
||||
if (vid != 0x0) {
|
||||
pinedio_set_option(&pinedio, PINEDIO_OPTION_VID, vid);
|
||||
pinedio_set_option(&pinedio, PINEDIO_OPTION_PID, pid);
|
||||
}
|
||||
int32_t ret = pinedio_init(&pinedio, NULL);
|
||||
if (ret != 0) {
|
||||
std::string s = "Could not open SPI: ";
|
||||
throw(s + std::to_string(ret));
|
||||
}
|
||||
|
||||
pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0);
|
||||
pinedio_set_pin_mode(&pinedio, 3, true);
|
||||
pinedio_set_pin_mode(&pinedio, 5, true);
|
||||
}
|
||||
|
||||
~Ch341Hal() { pinedio_deinit(&pinedio); }
|
||||
|
||||
void getSerialString(char *_serial, size_t len)
|
||||
{
|
||||
if (!pinedio_is_init) {
|
||||
return;
|
||||
}
|
||||
len = len > 8 ? 8 : len;
|
||||
strncpy(_serial, pinedio.serial_number, len);
|
||||
}
|
||||
|
||||
void init() override
|
||||
{
|
||||
// now the SPI
|
||||
spiBegin();
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
// stop the SPI
|
||||
spiEnd();
|
||||
}
|
||||
void init() override {}
|
||||
void term() override {}
|
||||
|
||||
// GPIO-related methods (pinMode, digitalWrite etc.) should check
|
||||
// RADIOLIB_NC as an alias for non-connected pins
|
||||
@@ -79,7 +92,7 @@ class Ch341Hal : public RadioLibHal
|
||||
|
||||
void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override
|
||||
{
|
||||
if ((interruptNum == RADIOLIB_NC)) {
|
||||
if (interruptNum == RADIOLIB_NC) {
|
||||
return;
|
||||
}
|
||||
// LOG_DEBUG("Attach interrupt to pin %d", interruptNum);
|
||||
@@ -88,23 +101,14 @@ class Ch341Hal : public RadioLibHal
|
||||
|
||||
void detachInterrupt(uint32_t interruptNum) override
|
||||
{
|
||||
if ((interruptNum == RADIOLIB_NC)) {
|
||||
if (interruptNum == RADIOLIB_NC) {
|
||||
return;
|
||||
}
|
||||
// LOG_DEBUG("Detach interrupt from pin %d", interruptNum);
|
||||
|
||||
pinedio_deattach_interrupt(&this->pinedio, (pinedio_int_pin)interruptNum);
|
||||
}
|
||||
|
||||
void delay(unsigned long ms) override
|
||||
{
|
||||
if (ms == 0) {
|
||||
sched_yield();
|
||||
return;
|
||||
}
|
||||
|
||||
usleep(ms * 1000);
|
||||
}
|
||||
void delay(unsigned long ms) override { delayMicroseconds(ms * 1000); }
|
||||
|
||||
void delayMicroseconds(unsigned long us) override
|
||||
{
|
||||
@@ -133,62 +137,26 @@ class Ch341Hal : public RadioLibHal
|
||||
|
||||
long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override
|
||||
{
|
||||
fprintf(stderr, "pulseIn for pin %u is not supported!\n", pin);
|
||||
std::cerr << "pulseIn for pin " << pin << "is not supported!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spiBegin()
|
||||
{
|
||||
if (!pinedio_is_init) {
|
||||
if (serial != "") {
|
||||
strncpy(pinedio.serial_number, serial.c_str(), 8);
|
||||
pinedio_set_option(&pinedio, PINEDIO_OPTION_SEARCH_SERIAL, 1);
|
||||
}
|
||||
pinedio_set_option(&pinedio, PINEDIO_OPTION_PID, pid);
|
||||
pinedio_set_option(&pinedio, PINEDIO_OPTION_VID, vid);
|
||||
int32_t ret = pinedio_init(&pinedio, NULL);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Could not open SPI: %d\n", ret);
|
||||
} else {
|
||||
pinedio_is_init = true;
|
||||
// LOG_INFO("USB Serial: %s", pinedio.serial_number);
|
||||
pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0);
|
||||
pinedio_set_pin_mode(&pinedio, 3, true);
|
||||
pinedio_set_pin_mode(&pinedio, 5, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void spiBegin() {}
|
||||
void spiBeginTransaction() {}
|
||||
|
||||
void spiTransfer(uint8_t *out, size_t len, uint8_t *in)
|
||||
{
|
||||
int32_t result = pinedio_transceive(&this->pinedio, out, in, len);
|
||||
if (result < 0) {
|
||||
fprintf(stderr, "Could not perform SPI transfer: %d\n", result);
|
||||
int32_t ret = pinedio_transceive(&this->pinedio, out, in, len);
|
||||
if (ret < 0) {
|
||||
std::cerr << "Could not perform SPI transfer: " << ret << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void spiEndTransaction() {}
|
||||
|
||||
void spiEnd()
|
||||
{
|
||||
if (pinedio_is_init) {
|
||||
pinedio_deinit(&pinedio);
|
||||
pinedio_is_init = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool isInit() { return pinedio_is_init; }
|
||||
|
||||
std::string serial = "";
|
||||
uint32_t pid = 0x5512;
|
||||
uint32_t vid = 0x1A86;
|
||||
void spiEnd() {}
|
||||
|
||||
private:
|
||||
// the HAL can contain any additional private members
|
||||
pinedio_inst pinedio = {0};
|
||||
bool pinedio_is_init = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#pragma once
|
||||
#include "../variants/rak2560/RAK9154Sensor.h"
|
||||
#include "PowerStatus.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
@@ -56,8 +55,8 @@ extern INA3221Sensor ina3221Sensor;
|
||||
extern MAX17048Sensor max17048Sensor;
|
||||
#endif
|
||||
|
||||
#if HAS_RAKPROT && !defined(ARCH_PORTDUINO)
|
||||
#include "../variants/rak2560/RAK9154Sensor.h"
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && HAS_RAKPROT && !defined(ARCH_PORTDUINO)
|
||||
#include "modules/Telemetry/Sensor/RAK9154Sensor.h"
|
||||
extern RAK9154Sensor rak9154Sensor;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ static const uint8_t A0 = PIN_A0;
|
||||
*/
|
||||
#define HAS_GPS 1
|
||||
#define GPS_UBLOX
|
||||
#define GPS_BAUDRATE 38400
|
||||
#define GPS_BAUDRATE 9600
|
||||
|
||||
// #define PIN_GPS_WAKE (GPIO_PORT1 + 2) // An output to wake GPS, low means allow sleep, high means force wake
|
||||
// Seems to be missing on this new board
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import struct
|
||||
|
||||
Import("env") # noqa: F821
|
||||
|
||||
|
||||
# Parse input and create UF2 file
|
||||
def create_uf2(source, target, env):
|
||||
# source_hex = target[0].get_abspath()
|
||||
source_hex = target[0].get_string(False)
|
||||
source_hex = ".\\" + source_hex
|
||||
print("#########################################################")
|
||||
print("Create UF2 from " + source_hex)
|
||||
print("#########################################################")
|
||||
# print("Source: " + source_hex)
|
||||
target = source_hex.replace(".hex", "")
|
||||
target = target + ".uf2"
|
||||
# print("Target: " + target)
|
||||
|
||||
with open(source_hex, mode="rb") as f:
|
||||
inpbuf = f.read()
|
||||
|
||||
outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
|
||||
|
||||
write_file(target, outbuf)
|
||||
print("#########################################################")
|
||||
print(target + " is ready to flash to target device")
|
||||
print("#########################################################")
|
||||
|
||||
|
||||
# Add callback after .hex file was created
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", create_uf2) # noqa: F821
|
||||
|
||||
# UF2 creation taken from uf2conv.py
|
||||
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
|
||||
UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
|
||||
UF2_MAGIC_END = 0x0AB16F30 # Ditto
|
||||
|
||||
familyid = 0xADA52840
|
||||
|
||||
|
||||
class Block:
|
||||
def __init__(self, addr):
|
||||
self.addr = addr
|
||||
self.bytes = bytearray(256)
|
||||
|
||||
def encode(self, blockno, numblocks):
|
||||
global familyid
|
||||
flags = 0x0
|
||||
if familyid:
|
||||
flags |= 0x2000
|
||||
hd = struct.pack(
|
||||
"<IIIIIIII",
|
||||
UF2_MAGIC_START0,
|
||||
UF2_MAGIC_START1,
|
||||
flags,
|
||||
self.addr,
|
||||
256,
|
||||
blockno,
|
||||
numblocks,
|
||||
familyid,
|
||||
)
|
||||
hd += self.bytes[0:256]
|
||||
while len(hd) < 512 - 4:
|
||||
hd += b"\x00"
|
||||
hd += struct.pack("<I", UF2_MAGIC_END)
|
||||
return hd
|
||||
|
||||
|
||||
def write_file(name, buf):
|
||||
with open(name, "wb") as f:
|
||||
f.write(buf)
|
||||
# print("Wrote %d bytes to %s." % (len(buf), name))
|
||||
|
||||
|
||||
def convert_from_hex_to_uf2(buf):
|
||||
global appstartaddr
|
||||
appstartaddr = None
|
||||
upper = 0
|
||||
currblock = None
|
||||
blocks = []
|
||||
for line in buf.split("\n"):
|
||||
if line[0] != ":":
|
||||
continue
|
||||
i = 1
|
||||
rec = []
|
||||
while i < len(line) - 1:
|
||||
rec.append(int(line[i : i + 2], 16))
|
||||
i += 2
|
||||
tp = rec[3]
|
||||
if tp == 4:
|
||||
upper = ((rec[4] << 8) | rec[5]) << 16
|
||||
elif tp == 2:
|
||||
upper = ((rec[4] << 8) | rec[5]) << 4
|
||||
assert (upper & 0xFFFF) == 0
|
||||
elif tp == 1:
|
||||
break
|
||||
elif tp == 0:
|
||||
addr = upper | (rec[1] << 8) | rec[2]
|
||||
if appstartaddr is None:
|
||||
appstartaddr = addr
|
||||
i = 4
|
||||
while i < len(rec) - 1:
|
||||
if not currblock or currblock.addr & ~0xFF != addr & ~0xFF:
|
||||
currblock = Block(addr & ~0xFF)
|
||||
blocks.append(currblock)
|
||||
currblock.bytes[addr & 0xFF] = rec[i]
|
||||
addr += 1
|
||||
i += 1
|
||||
numblocks = len(blocks)
|
||||
resfile = b""
|
||||
for i in range(0, numblocks):
|
||||
resfile += blocks[i].encode(i, numblocks)
|
||||
return resfile
|
||||
@@ -15,8 +15,6 @@ lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
${networking_base.lib_deps}
|
||||
melopero/Melopero RV3028@^1.1.0
|
||||
rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2
|
||||
beegee-tokyo/RAKwireless RAK12034@^1.0.0
|
||||
https://github.com/beegee-tokyo/RAK-OneWireSerial.git#0.0.2
|
||||
debug_tool = jlink
|
||||
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
|
||||
|
||||
Reference in New Issue
Block a user