mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-18 03:03:30 +00:00
Merge branch 'master' into NextHopRouter
This commit is contained in:
commit
b91b66bc7a
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 }}
|
45
.github/workflows/hook_copr.yml
vendored
45
.github/workflows/hook_copr.yml
vendored
@ -3,9 +3,7 @@ name: Trigger COPR build
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
COPR_HOOK_DAILY:
|
||||
COPR_HOOK_ALPHA:
|
||||
COPR_HOOK_BETA:
|
||||
COPR_API_CONFIG:
|
||||
inputs:
|
||||
copr_project:
|
||||
description: COPR project to target
|
||||
@ -27,35 +25,14 @@ jobs:
|
||||
ref: ${{ github.ref }}
|
||||
repository: ${{ github.repository }}
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
pip install requests
|
||||
|
||||
- name: Trigger COPR build
|
||||
shell: python
|
||||
run: |
|
||||
import requests
|
||||
|
||||
project_name = "${{ inputs.copr_project }}"
|
||||
if project_name == "daily":
|
||||
hook_secret = "${{ secrets.COPR_HOOK_DAILY }}"
|
||||
project_id = 160277
|
||||
elif project_name == "alpha":
|
||||
hook_secret = "${{ secrets.COPR_HOOK_ALPHA }}"
|
||||
project_id = 160278
|
||||
elif project_name == "beta":
|
||||
hook_secret = "${{ secrets.COPR_HOOK_BETA }}"
|
||||
project_id = 160279
|
||||
else:
|
||||
raise ValueError(f"Unknown COPR project: {project_name}")
|
||||
|
||||
webhook_url = f"https://copr.fedorainfracloud.org/webhooks/github/{project_id}/{hook_secret}/meshtasticd/"
|
||||
copr_payload = {
|
||||
"ref": "${{ github.ref }}",
|
||||
"after": "${{ github.sha }}",
|
||||
"repository": {
|
||||
"clone_url": "${{ github.server_url }}/${{ github.repository }}.git",
|
||||
}
|
||||
}
|
||||
r = requests.post(webhook_url, json=copr_payload, headers={"X-GitHub-Event": "push"})
|
||||
r.raise_for_status()
|
||||
uses: vidplace7/copr-build@main
|
||||
id: copr_build
|
||||
env:
|
||||
COPR_API_TOKEN_CONFIG: ${{ secrets.COPR_API_CONFIG }}
|
||||
with:
|
||||
owner: "@meshtastic"
|
||||
package-name: meshtasticd
|
||||
project-name: ${{ inputs.copr_project }}
|
||||
git-remote: "${{ github.server_url }}/${{ github.repository }}.git"
|
||||
committish: ${{ github.sha }}
|
||||
|
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
|
||||
|
9
.github/workflows/release_channels.yml
vendored
9
.github/workflows/release_channels.yml
vendored
@ -9,6 +9,13 @@ permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-docker:
|
||||
uses: ./.github/workflows/docker_manifest.yml
|
||||
with:
|
||||
release_channel: |-
|
||||
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
secrets: inherit
|
||||
|
||||
package-ppa:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -25,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
|
||||
|
@ -38,7 +38,8 @@ USER root
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libulfius2.7 libusb-1.0-0-dev liborcania2.3 libssl3 && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
&& mkdir -p /etc/meshtasticd/ssl
|
||||
|
||||
# Fetch compiled binary from the builder
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
|
||||
|
@ -29,7 +29,8 @@ USER root
|
||||
|
||||
RUN apk add libstdc++ libgpiod yaml-cpp libusb i2c-tools \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
&& mkdir -p /etc/meshtasticd/ssl
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
|
||||
|
||||
WORKDIR /var/lib/meshtasticd
|
||||
|
@ -4,7 +4,7 @@ platform = platformio/nordicnrf52@^10.7.0
|
||||
extends = arduino_base
|
||||
platform_packages =
|
||||
; our custom Git version until they merge our PR
|
||||
framework-arduinoadafruitnrf52 @ https://github.com/geeksville/Adafruit_nRF52_Arduino.git
|
||||
framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino.git#e13f5820002a4fb2a5e6754b42ace185277e5adf
|
||||
toolchain-gccarmnoneeabi@~1.90301.0
|
||||
|
||||
build_type = debug
|
||||
|
@ -78,6 +78,8 @@ Lora:
|
||||
# TXen: x # TX and RX enable pins
|
||||
# RXen: x
|
||||
|
||||
# SX126X_MAX_POWER: 8 # Limit the output power to 8 dBm, useful for amped nodes
|
||||
|
||||
# spiSpeed: 2000000
|
||||
|
||||
### Set default/fallback gpio chip to use in /dev/. Defaults to 0.
|
||||
@ -182,10 +184,12 @@ Logging:
|
||||
Webserver:
|
||||
# Port: 443 # Port for Webserver & Webservices
|
||||
# RootPath: /usr/share/meshtasticd/web # Root Dir of WebServer
|
||||
# SSLKey: /etc/meshtasticd/ssl/private_key.pem # Path to SSL Key, generated if not present
|
||||
# SSLCert: /etc/meshtasticd/ssl/certificate.pem # Path to SSL Certificate, generated if not present
|
||||
|
||||
General:
|
||||
MaxNodes: 200
|
||||
MaxMessageQueue: 100
|
||||
ConfigDirectory: /etc/meshtasticd/config.d/
|
||||
# MACAddress: AA:BB:CC:DD:EE:FF
|
||||
# MACAddressSource: eth0
|
||||
# MACAddressSource: eth0
|
9
bin/config.d/OpenWRT/BananaPi-BPI-R4-sx1262.yaml
Normal file
9
bin/config.d/OpenWRT/BananaPi-BPI-R4-sx1262.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
Lora:
|
||||
Module: sx1262 # BananaPi-BPI-R4 SPI via 26p GPIO Header
|
||||
## CS: 28
|
||||
IRQ: 50
|
||||
Busy: 62
|
||||
Reset: 51
|
||||
spidev: spidev1.0
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
DIO3_TCXO_VOLTAGE: true
|
@ -7,3 +7,6 @@ Lora:
|
||||
TXen: 13
|
||||
RXen: 12
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
# Only for E22-900M33S:
|
||||
# Limit the output power to 8 dBm
|
||||
# SX126X_MAX_POWER: 8
|
@ -102,7 +102,7 @@ pref_flags = []
|
||||
for pref in userPrefs:
|
||||
if userPrefs[pref].startswith("{"):
|
||||
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
|
||||
elif userPrefs[pref].replace(".", "").isdigit():
|
||||
elif userPrefs[pref].lstrip("-").replace(".", "").isdigit():
|
||||
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
|
||||
elif userPrefs[pref] == "true" or userPrefs[pref] == "false":
|
||||
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
|
||||
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,6 +1,8 @@
|
||||
meshtasticd (2.5.20.0) UNRELEASED; urgency=medium
|
||||
meshtasticd (2.5.21.0) UNRELEASED; urgency=medium
|
||||
|
||||
* Initial packaging
|
||||
* GitHub Actions Automatic version bump
|
||||
* GitHub Actions Automatic version bump
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
-- Austin Lane <github-actions[bot]@users.noreply.github.com> Mon, 13 Jan 2025 19:24:14 +0000
|
||||
-- Austin Lane <github-actions[bot]@users.noreply.github.com> Sat, 25 Jan 2025 01:39:16 +0000
|
||||
|
3
debian/meshtasticd.dirs
vendored
3
debian/meshtasticd.dirs
vendored
@ -1,4 +1,5 @@
|
||||
etc/meshtasticd
|
||||
etc/meshtasticd/config.d
|
||||
etc/meshtasticd/available.d
|
||||
usr/share/meshtasticd/web
|
||||
usr/share/meshtasticd/web
|
||||
etc/meshtasticd/ssl
|
@ -72,6 +72,8 @@ install -D -m 0644 bin/meshtasticd.service %{buildroot}%{_unitdir}/meshtasticd.s
|
||||
# Install the web files under /usr/share/meshtasticd/web
|
||||
mkdir -p %{buildroot}%{_datadir}/meshtasticd/web
|
||||
cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
|
||||
# Install default SSL storage directory (for web)
|
||||
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/ssl
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
@ -86,6 +88,7 @@ cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
|
||||
%dir %{_datadir}/meshtasticd
|
||||
%dir %{_datadir}/meshtasticd/web
|
||||
%{_datadir}/meshtasticd/web/*
|
||||
%dir %{_sysconfdir}/meshtasticd/ssl
|
||||
|
||||
%changelog
|
||||
%autochangelog
|
@ -20,7 +20,7 @@ extra_scripts = bin/platformio-custom.py
|
||||
build_flags = -Wno-missing-field-initializers
|
||||
|
||||
-Wno-format
|
||||
-Isrc -Isrc/mesh -Isrc/mesh/generated -Isrc/gps -Isrc/buzz -Wl,-Map,.pio/build/output.map
|
||||
-Isrc -Isrc/mesh -Isrc/mesh/generated -Isrc/gps -Isrc/buzz -Wl,-Map,"${platformio.build_dir}"/output.map
|
||||
-DUSE_THREAD_NAMES
|
||||
-DTINYGPS_OPTION_NO_CUSTOM_FIELDS
|
||||
-DPB_ENABLE_MALLOC=1
|
||||
@ -126,6 +126,7 @@ lib_deps =
|
||||
mprograms/QMC5883LCompass@1.2.3
|
||||
dfrobot/DFRobot_RTU@1.0.3
|
||||
https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d
|
||||
https://github.com/DFRobot/DFRobot_RainfallSensor#38fea5e02b40a5430be6dab39a99a6f6347d667e
|
||||
robtillaart/INA226@0.6.0
|
||||
|
||||
; Health Sensor Libraries
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 76f806e1bb1e2a7b157a14fadd095775f63db5e4
|
||||
Subproject commit 7f13df0e5f7cbb07f0e6f3a57c0d86ad448738db
|
@ -190,6 +190,20 @@ int32_t ButtonThread::runOnce()
|
||||
case 4:
|
||||
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
|
||||
break;
|
||||
#endif
|
||||
#if defined(RAK_4631)
|
||||
// 5 clicks: start accelerometer/magenetometer calibration for 30 seconds
|
||||
case 5:
|
||||
if (accelerometerThread) {
|
||||
accelerometerThread->calibrate(30);
|
||||
}
|
||||
break;
|
||||
// 6 clicks: start accelerometer/magenetometer calibration for 60 seconds
|
||||
case 6:
|
||||
if (accelerometerThread) {
|
||||
accelerometerThread->calibrate(60);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
// No valid multipress action
|
||||
default:
|
||||
|
@ -49,24 +49,6 @@ void OSFS::writeNBytes(uint16_t address, unsigned int num, const byte *input)
|
||||
}
|
||||
#endif
|
||||
|
||||
bool lfs_assert_failed =
|
||||
false; // Note: we use this global on all platforms, though it can only be set true on nrf52 (in our modified lfs_util.h)
|
||||
|
||||
extern "C" void lfs_assert(const char *reason)
|
||||
{
|
||||
LOG_ERROR("LFS assert: %s", reason);
|
||||
lfs_assert_failed = true;
|
||||
|
||||
#ifndef ARCH_PORTDUINO
|
||||
#ifdef FSCom
|
||||
// CORRUPTED FILESYSTEM. This causes bootloop so
|
||||
// might as well try formatting now.
|
||||
LOG_ERROR("Trying FSCom.format()");
|
||||
FSCom.format();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copies a file from one location to another.
|
||||
*
|
||||
@ -203,7 +185,7 @@ std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels)
|
||||
file.close();
|
||||
}
|
||||
} else {
|
||||
meshtastic_FileInfo fileInfo = {"", file.size()};
|
||||
meshtastic_FileInfo fileInfo = {"", static_cast<uint32_t>(file.size())};
|
||||
#ifdef ARCH_ESP32
|
||||
strcpy(fileInfo.file_name, file.path());
|
||||
#else
|
||||
@ -348,10 +330,16 @@ void rmDir(const char *dirname)
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Some platforms (nrf52) might need to do an extra step before FSBegin().
|
||||
*/
|
||||
__attribute__((weak, noinline)) void preFSBegin() {}
|
||||
|
||||
void fsInit()
|
||||
{
|
||||
#ifdef FSCom
|
||||
spiLock->lock();
|
||||
concurrency::LockGuard g(spiLock);
|
||||
preFSBegin();
|
||||
if (!FSBegin()) {
|
||||
LOG_ERROR("Filesystem mount failed");
|
||||
// assert(0); This auto-formats the partition, so no need to fail here.
|
||||
@ -362,7 +350,6 @@ void fsInit()
|
||||
LOG_DEBUG("Filesystem files:");
|
||||
#endif
|
||||
listDir("/", 10);
|
||||
spiLock->unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -400,4 +387,4 @@ void setupSDCard()
|
||||
LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024)));
|
||||
LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024)));
|
||||
#endif
|
||||
}
|
||||
}
|
@ -57,7 +57,4 @@ bool renameFile(const char *pathFrom, const char *pathTo);
|
||||
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
|
||||
void listDir(const char *dirname, uint8_t levels, bool del = false);
|
||||
void rmDir(const char *dirname);
|
||||
void setupSDCard();
|
||||
|
||||
extern bool lfs_assert_failed; // Note: we use this global on all platforms, though it can only be set true on nrf52 (in our
|
||||
// modified lfs_util.h)
|
||||
void setupSDCard();
|
@ -87,7 +87,7 @@ MAX17048Sensor max17048Sensor;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if HAS_RAKPROT && !defined(ARCH_PORTDUINO)
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && HAS_RAKPROT && !defined(ARCH_PORTDUINO)
|
||||
RAK9154Sensor rak9154Sensor;
|
||||
#endif
|
||||
|
||||
@ -243,7 +243,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
virtual uint16_t getBattVoltage() override
|
||||
{
|
||||
|
||||
#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
|
||||
#if HAS_TELEMETRY && defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) && \
|
||||
!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
if (hasRAK()) {
|
||||
return getRAKVoltage();
|
||||
}
|
||||
@ -406,7 +407,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
/// we can't be smart enough to say 'full'?
|
||||
virtual bool isCharging() override
|
||||
{
|
||||
#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && \
|
||||
!defined(HAS_PMU)
|
||||
if (hasRAK()) {
|
||||
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
|
||||
}
|
||||
@ -447,7 +449,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
|
||||
uint32_t last_read_time_ms = 0;
|
||||
|
||||
#if defined(HAS_RAKPROT)
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT)
|
||||
|
||||
uint16_t getRAKVoltage() { return rak9154Sensor.getBusVoltageMv(); }
|
||||
|
||||
|
@ -79,17 +79,17 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
|
||||
}
|
||||
if (color && logLevel != nullptr) {
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
|
||||
Print::write("\u001b[34m", 6);
|
||||
Print::write("\u001b[34m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
|
||||
Print::write("\u001b[32m", 6);
|
||||
Print::write("\u001b[32m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
|
||||
Print::write("\u001b[33m", 6);
|
||||
Print::write("\u001b[33m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
|
||||
Print::write("\u001b[31m", 6);
|
||||
Print::write("\u001b[31m", 5);
|
||||
}
|
||||
len = Print::write(printBuf, len);
|
||||
if (color && logLevel != nullptr) {
|
||||
Print::write("\u001b[0m", 5);
|
||||
Print::write("\u001b[0m", 4);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
@ -107,15 +107,15 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
|
||||
// include the header
|
||||
if (color) {
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
|
||||
Print::write("\u001b[34m", 6);
|
||||
Print::write("\u001b[34m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
|
||||
Print::write("\u001b[32m", 6);
|
||||
Print::write("\u001b[32m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
|
||||
Print::write("\u001b[33m", 6);
|
||||
Print::write("\u001b[33m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
|
||||
Print::write("\u001b[31m", 6);
|
||||
Print::write("\u001b[31m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0)
|
||||
Print::write("\u001b[35m", 6);
|
||||
Print::write("\u001b[35m", 5);
|
||||
}
|
||||
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
|
||||
@ -393,4 +393,4 @@ std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
|
||||
break;
|
||||
}
|
||||
return std::string(formatted.get());
|
||||
}
|
||||
}
|
@ -8,10 +8,8 @@ static File openFile(const char *filename, bool fullAtomic)
|
||||
concurrency::LockGuard g(spiLock);
|
||||
LOG_DEBUG("Opening %s, fullAtomic=%d", filename, fullAtomic);
|
||||
#ifdef ARCH_NRF52
|
||||
lfs_assert_failed = false;
|
||||
File file = FSCom.open(filename, FILE_O_WRITE);
|
||||
file.seek(0);
|
||||
return file;
|
||||
FSCom.remove(filename);
|
||||
return FSCom.open(filename, FILE_O_WRITE);
|
||||
#endif
|
||||
if (!fullAtomic)
|
||||
FSCom.remove(filename); // Nuke the old file to make space (ignore if it !exists)
|
||||
@ -20,7 +18,6 @@ static File openFile(const char *filename, bool fullAtomic)
|
||||
filenameTmp += ".tmp";
|
||||
|
||||
// clear any previous LFS errors
|
||||
lfs_assert_failed = false;
|
||||
return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE);
|
||||
}
|
||||
|
||||
@ -61,9 +58,6 @@ bool SafeFile::close()
|
||||
return false;
|
||||
|
||||
spiLock->lock();
|
||||
#ifdef ARCH_NRF52
|
||||
f.truncate();
|
||||
#endif
|
||||
f.close();
|
||||
spiLock->unlock();
|
||||
|
||||
@ -96,8 +90,6 @@ bool SafeFile::close()
|
||||
bool SafeFile::testReadback()
|
||||
{
|
||||
concurrency::LockGuard g(spiLock);
|
||||
bool lfs_failed = lfs_assert_failed;
|
||||
lfs_assert_failed = false;
|
||||
|
||||
String filenameTmp = filename;
|
||||
filenameTmp += ".tmp";
|
||||
@ -119,7 +111,7 @@ bool SafeFile::testReadback()
|
||||
return false;
|
||||
}
|
||||
|
||||
return !lfs_failed;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
@ -145,6 +145,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define OPT3001_ADDR_ALT 0x44
|
||||
#define MLX90632_ADDR 0x3A
|
||||
#define DFROBOT_LARK_ADDR 0x42
|
||||
#define DFROBOT_RAIN_ADDR 0x1d
|
||||
#define NAU7802_ADDR 0x2A
|
||||
#define MAX30102_ADDR 0x57
|
||||
#define MLX90614_ADDR_DEF 0x5A
|
||||
|
@ -66,6 +66,7 @@ class ScanI2C
|
||||
CGRADSENS,
|
||||
INA226,
|
||||
NXP_SE050,
|
||||
DFROBOT_RAIN,
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
@ -84,23 +84,33 @@ ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const
|
||||
return o_probe;
|
||||
}
|
||||
uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation ®isterLocation,
|
||||
ScanI2CTwoWire::ResponseWidth responseWidth) const
|
||||
ScanI2CTwoWire::ResponseWidth responseWidth, bool zeropad = false) const
|
||||
{
|
||||
uint16_t value = 0x00;
|
||||
TwoWire *i2cBus = fetchI2CBus(registerLocation.i2cAddress);
|
||||
|
||||
i2cBus->beginTransmission(registerLocation.i2cAddress.address);
|
||||
i2cBus->write(registerLocation.registerAddress);
|
||||
if (zeropad) {
|
||||
// Lark Commands need the argument list length in 2 bytes.
|
||||
i2cBus->write((int)0);
|
||||
i2cBus->write((int)0);
|
||||
}
|
||||
i2cBus->endTransmission();
|
||||
delay(20);
|
||||
i2cBus->requestFrom(registerLocation.i2cAddress.address, responseWidth);
|
||||
if (i2cBus->available() == 2) {
|
||||
if (i2cBus->available() > 1) {
|
||||
// Read MSB, then LSB
|
||||
value = (uint16_t)i2cBus->read() << 8;
|
||||
value |= i2cBus->read();
|
||||
} else if (i2cBus->available()) {
|
||||
value = i2cBus->read();
|
||||
}
|
||||
// Drain excess bytes
|
||||
for (uint8_t i = 0; i < responseWidth - 1; i++) {
|
||||
if (i2cBus->available())
|
||||
i2cBus->read();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -286,7 +296,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
RESPONSE_PAYLOAD 0x01
|
||||
RESPONSE_PAYLOAD+1 0x00
|
||||
*/
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x05), 2);
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x05), 6, true);
|
||||
LOG_DEBUG("Register MFG_UID 05: 0x%x", registerValue);
|
||||
if (registerValue == 0x5305) {
|
||||
logFoundDevice("DFRobot Lark", (uint8_t)addr.address);
|
||||
@ -402,6 +412,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
|
||||
#ifdef HAS_TPS65233
|
||||
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
|
||||
#endif
|
||||
|
@ -53,7 +53,7 @@ class ScanI2CTwoWire : public ScanI2C
|
||||
|
||||
concurrency::Lock lock;
|
||||
|
||||
uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const;
|
||||
uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth, bool) const;
|
||||
|
||||
DeviceType probeOLED(ScanI2C::DeviceAddress) const;
|
||||
|
||||
|
@ -123,7 +123,7 @@ static bool heartbeat = false;
|
||||
|
||||
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||
|
||||
/// Check if the display can render a string (detect special chars; emoji)
|
||||
// Check if the display can render a string (detect special chars; emoji)
|
||||
static bool haveGlyphs(const char *str)
|
||||
{
|
||||
#if defined(OLED_PL) || defined(OLED_UA) || defined(OLED_RU) || defined(OLED_CS)
|
||||
@ -162,11 +162,7 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
|
||||
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
#ifdef USERPREFS_SPLASH_TITLE
|
||||
const char *title = USERPREFS_SPLASH_TITLE;
|
||||
#else
|
||||
const char *title = "meshtastic.org";
|
||||
#endif
|
||||
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
@ -185,6 +181,56 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
|
||||
}
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
|
||||
static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
static const uint8_t xbm[] = USERPREFS_OEM_IMAGE_DATA;
|
||||
display->drawXbm(x + (SCREEN_WIDTH - USERPREFS_OEM_IMAGE_WIDTH) / 2,
|
||||
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - USERPREFS_OEM_IMAGE_HEIGHT) / 2 + 2, USERPREFS_OEM_IMAGE_WIDTH,
|
||||
USERPREFS_OEM_IMAGE_HEIGHT, xbm);
|
||||
|
||||
switch (USERPREFS_OEM_FONT_SIZE) {
|
||||
case 0:
|
||||
display->setFont(FONT_SMALL);
|
||||
break;
|
||||
case 2:
|
||||
display->setFont(FONT_LARGE);
|
||||
break;
|
||||
default:
|
||||
display->setFont(FONT_MEDIUM);
|
||||
break;
|
||||
}
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
const char *title = USERPREFS_OEM_TEXT;
|
||||
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// Draw region in upper left
|
||||
if (upperMsg)
|
||||
display->drawString(x + 0, y + 0, upperMsg);
|
||||
|
||||
// Draw version and shortname in upper right
|
||||
char buf[25];
|
||||
snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : "");
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display->drawString(x + SCREEN_WIDTH, y + 0, buf);
|
||||
screen->forceDisplay();
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
|
||||
}
|
||||
|
||||
static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// Draw region in upper left
|
||||
const char *region = myRegion ? myRegion->name : NULL;
|
||||
drawOEMIconScreen(region, display, state, x, y);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message)
|
||||
{
|
||||
uint16_t x_offset = display->width() / 2;
|
||||
@ -1658,6 +1704,10 @@ void Screen::setup()
|
||||
// Set the utf8 conversion function
|
||||
dispdev->setFontTableLookupFunction(customFontTableLookup);
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
logo_timeout *= 2; // Double the time if we have a custom logo
|
||||
#endif
|
||||
|
||||
// Add frames.
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
|
||||
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||
@ -1803,6 +1853,22 @@ int32_t Screen::runOnce()
|
||||
showingBootScreen = false;
|
||||
}
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
static bool showingOEMBootScreen = true;
|
||||
if (showingOEMBootScreen && (millis() > ((logo_timeout / 2) + serialSinceMsec))) {
|
||||
LOG_INFO("Switch to OEM screen...");
|
||||
// Change frames.
|
||||
static FrameCallback bootOEMFrames[] = {drawOEMBootScreen};
|
||||
static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]);
|
||||
ui->setFrames(bootOEMFrames, bootOEMFrameCount);
|
||||
ui->update();
|
||||
#ifndef USE_EINK
|
||||
ui->update();
|
||||
#endif
|
||||
showingOEMBootScreen = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_WELCOME_UNSET
|
||||
if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||
setWelcomeFrames();
|
||||
|
@ -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);
|
||||
|
39
src/main.cpp
39
src/main.cpp
@ -115,10 +115,6 @@ AccelerometerThread *accelerometerThread = nullptr;
|
||||
AudioThread *audioThread = nullptr;
|
||||
#endif
|
||||
|
||||
#if defined(TCXO_OPTIONAL)
|
||||
float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if TCXO is optional, put this here so it can be changed further down.
|
||||
#endif
|
||||
|
||||
using namespace concurrency;
|
||||
|
||||
volatile static const char slipstreamTZString[] = USERPREFS_TZ_STRING;
|
||||
@ -610,6 +606,7 @@ void setup()
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::CGRADSENS, meshtastic_TelemetrySensorType_RADSENS);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN, meshtastic_TelemetrySensorType_DFROBOT_RAIN);
|
||||
|
||||
i2cScanner.reset();
|
||||
#endif
|
||||
@ -927,13 +924,16 @@ void setup()
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
#endif
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
delete sxIf;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success");
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
@ -941,29 +941,28 @@ void setup()
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// Try using the specified TCXO voltage
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with TCXO, Vref %f V", tcxoVoltage);
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
tcxoVoltage = 0; // if it fails, set the TCXO voltage to zero for the next attempt
|
||||
// try using the specified TCXO voltage
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
delete sxIf;
|
||||
} else {
|
||||
LOG_WARN("SX1262 init success, TCXO, Vref %f V", tcxoVoltage);
|
||||
LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instea
|
||||
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instead
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with XTAL, Vref %f V", tcxoVoltage);
|
||||
LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if it fails, set the TCXO voltage back for the next radio search
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success, XTAL, Vref %f V", tcxoVoltage);
|
||||
LOG_INFO("SX1262 init success, XTAL, Vref 0.0V");
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,18 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
|
||||
// specified (may be dangerous if using external PA and LR11x0 power config forgotten)
|
||||
#if ARCH_PORTDUINO
|
||||
#define LR1110_MAX_POWER settingsMap[lr1110_max_power]
|
||||
#endif
|
||||
#ifndef LR1110_MAX_POWER
|
||||
#define LR1110_MAX_POWER 22
|
||||
#endif
|
||||
|
||||
// the 2.4G part maxes at 13dBm
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
#define LR1120_MAX_POWER settingsMap[lr1120_max_power]
|
||||
#endif
|
||||
#ifndef LR1120_MAX_POWER
|
||||
#define LR1120_MAX_POWER 13
|
||||
#endif
|
||||
|
@ -10,6 +10,7 @@
|
||||
std::vector<MeshModule *> *MeshModule::modules;
|
||||
|
||||
const meshtastic_MeshPacket *MeshModule::currentRequest;
|
||||
uint8_t MeshModule::numPeriodicModules = 0;
|
||||
|
||||
/**
|
||||
* If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow
|
||||
@ -35,6 +36,15 @@ MeshModule::~MeshModule()
|
||||
modules->erase(it);
|
||||
}
|
||||
|
||||
// ⚠️ **Only call once** to set the initial delay before a module starts broadcasting periodically
|
||||
int32_t MeshModule::setStartDelay()
|
||||
{
|
||||
int32_t startDelay = MESHMODULE_MIN_BROADCAST_DELAY_MS + numPeriodicModules * MESHMODULE_BROADCAST_SPACING_MS;
|
||||
numPeriodicModules++;
|
||||
|
||||
return startDelay;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
|
||||
uint8_t hopLimit)
|
||||
{
|
||||
|
@ -9,6 +9,9 @@
|
||||
#include <OLEDDisplayUi.h>
|
||||
#endif
|
||||
|
||||
#define MESHMODULE_MIN_BROADCAST_DELAY_MS 30 * 1000 // Min. delay after boot before sending first broadcast by any module
|
||||
#define MESHMODULE_BROADCAST_SPACING_MS 15 * 1000 // Initial spacing between broadcasts of different modules
|
||||
|
||||
/** handleReceived return enumeration
|
||||
*
|
||||
* Use ProcessMessage::CONTINUE to allows other modules to process a message.
|
||||
@ -119,6 +122,12 @@ class MeshModule
|
||||
*/
|
||||
static const meshtastic_MeshPacket *currentRequest;
|
||||
|
||||
// We keep track of the number of modules that send a periodic broadcast to schedule them spaced out over time
|
||||
static uint8_t numPeriodicModules;
|
||||
|
||||
// Set the start delay for module that broadcasts periodically
|
||||
int32_t setStartDelay();
|
||||
|
||||
/**
|
||||
* If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling.
|
||||
*/
|
||||
|
@ -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
|
||||
|
||||
#define NUM_RELAYERS \
|
||||
3 // Number of relayer we keep track of. Use 3 to be efficient with memory alignment of PacketRecord to 16 bytes
|
||||
|
@ -9,6 +9,9 @@
|
||||
#include "PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
#define RF95_MAX_POWER settingsMap[rf95_max_power]
|
||||
#endif
|
||||
#ifndef RF95_MAX_POWER
|
||||
#define RF95_MAX_POWER 20
|
||||
#endif
|
||||
@ -337,4 +340,4 @@ bool RF95Interface::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
@ -346,8 +346,11 @@ void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id)
|
||||
meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false);
|
||||
if (p) {
|
||||
p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr);
|
||||
txQueue.enqueue(p);
|
||||
LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
|
||||
if (txQueue.enqueue(p)) {
|
||||
LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
|
||||
} else {
|
||||
packetPool.release(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
|
||||
// specified (may be dangerous if using external PA and SX126x power config forgotten)
|
||||
#if ARCH_PORTDUINO
|
||||
#define SX126X_MAX_POWER settingsMap[sx126x_max_power]
|
||||
#endif
|
||||
#ifndef SX126X_MAX_POWER
|
||||
#define SX126X_MAX_POWER 22
|
||||
#endif
|
||||
@ -50,22 +53,13 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
#endif
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
float tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
|
||||
tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
|
||||
if (settingsMap[sx126x_ant_sw_pin] != RADIOLIB_NC) {
|
||||
digitalWrite(settingsMap[sx126x_ant_sw_pin], HIGH);
|
||||
pinMode(settingsMap[sx126x_ant_sw_pin], OUTPUT);
|
||||
}
|
||||
// FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE
|
||||
#elif !defined(SX126X_DIO3_TCXO_VOLTAGE)
|
||||
float tcxoVoltage =
|
||||
0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per
|
||||
// https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/SX126x/SX126x.h#L471C26-L471C104
|
||||
// (DIO3 is free to be used as an IRQ)
|
||||
#elif !defined(TCXO_OPTIONAL)
|
||||
float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE;
|
||||
// (DIO3 is not free to be used as an IRQ)
|
||||
#endif
|
||||
if (tcxoVoltage == 0)
|
||||
if (tcxoVoltage == 0.0)
|
||||
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
|
||||
else
|
||||
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
|
||||
@ -83,7 +77,7 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
|
||||
// \todo Display actual typename of the adapter, not just `SX126x`
|
||||
LOG_INFO("SX126x init result %d", res);
|
||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND)
|
||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
|
||||
return false;
|
||||
|
||||
LOG_INFO("Frequency set to %f", getFreq());
|
||||
@ -342,4 +336,4 @@ template <typename T> bool SX126xInterface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
@ -28,8 +28,11 @@ template <class T> class SX126xInterface : public RadioLibInterface
|
||||
|
||||
bool isIRQPending() override { return lora.getIrqFlags() != 0; }
|
||||
|
||||
void setTCXOVoltage(float voltage) { tcxoVoltage = voltage; }
|
||||
|
||||
protected:
|
||||
float currentLimit = 140; // Higher OCP limit for SX126x PA
|
||||
float tcxoVoltage = 0.0;
|
||||
|
||||
/**
|
||||
* Specific module instance
|
||||
|
@ -10,6 +10,9 @@
|
||||
#endif
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do
|
||||
#if ARCH_PORTDUINO
|
||||
#define SX128X_MAX_POWER settingsMap[sx128x_max_power]
|
||||
#endif
|
||||
#ifndef SX128X_MAX_POWER
|
||||
#define SX128X_MAX_POWER 13
|
||||
#endif
|
||||
@ -315,4 +318,4 @@ template <typename T> bool SX128xInterface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
@ -74,11 +74,17 @@ template <class T> class TypedQueue
|
||||
{
|
||||
std::queue<T> q;
|
||||
concurrency::OSThread *reader = NULL;
|
||||
int maxElements;
|
||||
|
||||
public:
|
||||
explicit TypedQueue(int maxElements) {}
|
||||
explicit TypedQueue(int _maxElements) : maxElements(_maxElements) {}
|
||||
|
||||
int numFree() { return 1; } // Always claim 1 free, because we can grow to any size
|
||||
int numFree()
|
||||
{
|
||||
if (maxElements <= 0)
|
||||
return 1; // Always claim 1 free, because we can grow to any size
|
||||
return maxElements - numUsed();
|
||||
}
|
||||
|
||||
bool isEmpty() { return q.empty(); }
|
||||
|
||||
@ -86,6 +92,9 @@ template <class T> class TypedQueue
|
||||
|
||||
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
||||
{
|
||||
if (numFree() <= 0)
|
||||
return false;
|
||||
|
||||
if (reader) {
|
||||
reader->setInterval(0);
|
||||
concurrency::mainDelay.interrupt();
|
||||
@ -112,4 +121,4 @@ template <class T> class TypedQueue
|
||||
|
||||
void setReader(concurrency::OSThread *t) { reader = t; }
|
||||
};
|
||||
#endif
|
||||
#endif
|
@ -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" */
|
||||
|
@ -65,6 +65,9 @@ mail: marchammermann@googlemail.com
|
||||
#define DEFAULT_REALM "default_realm"
|
||||
#define PREFIX ""
|
||||
|
||||
#define KEY_PATH settingsStrings[websslkeypath].c_str()
|
||||
#define CERT_PATH settingsStrings[websslcertpath].c_str()
|
||||
|
||||
struct _file_config configWeb;
|
||||
|
||||
// We need to specify some content-type mapping, so the resources get delivered with the
|
||||
@ -384,13 +387,13 @@ char *read_file_into_string(const char *filename)
|
||||
int PiWebServerThread::CheckSSLandLoad()
|
||||
{
|
||||
// read certificate
|
||||
cert_pem = read_file_into_string("certificate.pem");
|
||||
cert_pem = read_file_into_string(CERT_PATH);
|
||||
if (cert_pem == NULL) {
|
||||
LOG_ERROR("ERROR SSL Certificate File can't be loaded or is missing");
|
||||
return 1;
|
||||
}
|
||||
// read private key
|
||||
key_pem = read_file_into_string("private_key.pem");
|
||||
key_pem = read_file_into_string(KEY_PATH);
|
||||
if (key_pem == NULL) {
|
||||
LOG_ERROR("ERROR file private_key can't be loaded or is missing");
|
||||
return 2;
|
||||
@ -415,8 +418,8 @@ int PiWebServerThread::CreateSSLCertificate()
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Ope file to write private key file
|
||||
FILE *pkey_file = fopen("private_key.pem", "wb");
|
||||
// Open file to write private key file
|
||||
FILE *pkey_file = fopen(KEY_PATH, "wb");
|
||||
if (!pkey_file) {
|
||||
LOG_ERROR("Error opening private key file");
|
||||
return 3;
|
||||
@ -426,18 +429,19 @@ int PiWebServerThread::CreateSSLCertificate()
|
||||
fclose(pkey_file);
|
||||
|
||||
// open Certificate file
|
||||
FILE *x509_file = fopen("certificate.pem", "wb");
|
||||
FILE *x509_file = fopen(CERT_PATH, "wb");
|
||||
if (!x509_file) {
|
||||
LOG_ERROR("Error opening cert");
|
||||
return 4;
|
||||
}
|
||||
// write cirtificate
|
||||
// write certificate
|
||||
PEM_write_X509(x509_file, x509);
|
||||
fclose(x509_file);
|
||||
|
||||
EVP_PKEY_free(pkey);
|
||||
LOG_INFO("Create SSL Key %s successful", KEY_PATH);
|
||||
X509_free(x509);
|
||||
LOG_INFO("Create SSL Cert -certificate.pem- succesfull ");
|
||||
LOG_INFO("Create SSL Cert %s successful", CERT_PATH);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -81,7 +81,7 @@ int32_t DetectionSensorModule::runOnce()
|
||||
}
|
||||
LOG_INFO("Detection Sensor Module: init");
|
||||
|
||||
return DELAYED_INTERVAL;
|
||||
return setStartDelay();
|
||||
}
|
||||
|
||||
// LOG_DEBUG("Detection Sensor Module: Current pin state: %i", digitalRead(moduleConfig.detection_sensor.monitor_pin));
|
||||
@ -161,4 +161,4 @@ bool DetectionSensorModule::hasDetectionEvent()
|
||||
bool currentState = digitalRead(moduleConfig.detection_sensor.monitor_pin);
|
||||
// LOG_DEBUG("Detection Sensor Module: Current state: %i", currentState);
|
||||
return (moduleConfig.detection_sensor.detection_trigger_type & 1) ? currentState : !currentState;
|
||||
}
|
||||
}
|
@ -97,8 +97,9 @@ NodeInfoModule::NodeInfoModule()
|
||||
: ProtobufModule("nodeinfo", meshtastic_PortNum_NODEINFO_APP, &meshtastic_User_msg), concurrency::OSThread("NodeInfo")
|
||||
{
|
||||
isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others
|
||||
setIntervalFromNow(30 *
|
||||
1000); // Send our initial owner announcement 30 seconds after we start (to give network time to setup)
|
||||
|
||||
setIntervalFromNow(setStartDelay()); // Send our initial owner announcement 30 seconds
|
||||
// after we start (to give network time to setup)
|
||||
}
|
||||
|
||||
int32_t NodeInfoModule::runOnce()
|
||||
@ -112,4 +113,4 @@ int32_t NodeInfoModule::runOnce()
|
||||
sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies)
|
||||
}
|
||||
return Default::getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_node_info_broadcast_secs);
|
||||
}
|
||||
}
|
@ -28,8 +28,9 @@ PositionModule::PositionModule()
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)
|
||||
setIntervalFromNow(60 * 1000);
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) {
|
||||
setIntervalFromNow(setStartDelay());
|
||||
}
|
||||
|
||||
// Power saving trackers should clear their position on startup to avoid waking up and sending a stale position
|
||||
if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
|
||||
@ -160,7 +161,8 @@ bool PositionModule::hasGPS()
|
||||
#endif
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *PositionModule::allocReply()
|
||||
// Allocate a packet with our position data if we have one
|
||||
meshtastic_MeshPacket *PositionModule::allocPositionPacket()
|
||||
{
|
||||
if (precision == 0) {
|
||||
LOG_DEBUG("Skip location send because precision is set to 0!");
|
||||
@ -262,7 +264,8 @@ meshtastic_MeshPacket *PositionModule::allocReply()
|
||||
p.has_ground_speed = true;
|
||||
}
|
||||
|
||||
LOG_INFO("Position reply: time=%i lat=%i lon=%i", p.time, p.latitude_i, p.longitude_i);
|
||||
LOG_INFO("Position packet: time=%i lat=%i lon=%i", p.time, p.latitude_i, p.longitude_i);
|
||||
lastSentToMesh = millis();
|
||||
|
||||
// TAK Tracker devices should send their position in a TAK packet over the ATAK port
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)
|
||||
@ -271,6 +274,16 @@ meshtastic_MeshPacket *PositionModule::allocReply()
|
||||
return allocDataProtobuf(p);
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *PositionModule::allocReply()
|
||||
{
|
||||
if (lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 3 * 60 * 1000)) {
|
||||
LOG_DEBUG("Skip Position reply since we sent it <3min ago");
|
||||
ignoreRequest = true; // Mark it as ignored for MeshModule
|
||||
return nullptr;
|
||||
}
|
||||
return allocPositionPacket();
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *PositionModule::allocAtakPli()
|
||||
{
|
||||
LOG_INFO("Send TAK PLI packet");
|
||||
@ -333,9 +346,9 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha
|
||||
precision = 0;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *p = allocReply();
|
||||
meshtastic_MeshPacket *p = allocPositionPacket();
|
||||
if (p == nullptr) {
|
||||
LOG_DEBUG("allocReply returned a nullptr");
|
||||
LOG_DEBUG("allocPositionPacket returned a nullptr");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,7 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
meshtastic_MeshPacket *allocPositionPacket();
|
||||
struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition);
|
||||
meshtastic_MeshPacket *allocAtakPli();
|
||||
void trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate = false);
|
||||
@ -62,6 +63,7 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
|
||||
void sendLostAndFoundText();
|
||||
bool hasQualityTimesource();
|
||||
bool hasGPS();
|
||||
uint32_t lastSentToMesh = 0; // Last time we sent our position to the mesh
|
||||
|
||||
const uint32_t minimumTimeThreshold =
|
||||
Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30);
|
||||
|
@ -50,12 +50,12 @@ int32_t AirQualityTelemetryModule::runOnce()
|
||||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address;
|
||||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second =
|
||||
i2cScanner->fetchI2CBus(found.address);
|
||||
return 1000;
|
||||
return setStartDelay();
|
||||
}
|
||||
#endif
|
||||
return disable();
|
||||
}
|
||||
return 1000;
|
||||
return setStartDelay();
|
||||
}
|
||||
return disable();
|
||||
} else {
|
||||
|
@ -18,7 +18,7 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
|
||||
uptimeWrapCount = 0;
|
||||
uptimeLastMs = millis();
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
setIntervalFromNow(45 * 1000); // Wait until NodeInfo is sent
|
||||
setIntervalFromNow(setStartDelay()); // Wait until NodeInfo is sent
|
||||
}
|
||||
virtual bool wantUIFrame() { return false; }
|
||||
|
||||
@ -62,4 +62,4 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
|
||||
|
||||
uint32_t uptimeWrapCount;
|
||||
uint32_t uptimeLastMs;
|
||||
};
|
||||
};
|
@ -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;
|
||||
@ -105,8 +107,6 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
|
||||
if (moduleConfig.telemetry.environment_measurement_enabled) {
|
||||
LOG_INFO("Environment Telemetry: init");
|
||||
// it's possible to have this module enabled, only for displaying values on the screen.
|
||||
// therefore, we should only enable the sensor loop if measurement is also enabled
|
||||
#ifdef SENSECAP_INDICATOR
|
||||
result = indicatorSensor.runOnce();
|
||||
#endif
|
||||
@ -115,6 +115,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,9 +161,17 @@ 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;
|
||||
// it's possible to have this module enabled, only for displaying values on the screen.
|
||||
// therefore, we should only enable the sensor loop if measurement is also enabled
|
||||
return result == UINT32_MAX ? disable() : setStartDelay();
|
||||
} else {
|
||||
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
|
||||
if (!moduleConfig.telemetry.environment_measurement_enabled) {
|
||||
@ -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)
|
||||
|
@ -62,7 +62,7 @@ int32_t HealthTelemetryModule::runOnce()
|
||||
if (max30102Sensor.hasSensor())
|
||||
result = max30102Sensor.runOnce();
|
||||
}
|
||||
return result;
|
||||
return result == UINT32_MAX ? disable() : setStartDelay();
|
||||
} else {
|
||||
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
|
||||
if (!moduleConfig.telemetry.health_measurement_enabled) {
|
||||
|
@ -65,7 +65,7 @@ int32_t PowerTelemetryModule::runOnce()
|
||||
if (max17048Sensor.hasSensor() && !max17048Sensor.isInitialized())
|
||||
result = max17048Sensor.runOnce();
|
||||
}
|
||||
return result;
|
||||
return result == UINT32_MAX ? disable() : setStartDelay();
|
||||
#else
|
||||
return disable();
|
||||
#endif
|
||||
@ -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
|
||||
|
@ -217,6 +217,7 @@ bool isPrivateIpAddress(const IPAddress &ip)
|
||||
{.network = 169u << 24 | 254 << 16, .mask = 0xffff0000}, // 169.254.0.0/16
|
||||
{.network = 10u << 24, .mask = 0xff000000}, // 10.0.0.0/8
|
||||
{.network = 127u << 24 | 1, .mask = 0xffffffff}, // 127.0.0.1/32
|
||||
{.network = 100u << 24 | 64 << 16, .mask = 0xffc00000}, // 100.64.0.0/10
|
||||
};
|
||||
const uint32_t addr = ntohl(ip);
|
||||
for (const auto &cidrRange : privateCidrRanges) {
|
||||
|
@ -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
|
||||
@ -368,6 +369,12 @@ bool loadConfig(const char *configPath)
|
||||
}
|
||||
}
|
||||
|
||||
settingsMap[sx126x_max_power] = yamlConfig["Lora"]["SX126X_MAX_POWER"].as<int>(22);
|
||||
settingsMap[sx128x_max_power] = yamlConfig["Lora"]["SX128X_MAX_POWER"].as<int>(13);
|
||||
settingsMap[lr1110_max_power] = yamlConfig["Lora"]["LR1110_MAX_POWER"].as<int>(22);
|
||||
settingsMap[lr1120_max_power] = yamlConfig["Lora"]["LR1120_MAX_POWER"].as<int>(13);
|
||||
settingsMap[rf95_max_power] = yamlConfig["Lora"]["RF95_MAX_POWER"].as<int>(20);
|
||||
|
||||
settingsMap[dio2_as_rf_switch] = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as<bool>(false);
|
||||
settingsMap[dio3_tcxo_voltage] = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<float>(0) * 1000;
|
||||
if (settingsMap[dio3_tcxo_voltage] == 0 && yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<bool>(false)) {
|
||||
@ -517,7 +524,12 @@ bool loadConfig(const char *configPath)
|
||||
|
||||
if (yamlConfig["Webserver"]) {
|
||||
settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as<int>(-1);
|
||||
settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as<std::string>("");
|
||||
settingsStrings[webserverrootpath] =
|
||||
(yamlConfig["Webserver"]["RootPath"]).as<std::string>("/usr/share/meshtasticd/web");
|
||||
settingsStrings[websslkeypath] =
|
||||
(yamlConfig["Webserver"]["SSLKey"]).as<std::string>("/etc/meshtasticd/ssl/private_key.pem");
|
||||
settingsStrings[websslcertpath] =
|
||||
(yamlConfig["Webserver"]["SSLCert"]).as<std::string>("/etc/meshtasticd/ssl/certificate.pem");
|
||||
}
|
||||
|
||||
if (yamlConfig["General"]) {
|
||||
|
@ -27,6 +27,11 @@ enum configNames {
|
||||
sx126x_ant_sw_pin,
|
||||
sx126x_ant_sw_line,
|
||||
sx126x_ant_sw_gpiochip,
|
||||
sx126x_max_power,
|
||||
sx128x_max_power,
|
||||
lr1110_max_power,
|
||||
lr1120_max_power,
|
||||
rf95_max_power,
|
||||
dio2_as_rf_switch,
|
||||
dio3_tcxo_voltage,
|
||||
use_rf95,
|
||||
@ -76,6 +81,8 @@ enum configNames {
|
||||
webserver,
|
||||
webserverport,
|
||||
webserverrootpath,
|
||||
websslkeypath,
|
||||
websslcertpath,
|
||||
maxtophone,
|
||||
maxnodes,
|
||||
ascii_logs,
|
||||
@ -94,4 +101,4 @@ int initGPIOPin(int pinNum, std::string gpioChipname, int line);
|
||||
bool loadConfig(const char *configPath);
|
||||
static bool ends_with(std::string_view str, std::string_view suffix);
|
||||
void getMacAddr(uint8_t *dmac);
|
||||
bool MAC_from_string(std::string mac_str, uint8_t *dmac);
|
||||
bool MAC_from_string(std::string mac_str, uint8_t *dmac);
|
@ -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
|
||||
|
||||
|
@ -29,9 +29,13 @@
|
||||
// "USERPREFS_FIXED_GPS_LON": "2.294508368",
|
||||
// "USERPREFS_LORACONFIG_CHANNEL_NUM": "31",
|
||||
// "USERPREFS_LORACONFIG_MODEM_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST",
|
||||
// "USERPREFS_SPLASH_TITLE": "DEFCONtastic",
|
||||
"USERPREFS_TZ_STRING": "tzplaceholder "
|
||||
// "USERPREFS_USE_ADMIN_KEY_0": "{ 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 }",
|
||||
// "USERPREFS_USE_ADMIN_KEY_1": "{}",
|
||||
// "USERPREFS_USE_ADMIN_KEY_2": "{}"
|
||||
}
|
||||
// "USERPREFS_USE_ADMIN_KEY_2": "{}",
|
||||
// "USERPREFS_OEM_TEXT": "Caterham Car Club",
|
||||
// "USERPREFS_OEM_FONT_SIZE": "0",
|
||||
// "USERPREFS_OEM_IMAGE_WIDTH": "50",
|
||||
// "USERPREFS_OEM_IMAGE_HEIGHT": "28",
|
||||
// "USERPREFS_OEM_IMAGE_DATA": "{ 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x67, 0x00, 0x00, 0x00, 0x18, 0x1F, 0xF0, 0x67, 0x00, 0x00, 0x00, 0x30, 0x1F, 0xF8, 0x33, 0x00, 0x00, 0x00, 0x30, 0x00, 0xFC, 0x31, 0x00, 0x00, 0x00, 0x60, 0x00, 0xFE, 0x18, 0x00, 0x00, 0x00, 0x60, 0x00, 0x7E, 0x18, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, 0x0C, 0x00, 0x00, 0x00, 0xC0, 0x80, 0x1F, 0x0C, 0x00, 0x00, 0x00, 0x80, 0x81, 0x1F, 0x06, 0x00, 0x00, 0x00, 0x80, 0xC1, 0x0F, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x8F, 0x01, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xC7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00}"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -183,8 +183,7 @@ settings.
|
||||
*/
|
||||
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL
|
||||
extern float tcxoVoltage; // make this available everywhere
|
||||
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -14,7 +14,9 @@ lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
zinggjm/GxEPD2@^1.5.1
|
||||
adafruit/Adafruit NeoPixel @ ^1.12.0
|
||||
build_unflags = -DARDUINO_USB_MODE=1
|
||||
build_unflags =
|
||||
${esp32s3_base.build_unflags}
|
||||
-DARDUINO_USB_MODE=1
|
||||
build_flags =
|
||||
;${esp32_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_eink
|
||||
${esp32_base.build_flags} -D PRIVATE_HW -I variants/my_esp32s3_diy_eink
|
||||
|
@ -13,7 +13,9 @@ platform_packages =
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
adafruit/Adafruit NeoPixel @ ^1.12.0
|
||||
build_unflags = -DARDUINO_USB_MODE=1
|
||||
build_unflags =
|
||||
${esp32s3_base.build_unflags}
|
||||
-DARDUINO_USB_MODE=1
|
||||
build_flags =
|
||||
;${esp32_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_oled
|
||||
${esp32_base.build_flags} -D PRIVATE_HW -I variants/my_esp32s3_diy_oled
|
||||
|
@ -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)
|
||||
|
@ -70,5 +70,8 @@
|
||||
#define SX126X_RESET LORA_RESET
|
||||
#define SX126X_DIO2_AS_RF_SWITCH
|
||||
|
||||
#define TCXO_OPTIONAL // handle Indicator V1 and V2
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||
|
||||
#define USE_VIRTUAL_KEYBOARD 1
|
||||
#define DISPLAY_CLOCK_FRAME 1
|
||||
|
@ -1,4 +1,4 @@
|
||||
[VERSION]
|
||||
major = 2
|
||||
minor = 5
|
||||
build = 19
|
||||
build = 21
|
||||
|
Loading…
Reference in New Issue
Block a user