Compare commits

..

4 Commits

Author SHA1 Message Date
Ben Meadors
009830bde9 Merge branch 'master' into custom-esp32 2024-07-24 15:55:13 -05:00
Ben Meadors
50bf56132a Merge branch 'master' into custom-esp32 2024-07-23 11:52:29 -05:00
Ben Meadors
6c6a1d9530 Merge branch 'master' into custom-esp32 2024-07-23 10:22:41 -05:00
Jonathan Bennett
1b5edeb615 Add custom esp32 libs to save some iram space 2024-07-23 09:46:08 -05:00
273 changed files with 3018 additions and 7107 deletions

View File

@@ -1,24 +0,0 @@
FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12
# [Optional] Uncomment this section to install additional packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends \
ca-certificates \
g++ \
git \
libbluetooth-dev \
libgpiod-dev \
liborcania-dev \
libssl-dev \
libulfius-dev \
libyaml-cpp-dev \
pkg-config \
python3 \
python3-pip \
python3-venv \
python3-wheel \
wget \
zip \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN pip3 install --no-cache-dir -U platformio==6.1.15

View File

@@ -1,28 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/cpp
{
"name": "Meshtastic Firmware Dev",
"build": {
"dockerfile": "Dockerfile"
},
"features": {
"ghcr.io/devcontainers/features/python:1": {
"installTools": true,
"version": "latest"
}
},
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.cpptools",
"platformio.platformio-ide",
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [ 4403 ],
// Run commands to prepare the container for use
"postCreateCommand": ".devcontainer/setup.sh",
}

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env sh
git submodule update --init

View File

@@ -11,7 +11,7 @@ runs:
ref: ${{github.event.pull_request.head.ref}} ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}} repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Install dependencies - name: Install dependencies
shell: bash shell: bash
run: | run: |
sudo apt-get -y update --fix-missing sudo apt-get -y update --fix-missing
@@ -22,21 +22,19 @@ runs:
with: with:
python-version: 3.x python-version: 3.x
# - name: Cache python libs - name: Cache python libs
# uses: actions/cache@v4 uses: actions/cache@v4
# id: cache-pip # needed in if test id: cache-pip # needed in if test
# with: with:
# path: ~/.cache/pip path: ~/.cache/pip
# key: ${{ runner.os }}-pip key: ${{ runner.os }}-pip
- name: Upgrade python tools - name: Upgrade python tools
shell: bash shell: bash
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -U --no-build-isolation --no-cache-dir "setuptools<72" pip install -U platformio adafruit-nrfutil
pip install -U platformio adafruit-nrfutil --no-build-isolation pip install -U meshtastic --pre
pip install -U poetry --no-build-isolation
pip install -U meshtastic --pre --no-build-isolation
- name: Upgrade platformio - name: Upgrade platformio
shell: bash shell: bash

View File

@@ -1,33 +0,0 @@
name: Build STM32
on:
workflow_call:
inputs:
board:
required: true
type: string
jobs:
build-stm32:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Build STM32
run: bin/build-stm32.sh ${{ inputs.board }}
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
overwrite: true
path: |
release/*.hex
release/*.bin

View File

@@ -24,7 +24,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, stm32, check] arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, check]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- id: checkout - id: checkout
@@ -41,7 +41,6 @@ jobs:
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
rp2040: ${{ steps.jsonStep.outputs.rp2040 }} rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
stm32: ${{ steps.jsonStep.outputs.stm32 }}
check: ${{ steps.jsonStep.outputs.check }} check: ${{ steps.jsonStep.outputs.check }}
check: check:
@@ -104,15 +103,6 @@ jobs:
with: with:
board: ${{ matrix.board }} board: ${{ matrix.board }}
build-stm32:
needs: setup
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
uses: ./.github/workflows/build_stm32.yml
with:
board: ${{ matrix.board }}
package-raspbian: package-raspbian:
uses: ./.github/workflows/package_raspbian.yml uses: ./.github/workflows/package_raspbian.yml
@@ -144,7 +134,6 @@ jobs:
build-esp32-c3, build-esp32-c3,
build-nrf52, build-nrf52,
build-rpi2040, build-rpi2040,
build-stm32,
package-raspbian, package-raspbian,
package-raspbian-armv7l, package-raspbian-armv7l,
package-native, package-native,

View File

@@ -1,57 +0,0 @@
name: Test Simulator
on:
schedule:
- cron: "0 0 * * *" # Run every day at midnight
workflow_dispatch: {}
jobs:
test-simulator:
runs-on: ubuntu-latest
steps:
- name: Install libbluetooth
shell: bash
run: |
sudo apt-get update --fix-missing
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Upgrade python tools
shell: bash
run: |
python -m pip install --upgrade pip
pip install -U platformio adafruit-nrfutil
pip install -U meshtastic --pre
- name: Upgrade platformio
shell: bash
run: |
pio upgrade
- name: Build Native
run: bin/build-native.sh
# We now run integration test before other build steps (to quickly see runtime failures)
- name: Build for native
run: platformio run -e native
- name: Integration test
run: |
.pio/build/native/program & sleep 10 # 5 seconds was not enough
echo "Simulator started, launching python test..."
python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
- name: PlatformIO Tests
run: platformio test -e native --junit-output-path testreport.xml
- name: Test Report
uses: dorny/test-reporter@v1.9.1
if: success() || failure() # run this step even if previous step failed
with:
name: PlatformIO Tests
path: testreport.xml
reporter: java-junit

View File

@@ -1,2 +0,0 @@
tasks:
- init: pip install platformio && pip install --upgrade pip

View File

@@ -48,7 +48,6 @@ lib_deps =
https://github.com/dbSuS/libpax.git#7bcd3fcab75037505be9b122ab2b24cc5176b587 https://github.com/dbSuS/libpax.git#7bcd3fcab75037505be9b122ab2b24cc5176b587
https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6
https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f
rweather/Crypto@^0.4.0
lib_ignore = lib_ignore =
segger_rtt segger_rtt

View File

@@ -1,208 +0,0 @@
/*
* lfs utility functions
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
// MESHTASTIC/@geeksville note: This file is copied from the Adafruit nrf52 arduino lib. And we use a special -include in
// nrf52.ini to load it before EVERY file we do this hack because the default definitions for LFS_ASSERT are quite poor and we
// don't want to fork the adafruit lib (again) and send in a PR that they probably won't merge anyways. This file might break if
// they ever update lfs.util on their side, in which case we'll need to update this file to match their new version. The version
// this is a copy from is almost exactly
// https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/c25d93268a3b9c23e9a1ccfcaf9b208beca624ca/libraries/Adafruit_LittleFS/src/littlefs/lfs_util.h
#ifndef LFS_UTIL_H
#define LFS_UTIL_H
// Users can override lfs_util.h with their own configuration by defining
// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
//
// If LFS_CONFIG is used, none of the default utils will be emitted and must be
// provided by the config file. To start I would suggest copying lfs_util.h and
// modifying as needed.
#ifdef LFS_CONFIG
#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
#define LFS_STRINGIZE2(x) #x
#include LFS_STRINGIZE(LFS_CONFIG)
#else
// System includes
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#ifndef LFS_NO_MALLOC
#include <stdlib.h>
#endif
#ifndef LFS_NO_ASSERT
#include <assert.h>
#endif
#if !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR)
#include <stdio.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
// Macros, may be replaced by system specific wrappers. Arguments to these
// macros must not have side-effects as the macros can be removed for a smaller
// code footprint
// Logging functions
#ifndef LFS_NO_DEBUG
void logLegacy(const char *level, const char *fmt, ...);
#define LFS_DEBUG(fmt, ...) logLegacy("DEBUG", "lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else
#define LFS_DEBUG(fmt, ...)
#endif
#ifndef LFS_NO_WARN
#define LFS_WARN(fmt, ...) logLegacy("WARN", "lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else
#define LFS_WARN(fmt, ...)
#endif
#ifndef LFS_NO_ERROR
#define LFS_ERROR(fmt, ...) logLegacy("ERROR", "lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else
#define LFS_ERROR(fmt, ...)
#endif
// Runtime assertions
#ifndef LFS_NO_ASSERT
#define LFS_ASSERT(test) assert(test)
#else
extern void lfs_assert(const char *reason);
#define LFS_ASSERT(test) \
if (!(test)) \
lfs_assert(#test)
#endif
// Builtin functions, these may be replaced by more efficient
// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more
// expensive basic C implementation for debugging purposes
// Min/max functions for unsigned 32-bit numbers
static inline uint32_t lfs_max(uint32_t a, uint32_t b)
{
return (a > b) ? a : b;
}
static inline uint32_t lfs_min(uint32_t a, uint32_t b)
{
return (a < b) ? a : b;
}
// Find the next smallest power of 2 less than or equal to a
static inline uint32_t lfs_npw2(uint32_t a)
{
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return 32 - __builtin_clz(a - 1);
#else
uint32_t r = 0;
uint32_t s;
a -= 1;
s = (a > 0xffff) << 4;
a >>= s;
r |= s;
s = (a > 0xff) << 3;
a >>= s;
r |= s;
s = (a > 0xf) << 2;
a >>= s;
r |= s;
s = (a > 0x3) << 1;
a >>= s;
r |= s;
return (r | (a >> 1)) + 1;
#endif
}
// Count the number of trailing binary zeros in a
// lfs_ctz(0) may be undefined
static inline uint32_t lfs_ctz(uint32_t a)
{
#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__)
return __builtin_ctz(a);
#else
return lfs_npw2((a & -a) + 1) - 1;
#endif
}
// Count the number of binary ones in a
static inline uint32_t lfs_popc(uint32_t a)
{
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return __builtin_popcount(a);
#else
a = a - ((a >> 1) & 0x55555555);
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
#endif
}
// Find the sequence comparison of a and b, this is the distance
// between a and b ignoring overflow
static inline int lfs_scmp(uint32_t a, uint32_t b)
{
return (int)(unsigned)(a - b);
}
// Convert from 32-bit little-endian to native order
static inline uint32_t lfs_fromle32(uint32_t a)
{
#if !defined(LFS_NO_INTRINSICS) && ((defined(BYTE_ORDER) && BYTE_ORDER == ORDER_LITTLE_ENDIAN) || \
(defined(__BYTE_ORDER) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
return a;
#elif !defined(LFS_NO_INTRINSICS) && \
((defined(BYTE_ORDER) && BYTE_ORDER == ORDER_BIG_ENDIAN) || (defined(__BYTE_ORDER) && __BYTE_ORDER == __ORDER_BIG_ENDIAN) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
return __builtin_bswap32(a);
#else
return (((uint8_t *)&a)[0] << 0) | (((uint8_t *)&a)[1] << 8) | (((uint8_t *)&a)[2] << 16) | (((uint8_t *)&a)[3] << 24);
#endif
}
// Convert to 32-bit little-endian from native order
static inline uint32_t lfs_tole32(uint32_t a)
{
return lfs_fromle32(a);
}
// Calculate CRC-32 with polynomial = 0x04c11db7
void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
// Allocate memory, only used if buffers are not provided to littlefs
static inline void *lfs_malloc(size_t size)
{
#ifndef LFS_NO_MALLOC
extern void *pvPortMalloc(size_t xWantedSize);
return pvPortMalloc(size);
#else
(void)size;
return NULL;
#endif
}
// Deallocate memory, only used if buffers are not provided to littlefs
static inline void lfs_free(void *p)
{
#ifndef LFS_NO_MALLOC
extern void vPortFree(void *pv);
vPortFree(p);
#else
(void)p;
#endif
}
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif
#endif

View File

@@ -2,13 +2,9 @@
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files ; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
platform = platformio/nordicnrf52@^10.5.0 platform = platformio/nordicnrf52@^10.5.0
extends = arduino_base extends = arduino_base
platform_packages =
; our custom Git version until they merge our PR
framework-arduinoadafruitnrf52 @ https://github.com/geeksville/Adafruit_nRF52_Arduino.git
build_type = debug build_type = debug
build_flags = build_flags =
-include arch/nrf52/cpp_overrides/lfs_util.h
${arduino_base.build_flags} ${arduino_base.build_flags}
-DSERIAL_BUFFER_SIZE=1024 -DSERIAL_BUFFER_SIZE=1024
-Wno-unused-variable -Wno-unused-variable
@@ -20,7 +16,6 @@ build_src_filter =
lib_deps= lib_deps=
${arduino_base.lib_deps} ${arduino_base.lib_deps}
rweather/Crypto@^0.4.0
lib_ignore = lib_ignore =
BluetoothOTA BluetoothOTA

View File

@@ -7,72 +7,3 @@ lib_deps =
${nrf52_base.lib_deps} ${nrf52_base.lib_deps}
${environmental_base.lib_deps} ${environmental_base.lib_deps}
https://github.com/Kongduino/Adafruit_nRFCrypto.git#e31a8825ea3300b163a0a3c1ddd5de34e10e1371 https://github.com/Kongduino/Adafruit_nRFCrypto.git#e31a8825ea3300b163a0a3c1ddd5de34e10e1371
; Common NRF52 debugging settings follow. See the Meshtastic developer docs for how to connect SWD debugging probes to your board.
; We want the initial breakpoint at setup() instead of main(). Also we want to enable semihosting at that point so instead of
debug_init_break = tbreak setup
; we just turn off the platformio tbreak and do it in .gdbinit (where we have more flexibility for scripting)
; also we use a permanent breakpoint so it gets reused each time we restart the debugging session?
; debug_init_break = tbreak main
; Note: add "monitor arm semihosting_redirect tcp 4444 all" if you want the stdout from the device to go to that port number instead
; (for use by meshtastic command line)
; monitor arm semihosting disable
; monitor debug_level 3
;
; IMPORTANT: fileio must be disabled before using port 5555 - openocd ver 0.12 has a bug where if enabled it never properly parses the special :tt name
; for stdio access.
; monitor arm semihosting_redirect tcp 5555 stdio
; Also note: it is _impossible_ to do non blocking reads on the semihost console port (an oversight when ARM specified the semihost API).
; So we'll neve be able to general purpose bi-directional communication with the device over semihosting.
debug_extra_cmds =
echo Running .gdbinit script
;monitor arm semihosting enable
;monitor arm semihosting_fileio enable
;monitor arm semihosting_redirect disable
commands 1
; echo Breakpoint at setup() has semihosting console, connect to it with "telnet localhost 5555"
; set wantSemihost = 1
set useSoftDevice = 0
end
; Only reprogram the board if the code has changed
debug_load_mode = modified
;debug_load_mode = manual
; We default to the stlink adapter because it is very cheap and works well, though others (such as jlink) are also supported.
;debug_tool = jlink
debug_tool = stlink
debug_speed = 4000
;debug_tool = custom
; debug_server =
; openocd
; -f
; /usr/local/share/openocd/scripts/interface/stlink.cfg
; -f
; /usr/local/share/openocd/scripts/target/nrf52.cfg
; $PLATFORMIO_CORE_DIR/packages/tool-openocd/openocd/scripts/interface/cmsis-dap.cfg
; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!)
; programming time is about the same as the bootloader version.
; For information on this see the meshtastic developers documentation for "Development on the NRF52"
; We manually pass in the elf file so that pyocd can reverse engineer FreeRTOS data (running threads, etc...)
;debug_server =
; pyocd
; gdbserver
; -j
; ${platformio.workspace_dir}/..
; -t
; nrf52840
; --semihosting
; --elf
; ${platformio.build_dir}/${this.__env__}/firmware.elf
; If you want to debug the semihosting support you can turn on extra logging in pyocd with
; -L
; pyocd.debug.semihost.trace=debug
; The following is not needed because it automatically tries do this
;debug_server_ready_pattern = -.*GDB server started on port \d+.*
;debug_port = localhost:3333

View File

@@ -1,37 +0,0 @@
[stm32_base]
extends = arduino_base
platform = ststm32
platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32.git#361a7fdb67e2a7104e99b4f42a802469eef8b129
build_type = release
;board_build.flash_offset = 0x08000000
build_flags =
${arduino_base.build_flags}
-flto
-Isrc/platform/stm32wl -g
-DMESHTASTIC_MINIMIZE_BUILD
-DMESHTASTIC_EXCLUDE_GPS
-DDEBUG_MUTE
; -DVECT_TAB_OFFSET=0x08000000
-DconfigUSE_CMSIS_RTOS_V2=1
; -DSPI_MODE_0=SPI_MODE0
-fmerge-all-constants
-ffunction-sections
-fdata-sections
build_src_filter =
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/RemoteHardwareModule.cpp> -<platform/nrf52> -<platform/portduino> -<platform/rp2040> -<mesh/raspihttp>
board_upload.offset_address = 0x08000000
upload_protocol = stlink
lib_deps =
${env.lib_deps}
charlesbaynham/OSFS@^1.2.3
https://github.com/caveman99/Crypto.git#f61ae26a53f7a2d0ba5511625b8bf8eff3a35d5e
lib_ignore =
https://github.com/mathertel/OneButton@~2.6.1
Wire

28
arch/stm32/stm32wl5e.ini Normal file
View File

@@ -0,0 +1,28 @@
[stm32wl5e_base]
platform_packages = platformio/framework-arduinoststm32 @ https://github.com/stm32duino/Arduino_Core_STM32.git#6e3f9910d0122e82a6c3438507dfac3d2fd80a39
platform = ststm32
board = generic_wl5e
framework = arduino
build_type = debug
build_flags =
${arduino_base.build_flags}
-Isrc/platform/stm32wl -g
-DconfigUSE_CMSIS_RTOS_V2=1
-DVECT_TAB_OFFSET=0x08000000
build_src_filter =
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/Telemetry> -<platform/nrf52> -<platform/portduino> -<platform/rp2040> -<mesh/raspihttp>
board_upload.offset_address = 0x08000000
upload_protocol = stlink
lib_deps =
${env.lib_deps}
https://github.com/kokke/tiny-AES-c.git#f06ac37fc31dfdaca2e0d9bec83f90d5663c319b
https://github.com/littlefs-project/littlefs.git#v2.5.1
https://github.com/stm32duino/STM32FreeRTOS.git#10.3.1
lib_ignore =
mathertel/OneButton

Binary file not shown.

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1 platformio pkg update
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.* rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1 platformio pkg update
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.* rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1 platformio pkg update
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.* rm -f .pio/build/$1/firmware.*

View File

@@ -1,29 +0,0 @@
#!/usr/bin/env bash
set -e
VERSION=$(bin/buildinfo.py long)
SHORT_VERSION=$(bin/buildinfo.py short)
OUTDIR=release/
rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*
# The shell vars the build tool expects to find
export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
pio run --environment $1 # -v
SRCELF=.pio/build/$1/firmware.elf
cp $SRCELF $OUTDIR/$basename.elf
SRCBIN=.pio/build/$1/firmware.bin
cp $SRCBIN $OUTDIR/$basename.bin

View File

@@ -54,8 +54,6 @@ Lora:
# ch341_quirk: true # Uncomment this to use the chunked SPI transfer that seems to fix the ch341 # ch341_quirk: true # Uncomment this to use the chunked SPI transfer that seems to fix the ch341
# spiSpeed: 2000000
### Set gpio chip to use in /dev/. Defaults to 0. ### Set gpio chip to use in /dev/. Defaults to 0.
### Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4 ### Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4
# gpiochip: 4 # gpiochip: 4
@@ -113,9 +111,6 @@ Display:
# Height: 320 # Height: 320
# Rotate: true # Rotate: true
### You can also specify the spi device for the display to use
# spidev: spidev0.0
Touchscreen: Touchscreen:
### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching. ### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching.
@@ -131,21 +126,15 @@ Touchscreen:
# CS: 7 # CS: 7
# IRQ: 17 # IRQ: 17
### You can also specify the spi device for the touchscreen to use
# spidev: spidev0.0
Input:
### Configure device for direct keyboard input ### Configure device for direct keyboard input
Input:
# KeyboardDevice: /dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd # KeyboardDevice: /dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd
### ###
Logging: Logging:
LogLevel: info # debug, info, warn, error LogLevel: info # debug, info, warn, error
# TraceFile: /var/log/meshtasticd.json
# AsciiLogs: true # default if not specified is !isatty() on stdout
Webserver: Webserver:
# Port: 443 # Port for Webserver & Webservices # Port: 443 # Port for Webserver & Webservices
@@ -153,4 +142,3 @@ Webserver:
General: General:
MaxNodes: 200 MaxNodes: 200
MaxMessageQueue: 100

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821) # trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports # trunk-ignore-all(flake8/F821): For SConstruct imports
import sys import sys
@@ -8,6 +7,7 @@ from readprops import readProps
Import("env") Import("env")
platform = env.PioPlatform() platform = env.PioPlatform()
board = env.GetProjectOption("board")
def esp32_create_combined_bin(source, target, env): def esp32_create_combined_bin(source, target, env):
@@ -78,11 +78,9 @@ if platform.name == "espressif32":
else: else:
# For newer ESP32 targets, using newlib nano works better. # For newer ESP32 targets, using newlib nano works better.
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
if board == "ttgo-t-beam":
if platform.name == "nordicnrf52": print("patching esp32 libs")
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", env.Execute("tar -xvf bin/arduino-esp32-libs-release_*tar.gz -C ~/.platformio/packages/framework-arduinoespressif32/")
env.VerboseAction(f"{sys.executable} ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2",
"Generating UF2 file"))
Import("projenv") Import("projenv")
@@ -96,4 +94,4 @@ projenv.Append(
"-DAPP_VERSION=" + verObj["long"], "-DAPP_VERSION=" + verObj["long"],
"-DAPP_VERSION_SHORT=" + verObj["short"], "-DAPP_VERSION_SHORT=" + verObj["short"],
] ]
) )

View File

@@ -1,5 +1,9 @@
import configparser
import subprocess import subprocess
import configparser
import traceback
import sys
def readProps(prefsLoc): def readProps(prefsLoc):
@@ -7,36 +11,27 @@ def readProps(prefsLoc):
config = configparser.RawConfigParser() config = configparser.RawConfigParser()
config.read(prefsLoc) config.read(prefsLoc)
version = dict(config.items("VERSION")) version = dict(config.items('VERSION'))
verObj = dict( verObj = dict(short = "{}.{}.{}".format(version["major"], version["minor"], version["build"]),
short="{}.{}.{}".format(version["major"], version["minor"], version["build"]), long = "unset")
long="unset",
)
# Try to find current build SHA if if the workspace is clean. This could fail if git is not installed # Try to find current build SHA if if the workspace is clean. This could fail if git is not installed
try: try:
sha = ( sha = subprocess.check_output(
subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]) ['git', 'rev-parse', '--short', 'HEAD']).decode("utf-8").strip()
.decode("utf-8") isDirty = subprocess.check_output(
.strip() ['git', 'diff', 'HEAD']).decode("utf-8").strip()
)
isDirty = (
subprocess.check_output(["git", "diff", "HEAD"]).decode("utf-8").strip()
)
suffix = sha suffix = sha
# if isDirty: # if isDirty:
# # short for 'dirty', we want to keep our verstrings source for protobuf reasons # # short for 'dirty', we want to keep our verstrings source for protobuf reasons
# suffix = sha + "-d" # suffix = sha + "-d"
verObj["long"] = "{}.{}.{}.{}".format( verObj['long'] = "{}.{}.{}.{}".format(
version["major"], version["minor"], version["build"], suffix version["major"], version["minor"], version["build"], suffix)
)
except: except:
# print("Unexpected error:", sys.exc_info()[0]) # print("Unexpected error:", sys.exc_info()[0])
# traceback.print_exc() # traceback.print_exc()
verObj["long"] = verObj["short"] verObj['long'] = verObj['short']
# print("firmware version " + verStr) # print("firmware version " + verStr)
return verObj return verObj
# print("path is" + ','.join(sys.path)) # print("path is" + ','.join(sys.path))

View File

@@ -1,30 +0,0 @@
# tips from mark on how to replace the (broken) bootloader on the red wio_tracker_1110 boards.
~/.platformio/penv/bin/adafruit-nrfutil --verbose dfu serial --package wio_tracker_1110_bootloader-0.9.1_s140_7.3.0.zip -p /dev/ttyACM1 -b 115200 --singlebank --touch 1200
exit
# Output should look like
Upgrading target on /dev/ttyACM1 with DFU package /home/kevinh/development/meshtastic/WioWM1110/wio_tracker_1110_bootloader-0.9.1_s140_7.3.0.zip. Flow control is disabled, Single bank, Touch 1200
Touched serial port /dev/ttyACM1
Opened serial port /dev/ttyACM1
Starting DFU upgrade of type 3, SoftDevice size: 152728, bootloader size: 39000, application size: 0
Sending DFU start packet
Sending DFU init packet
Sending firmware file
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
###############
Activating new firmware
DFU upgrade took 20.242434978485107s
Device programmed.

View File

@@ -1,8 +1,5 @@
{ {
"build": { "build": {
"arduino": {
"variant_h": "variant_RAK3172_MODULE.h"
},
"core": "stm32", "core": "stm32",
"cpu": "cortex-m4", "cpu": "cortex-m4",
"extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_GENERIC_WLE5CCUX", "extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_GENERIC_WLE5CCUX",

View File

@@ -1,42 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [
["0x303A", "0x1001"],
["0x303A", "0x0002"]
],
"mcu": "esp32s3",
"variant": "heltec_vision_master_e290"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "Heltec Vision Master E290",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://heltec.org/project/vision-master-e290/",
"vendor": "Heltec"
}

View File

@@ -1,42 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [
["0x303A", "0x1001"],
["0x303A", "0x0002"]
],
"mcu": "esp32s3",
"variant": "heltec_vision_master_t190"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "Heltec Vision Master t190",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://heltec.org/project/vision-master-t190/",
"vendor": "Heltec"
}

View File

@@ -1,58 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v7.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "ME25LS01-BOOT",
"mcu": "nrf52840",
"variant": "MINEWSEMI_ME25LS01",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "7.3.0",
"sd_fwid": "0x0123"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Minesemi ME25LS01",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap",
"blackmagic"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://en.minewsemi.com/lora-module/lr1110-nrf52840-me25LS01l",
"vendor": "Minesemi"
}

View File

@@ -1,58 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v7.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "T1000-E-BOOT",
"mcu": "nrf52840",
"variant": "Seeed_T1000-E",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "7.3.0",
"sd_fwid": "0x0123"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Seeed T1000-E",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap",
"blackmagic"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-E-for-Meshtastic-p-5913.html",
"vendor": "Seeed Studio"
}

View File

@@ -5,24 +5,16 @@ Import("env")
# NOTE: This is not currently used, but can serve as an example on how to write extra_scripts # NOTE: This is not currently used, but can serve as an example on how to write extra_scripts
# print("Current CLI targets", COMMAND_LINE_TARGETS) print("Current CLI targets", COMMAND_LINE_TARGETS)
# print("Current Build targets", BUILD_TARGETS) print("Current Build targets", BUILD_TARGETS)
# print("CPP defs", env.get("CPPDEFINES")) print("CPP defs", env.get("CPPDEFINES"))
# print(env.Dump())
# Adafruit.py in the platformio build tree is a bit naive and always enables their USB stack for building. We don't want this. # Adafruit.py in the platformio build tree is a bit naive and always enables their USB stack for building. We don't want this.
# So come in after that python script has run and disable it. This hack avoids us having to fork that big project and send in a PR # So come in after that python script has run and disable it. This hack avoids us having to fork that big project and send in a PR
# which might not be accepted. -@geeksville # which might not be accepted. -@geeksville
lib_builders = env.get("__PIO_LIB_BUILDERS", None) env["CPPDEFINES"].remove("USBCON")
if lib_builders is not None: env["CPPDEFINES"].remove("USE_TINYUSB")
print("Disabling Adafruit USB stack")
for k in lib_builders:
if k.name == "Adafruit TinyUSB Library":
libenv = k.env
# print(f"{k.name }: { libenv.Dump() } ")
# libenv["CPPDEFINES"].remove("USBCON")
libenv["CPPDEFINES"].remove("USE_TINYUSB")
# Custom actions when building program/firmware # Custom actions when building program/firmware
# env.AddPreAction("buildprog", callback...) # env.AddPreAction("buildprog", callback...)

View File

@@ -18,7 +18,10 @@ import subprocess
import sys import sys
from platformio.project.exception import PlatformioException from platformio.project.exception import PlatformioException
from platformio.public import DeviceMonitorFilterBase, load_build_metadata from platformio.public import (
DeviceMonitorFilterBase,
load_build_metadata,
)
# By design, __init__ is called inside miniterm and we can't pass context to it. # By design, __init__ is called inside miniterm and we can't pass context to it.
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
@@ -29,7 +32,7 @@ IS_WINDOWS = sys.platform.startswith("win")
class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase): class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase):
NAME = "esp32_c3_exception_decoder" NAME = "esp32_c3_exception_decoder"
PCADDR_PATTERN = re.compile(r"0x4[0-9a-f]{7}", re.IGNORECASE) PCADDR_PATTERN = re.compile(r'0x4[0-9a-f]{7}', re.IGNORECASE)
def __call__(self): def __call__(self):
self.buffer = "" self.buffer = ""
@@ -72,14 +75,14 @@ See https://docs.platformio.org/page/projectconf/build_configurations.html
% self.__class__.__name__ % self.__class__.__name__
) )
return False return False
if not os.path.isfile(self.addr2line_path): if not os.path.isfile(self.addr2line_path):
sys.stderr.write( sys.stderr.write(
"%s: disabling, addr2line at %s does not exist\n" "%s: disabling, addr2line at %s does not exist\n"
% (self.__class__.__name__, self.addr2line_path) % (self.__class__.__name__, self.addr2line_path)
) )
return False return False
return True return True
except PlatformioException as e: except PlatformioException as e:
sys.stderr.write( sys.stderr.write(
@@ -114,7 +117,7 @@ See https://docs.platformio.org/page/projectconf/build_configurations.html
trace = self.get_backtrace(m) trace = self.get_backtrace(m)
if len(trace) != "": if len(trace) != "":
text = text[:last] + trace + text[last:] text = text[: last] + trace + text[last :]
last += len(trace) last += len(trace)
return text return text
@@ -122,10 +125,14 @@ See https://docs.platformio.org/page/projectconf/build_configurations.html
def get_backtrace(self, match): def get_backtrace(self, match):
trace = "\n" trace = "\n"
enc = "mbcs" if IS_WINDOWS else "utf-8" enc = "mbcs" if IS_WINDOWS else "utf-8"
args = [self.addr2line_path, "-fipC", "-e", self.firmware_path] args = [self.addr2line_path, u"-fipC", u"-e", self.firmware_path]
try: try:
addr = match.group() addr = match.group()
output = subprocess.check_output(args + [addr]).decode(enc).strip() output = (
subprocess.check_output(args + [addr])
.decode(enc)
.strip()
)
output = output.replace( output = output.replace(
"\n", "\n " "\n", "\n "
) # newlines happen with inlined methods ) # newlines happen with inlined methods

View File

@@ -34,7 +34,6 @@ default_envs = tbeam
;default_envs = wio-e5 ;default_envs = wio-e5
;default_envs = radiomaster_900_bandit_nano ;default_envs = radiomaster_900_bandit_nano
;default_envs = radiomaster_900_bandit_micro ;default_envs = radiomaster_900_bandit_micro
;default_envs = radiomaster_900_bandit
;default_envs = heltec_capsule_sensor_v3 ;default_envs = heltec_capsule_sensor_v3
;default_envs = heltec_vision_master_t190 ;default_envs = heltec_vision_master_t190
;default_envs = heltec_vision_master_e213 ;default_envs = heltec_vision_master_e213
@@ -46,7 +45,6 @@ extra_configs =
variants/*/platformio.ini variants/*/platformio.ini
[env] [env]
test_build_src = true
extra_scripts = bin/platformio-custom.py extra_scripts = bin/platformio-custom.py
; note: we add src to our include search path so that lmic_project_config can override ; note: we add src to our include search path so that lmic_project_config can override
@@ -88,7 +86,7 @@ monitor_filters = direct
lib_deps = lib_deps =
jgromes/RadioLib@~6.6.0 jgromes/RadioLib@~6.6.0
https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306
https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4
https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0 https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
@@ -157,4 +155,4 @@ lib_deps =
mprograms/QMC5883LCompass@^1.2.0 mprograms/QMC5883LCompass@^1.2.0
https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee

View File

@@ -1,4 +1,3 @@
#include "Observer.h"
#include "configuration.h" #include "configuration.h"
#ifdef HAS_NCP5623 #ifdef HAS_NCP5623
@@ -23,18 +22,10 @@ class AmbientLightingThread : public concurrency::OSThread
public: public:
explicit AmbientLightingThread(ScanI2C::DeviceType type) : OSThread("AmbientLightingThread") explicit AmbientLightingThread(ScanI2C::DeviceType type) : OSThread("AmbientLightingThread")
{ {
notifyDeepSleepObserver.observe(&notifyDeepSleep); // Let us know when shutdown() is issued.
// Enables Ambient Lighting by default if conditions are meet.
#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
#ifdef ENABLE_AMBIENTLIGHTING
moduleConfig.ambient_lighting.led_state = true;
#endif
#endif
// Uncomment to test module // Uncomment to test module
// moduleConfig.ambient_lighting.led_state = true; // moduleConfig.ambient_lighting.led_state = true;
// moduleConfig.ambient_lighting.current = 10; // moduleConfig.ambient_lighting.current = 10;
// Default to a color based on our node number // // Default to a color based on our node number
// moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16; // moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16;
// moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8; // moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8;
// moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF; // moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF;
@@ -91,46 +82,9 @@ class AmbientLightingThread : public concurrency::OSThread
return disable(); return disable();
} }
// When shutdown() is issued, setLightingOff will be called.
CallbackObserver<AmbientLightingThread, void *> notifyDeepSleepObserver =
CallbackObserver<AmbientLightingThread, void *>(this, &AmbientLightingThread::setLightingOff);
private: private:
ScanI2C::DeviceType _type = ScanI2C::DeviceType::NONE; ScanI2C::DeviceType _type = ScanI2C::DeviceType::NONE;
// Turn RGB lighting off, is used in junction to shutdown()
int setLightingOff(void *unused)
{
#ifdef HAS_NCP5623
rgb.setCurrent(0);
rgb.setRed(0);
rgb.setGreen(0);
rgb.setBlue(0);
LOG_INFO("Turn Off NCP5623 Ambient lighting.\n");
#endif
#ifdef HAS_NEOPIXEL
pixels.clear();
pixels.show();
LOG_INFO("Turn Off NeoPixel Ambient lighting.\n");
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - 0);
analogWrite(RGBLED_GREEN, 255 - 0);
analogWrite(RGBLED_BLUE, 255 - 0);
LOG_INFO("Turn Off Ambient lighting RGB Common Anode.\n");
#elif defined(RGBLED_RED)
analogWrite(RGBLED_RED, 0);
analogWrite(RGBLED_GREEN, 0);
analogWrite(RGBLED_BLUE, 0);
LOG_INFO("Turn Off Ambient lighting RGB Common Cathode.\n");
#endif
#ifdef UNPHONE
unphone.rgb(0, 0, 0);
LOG_INFO("Turn Off unPhone Ambient lighting.\n");
#endif
return 0;
}
void setLighting() void setLighting()
{ {
#ifdef HAS_NCP5623 #ifdef HAS_NCP5623
@@ -146,17 +100,6 @@ class AmbientLightingThread : public concurrency::OSThread
pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue), moduleConfig.ambient_lighting.blue),
0, NEOPIXEL_COUNT); 0, NEOPIXEL_COUNT);
// RadioMaster Bandit has addressable LED at the two buttons
// this allow us to set different lighting for them in variant.h file.
#ifdef RADIOMASTER_900_BANDIT
#if defined(BUTTON1_COLOR) && defined(BUTTON1_COLOR_INDEX)
pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1);
#endif
#if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX)
pixels.fill(BUTTON2_COLOR, BUTTON1_COLOR_INDEX, 1);
#endif
#endif
pixels.show(); pixels.show();
LOG_DEBUG("Initializing NeoPixel Ambient lighting w/ brightness(current)=%d, red=%d, green=%d, blue=%d\n", LOG_DEBUG("Initializing NeoPixel Ambient lighting w/ brightness(current)=%d, red=%d, green=%d, blue=%d\n",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,

View File

@@ -29,6 +29,7 @@ volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BU
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
OneButton ButtonThread::userButton; // Get reference to static member OneButton ButtonThread::userButton; // Get reference to static member
#endif #endif
ButtonThread::ButtonThread() : OSThread("Button") ButtonThread::ButtonThread() : OSThread("Button")
{ {
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
@@ -42,7 +43,7 @@ ButtonThread::ButtonThread() : OSThread("Button")
int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin
#if defined(HELTEC_CAPSULE_SENSOR_V3) #if defined(HELTEC_CAPSULE_SENSOR_V3)
this->userButton = OneButton(pin, false, false); this->userButton = OneButton(pin, false, false);
#elif defined(BUTTON_ACTIVE_LOW) #elif defined(BUTTON_ACTIVE_LOW) // change by WayenWeng
this->userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP); this->userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP);
#else #else
this->userButton = OneButton(pin, true, true); this->userButton = OneButton(pin, true, true);
@@ -52,7 +53,7 @@ ButtonThread::ButtonThread() : OSThread("Button")
#ifdef INPUT_PULLUP_SENSE #ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
#ifdef BUTTON_SENSE_TYPE #ifdef BUTTON_SENSE_TYPE // change by WayenWeng
pinMode(pin, BUTTON_SENSE_TYPE); pinMode(pin, BUTTON_SENSE_TYPE);
#else #else
pinMode(pin, INPUT_PULLUP_SENSE); pinMode(pin, INPUT_PULLUP_SENSE);
@@ -144,8 +145,8 @@ int32_t ButtonThread::runOnce()
case BUTTON_EVENT_DOUBLE_PRESSED: { case BUTTON_EVENT_DOUBLE_PRESSED: {
LOG_BUTTON("Double press!\n"); LOG_BUTTON("Double press!\n");
service->refreshLocalMeshNode(); service.refreshLocalMeshNode();
auto sentPosition = service->trySendPosition(NODENUM_BROADCAST, true); auto sentPosition = service.trySendPosition(NODENUM_BROADCAST, true);
if (screen) { if (screen) {
if (sentPosition) if (sentPosition)
screen->print("Sent ad-hoc position\n"); screen->print("Sent ad-hoc position\n");
@@ -224,6 +225,7 @@ int32_t ButtonThread::runOnce()
btnEvent = BUTTON_EVENT_NONE; btnEvent = BUTTON_EVENT_NONE;
} }
runASAP = false;
return 50; return 50;
} }

View File

@@ -13,7 +13,7 @@
#endif #endif
#ifndef BUTTON_TOUCH_MS #ifndef BUTTON_TOUCH_MS
#define BUTTON_TOUCH_MS 400 #define BUTTON_TOCH_MS 400
#endif #endif
class ButtonThread : public concurrency::OSThread class ButtonThread : public concurrency::OSThread

View File

@@ -26,20 +26,6 @@ SOFTWARE.*/
#include "DebugConfiguration.h" #include "DebugConfiguration.h"
#ifdef ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
#endif
/// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic
extern "C" void logLegacy(const char *level, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
if (console)
console->vprintf(level, fmt, args);
va_end(args);
}
#if HAS_NETWORKING #if HAS_NETWORKING
Syslog::Syslog(UDP &client) Syslog::Syslog(UDP &client)
@@ -143,11 +129,6 @@ bool Syslog::vlogf(uint16_t pri, const char *appName, const char *fmt, va_list a
inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *message) inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *message)
{ {
int result; int result;
#ifdef ARCH_PORTDUINO
bool utf = !settingsMap[ascii_logs];
#else
bool utf = true;
#endif
if (!this->_enabled) if (!this->_enabled)
return false; return false;
@@ -178,12 +159,7 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
this->_client->print(this->_deviceHostname); this->_client->print(this->_deviceHostname);
this->_client->print(' '); this->_client->print(' ');
this->_client->print(appName); this->_client->print(appName);
this->_client->print(F(" - - - ")); this->_client->print(F(" - - - \xEF\xBB\xBF"));
if (utf) {
this->_client->print(F("\xEF\xBB\xBF"));
} else {
this->_client->print(F(" "));
}
this->_client->print(F("[")); this->_client->print(F("["));
this->_client->print(int(millis() / 1000)); this->_client->print(int(millis() / 1000));
this->_client->print(F("]: ")); this->_client->print(F("]: "));
@@ -193,4 +169,4 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
return true; return true;
} }
#endif #endif

View File

@@ -3,8 +3,8 @@
#include "configuration.h" #include "configuration.h"
// DEBUG LED // DEBUG LED
#ifndef LED_STATE_ON #ifndef LED_INVERTED
#define LED_STATE_ON 1 #define LED_INVERTED 0 // define as 1 if LED is active low (on)
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -45,7 +45,7 @@
#define LOG_CRIT(...) SEGGER_RTT_printf(0, __VA_ARGS__) #define LOG_CRIT(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__) #define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else #else
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) && !defined(PIO_UNIT_TESTING) #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
#define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__) #define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__)
#define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__) #define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__)
#define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__) #define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__)
@@ -62,9 +62,6 @@
#endif #endif
#endif #endif
/// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic
extern "C" void logLegacy(const char *level, const char *fmt, ...);
#define SYSLOG_NILVALUE "-" #define SYSLOG_NILVALUE "-"
#define SYSLOG_CRIT 2 /* critical conditions */ #define SYSLOG_CRIT 2 /* critical conditions */

View File

@@ -3,9 +3,6 @@
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName) const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName)
{ {
switch (preset) { switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
return useShortName ? "ShortT" : "ShortTurbo";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
return useShortName ? "ShortS" : "ShortSlow"; return useShortName ? "ShortS" : "ShortSlow";
break; break;

View File

@@ -24,39 +24,6 @@ SPIClass SPI1(HSPI);
#endif // HAS_SDCARD #endif // HAS_SDCARD
#if defined(ARCH_STM32WL)
uint16_t OSFS::startOfEEPROM = 1;
uint16_t OSFS::endOfEEPROM = 2048;
// 3) How do I read from the medium?
void OSFS::readNBytes(uint16_t address, unsigned int num, byte *output)
{
for (uint16_t i = address; i < address + num; i++) {
*output = EEPROM.read(i);
output++;
}
}
// 4) How to I write to the medium?
void OSFS::writeNBytes(uint16_t address, unsigned int num, const byte *input)
{
for (uint16_t i = address; i < address + num; i++) {
EEPROM.update(i, *input);
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\n", reason);
lfs_assert_failed = true;
}
/** /**
* @brief Copies a file from one location to another. * @brief Copies a file from one location to another.
* *
@@ -66,33 +33,7 @@ extern "C" void lfs_assert(const char *reason)
*/ */
bool copyFile(const char *from, const char *to) bool copyFile(const char *from, const char *to)
{ {
#ifdef ARCH_STM32WL #ifdef FSCom
unsigned char cbuffer[2048];
// Var to hold the result of actions
OSFS::result r;
r = OSFS::getFile(from, cbuffer);
if (r == notfound) {
LOG_ERROR("Failed to open source file %s\n", from);
return false;
} else if (r == noerr) {
r = OSFS::newFile(to, cbuffer, true);
if (r == noerr) {
return true;
} else {
LOG_ERROR("OSFS Error %d\n", r);
return false;
}
} else {
LOG_ERROR("OSFS Error %d\n", r);
return false;
}
return true;
#elif defined(FSCom)
unsigned char cbuffer[16]; unsigned char cbuffer[16];
File f1 = FSCom.open(from, FILE_O_READ); File f1 = FSCom.open(from, FILE_O_READ);
@@ -129,13 +70,7 @@ bool copyFile(const char *from, const char *to)
*/ */
bool renameFile(const char *pathFrom, const char *pathTo) bool renameFile(const char *pathFrom, const char *pathTo)
{ {
#ifdef ARCH_STM32WL #ifdef FSCom
if (copyFile(pathFrom, pathTo) && (OSFS::deleteFile(pathFrom) == OSFS::result::NO_ERROR)) {
return true;
} else {
return false;
}
#elif defined(FSCom)
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
// rename was fixed for ESP32 IDF LittleFS in April // rename was fixed for ESP32 IDF LittleFS in April
return FSCom.rename(pathFrom, pathTo); return FSCom.rename(pathFrom, pathTo);
@@ -208,7 +143,7 @@ std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels)
* @param levels The number of levels of subdirectories to list. * @param levels The number of levels of subdirectories to list.
* @param del Whether or not to delete the contents of the directory after listing. * @param del Whether or not to delete the contents of the directory after listing.
*/ */
void listDir(const char *dirname, uint8_t levels, bool del) void listDir(const char *dirname, uint8_t levels, bool del = false)
{ {
#ifdef FSCom #ifdef FSCom
#if (defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)) #if (defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO))
@@ -223,9 +158,7 @@ void listDir(const char *dirname, uint8_t levels, bool del)
} }
File file = root.openNextFile(); File file = root.openNextFile();
while ( while (file) {
file &&
file.name()[0]) { // This file.name() check is a workaround for a bug in the Adafruit LittleFS nrf52 glue (see issue 4395)
if (file.isDirectory() && !String(file.name()).endsWith(".")) { if (file.isDirectory() && !String(file.name()).endsWith(".")) {
if (levels) { if (levels) {
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@@ -249,7 +182,6 @@ void listDir(const char *dirname, uint8_t levels, bool del)
file.close(); file.close();
} }
#else #else
LOG_DEBUG(" %s (directory)\n", file.name());
listDir(file.name(), levels - 1, del); listDir(file.name(), levels - 1, del);
file.close(); file.close();
#endif #endif
@@ -276,7 +208,7 @@ void listDir(const char *dirname, uint8_t levels, bool del)
file.close(); file.close();
} }
#else #else
LOG_DEBUG(" %s (%i Bytes)\n", file.name(), file.size()); LOG_DEBUG(" %s (%i Bytes)\n", file.name(), file.size());
file.close(); file.close();
#endif #endif
} }
@@ -325,6 +257,62 @@ void rmDir(const char *dirname)
#endif #endif
} }
bool fsCheck()
{
#if defined(ARCH_NRF52)
size_t write_size = 0;
size_t read_size = 0;
char buf[32] = {0};
Adafruit_LittleFS_Namespace::File file(FSCom);
const char *text = "meshtastic fs test";
size_t text_length = strlen(text);
const char *filename = "/meshtastic.txt";
LOG_DEBUG("Try create file .\n");
if (file.open(filename, FILE_O_WRITE)) {
write_size = file.write(text);
} else {
LOG_DEBUG("Open file failed .\n");
goto FORMAT_FS;
}
if (write_size != text_length) {
LOG_DEBUG("Text bytes do not match .\n");
file.close();
goto FORMAT_FS;
}
file.close();
if (!file.open(filename, FILE_O_READ)) {
LOG_DEBUG("Open file failed .\n");
goto FORMAT_FS;
}
read_size = file.readBytes(buf, text_length);
if (read_size != text_length) {
LOG_DEBUG("Text bytes do not match .\n");
file.close();
goto FORMAT_FS;
}
if (memcmp(buf, text, text_length) != 0) {
LOG_DEBUG("The written bytes do not match the read bytes .\n");
file.close();
goto FORMAT_FS;
}
return true;
FORMAT_FS:
LOG_DEBUG("Format FS ....\n");
FSCom.format();
FSCom.begin();
return false;
#else
return true;
#endif
}
void fsInit() void fsInit()
{ {
#ifdef FSCom #ifdef FSCom
@@ -334,6 +322,35 @@ void fsInit()
} }
#if defined(ARCH_ESP32) #if defined(ARCH_ESP32)
LOG_DEBUG("Filesystem files (%d/%d Bytes):\n", FSCom.usedBytes(), FSCom.totalBytes()); LOG_DEBUG("Filesystem files (%d/%d Bytes):\n", FSCom.usedBytes(), FSCom.totalBytes());
#elif defined(ARCH_NRF52)
/*
* nRF52840 has a certain chance of automatic formatting failure.
* Try to create a file after initializing the file system. If the creation fails,
* it means that the file system is not working properly. Please format it manually again.
* To check the normality of the file system, you need to disable the LFS_NO_ASSERT assertion.
* Otherwise, the assertion will be entered at the moment of reading or opening, and the FS will not be formatted.
* */
bool ret = false;
uint8_t retry = 3;
while (retry--) {
ret = fsCheck();
if (ret) {
LOG_DEBUG("File system check is OK.\n");
break;
}
delay(10);
}
// It may not be possible to reach this step.
// Add a loop here to prevent unpredictable situations from happening.
// Can add a screen to display error status later.
if (!ret) {
while (1) {
LOG_ERROR("The file system is damaged and cannot proceed to the next step.\n");
delay(1000);
}
}
#else #else
LOG_DEBUG("Filesystem files:\n"); LOG_DEBUG("Filesystem files:\n");
#endif #endif

View File

@@ -15,13 +15,10 @@
#endif #endif
#if defined(ARCH_STM32WL) #if defined(ARCH_STM32WL)
// STM32WL series 2 Kbytes (8 rows of 256 bytes) #include "platform/stm32wl/InternalFileSystem.h" // STM32WL version
#include <EEPROM.h> #define FSCom InternalFS
#include <OSFS.h> #define FSBegin() FSCom.begin()
using namespace LittleFS_Namespace;
// Useful consts
const OSFS::result noerr = OSFS::result::NO_ERROR;
const OSFS::result notfound = OSFS::result::FILE_NOT_FOUND;
#endif #endif
#if defined(ARCH_RP2040) #if defined(ARCH_RP2040)
@@ -51,13 +48,9 @@ using namespace Adafruit_LittleFS_Namespace;
#endif #endif
void fsInit(); void fsInit();
void fsListFiles();
bool copyFile(const char *from, const char *to); bool copyFile(const char *from, const char *to);
bool renameFile(const char *pathFrom, const char *pathTo); bool renameFile(const char *pathFrom, const char *pathTo);
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels); std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
void listDir(const char *dirname, uint8_t levels, bool del = false); void listDir(const char *dirname, uint8_t levels, bool del);
void rmDir(const char *dirname); void rmDir(const char *dirname);
void setupSDCard(); 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)

View File

@@ -1,84 +0,0 @@
#include "GpioLogic.h"
#include <assert.h>
void GpioVirtPin::set(bool value)
{
if (value != this->value) {
this->value = value ? PinState::On : PinState::Off;
if (dependentPin)
dependentPin->update();
}
}
GpioTransformer::GpioTransformer(GpioPin *outPin) : outPin(outPin) {}
void GpioTransformer::set(bool value)
{
outPin->set(value);
}
GpioNotTransformer::GpioNotTransformer(GpioVirtPin *inPin, GpioPin *outPin) : GpioTransformer(outPin), inPin(inPin)
{
assert(!inPin->dependentPin); // We only allow one dependent pin
inPin->dependentPin = this;
// Don't update at construction time, because various GpioPins might be global constructor based not yet initied because
// order of operations for global constructors is not defined.
// update();
}
/**
* Update the output pin based on the current state of the input pin.
*/
void GpioNotTransformer::update()
{
auto p = inPin->get();
if (p == GpioVirtPin::PinState::Unset)
return; // Not yet fully initialized
set(!p);
}
GpioBinaryTransformer::GpioBinaryTransformer(GpioVirtPin *inPin1, GpioVirtPin *inPin2, GpioPin *outPin, Operation operation)
: GpioTransformer(outPin), inPin1(inPin1), inPin2(inPin2), operation(operation)
{
assert(!inPin1->dependentPin); // We only allow one dependent pin
inPin1->dependentPin = this;
assert(!inPin2->dependentPin); // We only allow one dependent pin
inPin2->dependentPin = this;
// Don't update at construction time, because various GpioPins might be global constructor based not yet initied because
// order of operations for global constructors is not defined.
// update();
}
void GpioBinaryTransformer::update()
{
auto p1 = inPin1->get(), p2 = inPin2->get();
GpioVirtPin::PinState newValue = GpioVirtPin::PinState::Unset;
if (p1 == GpioVirtPin::PinState::Unset)
newValue = p2; // Not yet fully initialized
else if (p2 == GpioVirtPin::PinState::Unset)
newValue = p1; // Not yet fully initialized
// If we've already found our value just use it, otherwise need to do the operation
if (newValue == GpioVirtPin::PinState::Unset) {
switch (operation) {
case And:
newValue = (GpioVirtPin::PinState)(p1 && p2);
break;
case Or:
newValue = (GpioVirtPin::PinState)(p1 || p2);
break;
case Xor:
newValue = (GpioVirtPin::PinState)(p1 != p2);
break;
default:
assert(false);
}
}
set(newValue);
}
GpioSplitter::GpioSplitter(GpioPin *outPin1, GpioPin *outPin2) : outPin1(outPin1), outPin2(outPin2) {}

View File

@@ -1,144 +0,0 @@
#pragma once
#include "configuration.h"
/**This is a set of classes to mediate access to GPIOs in a structured way. Most usage of GPIOs do not
require these classes! But if your hardware has a GPIO that is 'shared' between multiple devices (i.e. a shared power enable)
then using these classes might be able to let you cleanly turn on that enable when either dependent device is needed.
Note: these classes are intended to be 99% inline for the common case so should have minimal impact on flash or RAM
requirements.
*/
/**
* A logical GPIO pin (not necessary raw hardware).
*/
class GpioPin
{
public:
virtual void set(bool value) = 0;
};
/**
* A physical GPIO hw pin.
*/
class GpioHwPin : public GpioPin
{
uint32_t num;
public:
explicit GpioHwPin(uint32_t num) : num(num) {}
void set(bool value) { digitalWrite(num, value); }
};
class GpioTransformer;
class GpioNotTransformer;
class GpioBinaryTransformer;
/**
* A virtual GPIO pin.
*/
class GpioVirtPin : public GpioPin
{
friend class GpioBinaryTransformer;
friend class GpioNotTransformer;
public:
enum PinState { On = true, Off = false, Unset = 2 };
void set(bool value);
PinState get() const { return value; }
private:
PinState value = PinState::Unset;
GpioTransformer *dependentPin = NULL;
};
#include <assert.h>
/**
* A 'smart' trigger that can depend in a fake GPIO and if that GPIO changes, drive some other downstream GPIO to change.
* notably: the set method is not public (because it always is calculated by a subclass)
*/
class GpioTransformer
{
public:
/**
* Update the output pin based on the current state of the input pin.
*/
virtual void update() = 0;
protected:
GpioTransformer(GpioPin *outPin);
void set(bool value);
private:
GpioPin *outPin;
};
/**
* A transformer that performs a unary NOT operation from an input.
*/
class GpioNotTransformer : public GpioTransformer
{
public:
GpioNotTransformer(GpioVirtPin *inPin, GpioPin *outPin);
protected:
friend class GpioVirtPin;
/**
* Update the output pin based on the current state of the input pin.
*/
void update();
private:
GpioVirtPin *inPin;
};
/**
* A transformer that combines multiple virtual pins to drive an output pin
*/
class GpioBinaryTransformer : public GpioTransformer
{
public:
enum Operation { And, Or, Xor };
GpioBinaryTransformer(GpioVirtPin *inPin1, GpioVirtPin *inPin2, GpioPin *outPin, Operation operation);
protected:
friend class GpioVirtPin;
/**
* Update the output pin based on the current state of the input pins.
*/
void update();
private:
GpioVirtPin *inPin1;
GpioVirtPin *inPin2;
Operation operation;
};
/**
* Sometimes a single output GPIO single needs to drive multiple physical GPIOs. This class provides that.
*/
class GpioSplitter : public GpioPin
{
public:
GpioSplitter(GpioPin *outPin1, GpioPin *outPin2);
void set(bool value)
{
outPin1->set(value);
outPin2->set(value);
}
private:
GpioPin *outPin1;
GpioPin *outPin2;
};

View File

@@ -1,66 +0,0 @@
#include "Led.h"
#include "PowerMon.h"
#include "main.h"
#include "power.h"
GpioVirtPin ledForceOn, ledBlink;
#if defined(LED_PIN)
// Most boards have a GPIO for LED control
static GpioHwPin ledRawHwPin(LED_PIN);
#else
static GpioVirtPin ledRawHwPin; // Dummy pin for no hardware
#endif
#if LED_STATE_ON == 0
static GpioVirtPin ledHwPin;
static GpioNotTransformer ledInverter(&ledHwPin, &ledRawHwPin);
#else
static GpioPin &ledHwPin = ledRawHwPin;
#endif
#if defined(HAS_PMU)
/**
* A GPIO controlled by the PMU
*/
class GpioPmuPin : public GpioPin
{
public:
void set(bool value)
{
if (pmu_found && PMU) {
// blink the axp led
PMU->setChargingLedMode(value ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF);
}
}
} ledPmuHwPin;
// In some cases we need to drive a PMU LED and a normal LED
static GpioSplitter ledFinalPin(&ledHwPin, &ledPmuHwPin);
#else
static GpioPin &ledFinalPin = ledHwPin;
#endif
#ifdef USE_POWERMON
/**
* We monitor changes to the LED drive output because we use that as a sanity test in our power monitor stuff.
*/
class MonitoredLedPin : public GpioPin
{
public:
void set(bool value)
{
if (powerMon) {
if (value)
powerMon->setState(meshtastic_PowerMon_State_LED_On);
else
powerMon->clearState(meshtastic_PowerMon_State_LED_On);
}
ledFinalPin.set(value);
}
} monitoredLedPin;
#else
static GpioPin &monitoredLedPin = ledFinalPin;
#endif
static GpioBinaryTransformer ledForcer(&ledForceOn, &ledBlink, &monitoredLedPin, GpioBinaryTransformer::Or);

View File

@@ -1,7 +0,0 @@
#include "GpioLogic.h"
#include "configuration.h"
/**
* ledForceOn and ledForceOff both override the normal ledBlinker behavior (which is controlled by main)
*/
extern GpioVirtPin ledForceOn, ledBlink;

60
src/OSTimer.cpp Normal file
View File

@@ -0,0 +1,60 @@
#include "OSTimer.h"
#include "configuration.h"
/**
* Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR)
*
* NOTE! xTimerPend... seems to ignore the time passed in on ESP32 and on NRF52
* The reason this didn't work is because xTimerPednFunctCall really isn't a timer function at all - it just means run the
callback
* from the timer thread the next time you have spare cycles.
*
* @return true if successful, false if the timer fifo is too full.
bool scheduleOSCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec)
{
return xTimerPendFunctionCall(callback, param1, param2, pdMS_TO_TICKS(delayMsec));
} */
#ifdef ARCH_ESP32
// Super skanky quick hack to use hardware timers of the ESP32
static hw_timer_t *timer;
static PendableFunction tCallback;
static void *tParam1;
static uint32_t tParam2;
static void IRAM_ATTR onTimer()
{
(*tCallback)(tParam1, tParam2);
}
/**
* Schedules a hardware callback function to be executed after a specified delay.
*
* @param callback The function to be executed.
* @param param1 The first parameter to be passed to the function.
* @param param2 The second parameter to be passed to the function.
* @param delayMsec The delay time in milliseconds before the function is executed.
*
* @return True if the function was successfully scheduled, false otherwise.
*/
bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec)
{
if (!timer) {
timer = timerBegin(0, 80, true); // one usec per tick (main clock is 80MhZ on ESP32)
assert(timer);
timerAttachInterrupt(timer, &onTimer, true);
}
tCallback = callback;
tParam1 = param1;
tParam2 = param2;
timerAlarmWrite(timer, delayMsec * 1000UL, false); // Do not reload, we want it to be a single shot timer
timerRestart(timer);
timerAlarmEnable(timer);
return true;
}
#endif

8
src/OSTimer.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#include <Arduino.h>
typedef void (*PendableFunction)(void *pvParameter1, uint32_t ulParameter2);
/// Uses a hardware timer, but calls the handler in _interrupt_ context
bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec);

View File

@@ -80,6 +80,9 @@ RAK9154Sensor rak9154Sensor;
#endif #endif
#ifdef HAS_PMU #ifdef HAS_PMU
#include "XPowersAXP192.tpp"
#include "XPowersAXP2101.tpp"
#include "XPowersLibInterface.hpp"
XPowersLibInterface *PMU = NULL; XPowersLibInterface *PMU = NULL;
#else #else
@@ -197,8 +200,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
} }
#endif #endif
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(HAS_PMU) && \ #if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (hasINA()) { if (hasINA()) {
LOG_DEBUG("Using INA on I2C addr 0x%x for device battery voltage\n", config.power.device_battery_ina_address); LOG_DEBUG("Using INA on I2C addr 0x%x for device battery voltage\n", config.power.device_battery_ina_address);
return getINAVoltage(); return getINAVoltage();
@@ -418,7 +420,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
} }
#endif #endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
uint16_t getINAVoltage() uint16_t getINAVoltage()
{ {
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) { if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
@@ -458,7 +460,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
#endif #endif
}; };
static AnalogBatteryLevel analogLevel; AnalogBatteryLevel analogLevel;
Power::Power() : OSThread("Power") Power::Power() : OSThread("Power")
{ {
@@ -557,10 +559,6 @@ bool Power::setup()
{ {
bool found = axpChipInit() || analogInit(); bool found = axpChipInit() || analogInit();
#ifdef NRF_APM
found = true;
#endif
enabled = found; enabled = found;
low_voltage_counter = 0; low_voltage_counter = 0;
@@ -590,16 +588,10 @@ void Power::shutdown()
// TODO(girts): move this and other axp stuff to power.h/power.cpp. // TODO(girts): move this and other axp stuff to power.h/power.cpp.
void Power::readPowerStatus() void Power::readPowerStatus()
{ {
int32_t batteryVoltageMv = -1; // Assume unknown
int8_t batteryChargePercent = -1;
OptionalBool usbPowered = OptUnknown;
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time
OptionalBool isCharging = OptUnknown;
if (batteryLevel) { if (batteryLevel) {
hasBattery = batteryLevel->isBatteryConnect() ? OptTrue : OptFalse; bool hasBattery = batteryLevel->isBatteryConnect();
usbPowered = batteryLevel->isVbusIn() ? OptTrue : OptFalse; uint32_t batteryVoltageMv = 0;
isCharging = batteryLevel->isCharging() ? OptTrue : OptFalse; int8_t batteryChargePercent = 0;
if (hasBattery) { if (hasBattery) {
batteryVoltageMv = batteryLevel->getBattVoltage(); batteryVoltageMv = batteryLevel->getBattVoltage();
// If the AXP192 returns a valid battery percentage, use it // If the AXP192 returns a valid battery percentage, use it
@@ -614,90 +606,102 @@ void Power::readPowerStatus()
0, 100); 0, 100);
} }
} }
}
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass OptionalBool NRF_USB = OptFalse;
// (which shares a superclass with the BatteryLevel stuff)
// that just provides a few methods. But in the interest of fixing this bug I'm going to follow current
// practice.
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect #ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect
// changes. // changes.
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get(); static nrfx_power_usb_state_t prev_nrf_usb_state = (nrfx_power_usb_state_t)-1; // -1 so that state detected at boot
// LOG_DEBUG("NRF Power %d\n", nrf_usb_state); nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
// If changed to DISCONNECTED // If state changed
if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) if (nrf_usb_state != prev_nrf_usb_state) {
isCharging = usbPowered = OptFalse; // If changed to DISCONNECTED
// If changed to CONNECTED / READY if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) {
else powerFSM.trigger(EVENT_POWER_DISCONNECTED);
isCharging = usbPowered = OptTrue; NRF_USB = OptFalse;
}
// If changed to CONNECTED / READY
else {
powerFSM.trigger(EVENT_POWER_CONNECTED);
NRF_USB = OptTrue;
}
// Cache the current state
prev_nrf_usb_state = nrf_usb_state;
}
#endif #endif
// Notify any status instances that are observing us
// Notify any status instances that are observing us const PowerStatus powerStatus2 = PowerStatus(
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isCharging, batteryVoltageMv, batteryChargePercent); hasBattery ? OptTrue : OptFalse, batteryLevel->isVbusIn() || NRF_USB == OptTrue ? OptTrue : OptFalse,
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus2.getHasUSB(), batteryLevel->isCharging() || NRF_USB == OptTrue ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent);
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus2.getHasUSB(),
newStatus.notifyObservers(&powerStatus2); powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
newStatus.notifyObservers(&powerStatus2);
#ifdef DEBUG_HEAP #ifdef DEBUG_HEAP
if (lastheap != memGet.getFreeHeap()) { if (lastheap != memGet.getFreeHeap()) {
LOG_DEBUG("Threads running:"); LOG_DEBUG("Threads running:");
int running = 0; int running = 0;
for (int i = 0; i < MAX_THREADS; i++) { for (int i = 0; i < MAX_THREADS; i++) {
auto thread = concurrency::mainController.get(i); auto thread = concurrency::mainController.get(i);
if ((thread != nullptr) && (thread->enabled)) { if ((thread != nullptr) && (thread->enabled)) {
LOG_DEBUG(" %s", thread->ThreadName.c_str()); LOG_DEBUG(" %s", thread->ThreadName.c_str());
running++; running++;
}
} }
LOG_DEBUG("\n");
LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads\n", memGet.getFreeHeap(), memGet.getHeapSize(),
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
lastheap = memGet.getFreeHeap();
} }
LOG_DEBUG("\n");
LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads\n", memGet.getFreeHeap(), memGet.getHeapSize(),
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
lastheap = memGet.getFreeHeap();
}
#ifdef DEBUG_HEAP_MQTT #ifdef DEBUG_HEAP_MQTT
if (mqtt) { if (mqtt) {
// send MQTT-Packet with Heap-Size // send MQTT-Packet with Heap-Size
uint8_t dmac[6]; uint8_t dmac[6];
getMacAddr(dmac); // Get our hardware ID getMacAddr(dmac); // Get our hardware ID
char mac[18]; char mac[18];
sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]); sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]);
auto newHeap = memGet.getFreeHeap(); auto newHeap = memGet.getFreeHeap();
std::string heapTopic = std::string heapTopic =
(*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/heap/") + std::string(mac); (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/heap/") + std::string(mac);
std::string heapString = std::to_string(newHeap); std::string heapString = std::to_string(newHeap);
mqtt->pubSub.publish(heapTopic.c_str(), heapString.c_str(), false); mqtt->pubSub.publish(heapTopic.c_str(), heapString.c_str(), false);
auto wifiRSSI = WiFi.RSSI(); auto wifiRSSI = WiFi.RSSI();
std::string wifiTopic = std::string wifiTopic =
(*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/wifi/") + std::string(mac); (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/wifi/") + std::string(mac);
std::string wifiString = std::to_string(wifiRSSI); std::string wifiString = std::to_string(wifiRSSI);
mqtt->pubSub.publish(wifiTopic.c_str(), wifiString.c_str(), false); mqtt->pubSub.publish(wifiTopic.c_str(), wifiString.c_str(), false);
}
#endif
#endif
// If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in
// a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
//
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
if (batteryLevel->getBattVoltage() < OCV[NUM_OCV_POINTS - 1]) {
low_voltage_counter++;
LOG_DEBUG("Low voltage counter: %d/10\n", low_voltage_counter);
if (low_voltage_counter > 10) {
#ifdef ARCH_NRF52
// We can't trigger deep sleep on NRF52, it's freezing the board
LOG_DEBUG("Low voltage detected, but not triggering deep sleep\n");
#else
LOG_INFO("Low voltage detected, triggering deep sleep\n");
powerFSM.trigger(EVENT_LOW_BATTERY);
#endif
}
} else {
low_voltage_counter = 0;
} }
#endif
#endif
// If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in
// a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
//
if (powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
if (batteryLevel->getBattVoltage() < OCV[NUM_OCV_POINTS - 1]) {
low_voltage_counter++;
LOG_DEBUG("Low voltage counter: %d/10\n", low_voltage_counter);
if (low_voltage_counter > 10) {
#ifdef ARCH_NRF52
// We can't trigger deep sleep on NRF52, it's freezing the board
LOG_DEBUG("Low voltage detected, but not triggering deep sleep\n");
#else
LOG_INFO("Low voltage detected, triggering deep sleep\n");
powerFSM.trigger(EVENT_LOW_BATTERY);
#endif
}
} else {
low_voltage_counter = 0;
}
}
} else {
// No power sensing on this board - tell everyone else we have no idea what is happening
const PowerStatus powerStatus3 = PowerStatus(OptUnknown, OptUnknown, OptUnknown, -1, -1);
newStatus.notifyObservers(&powerStatus3);
} }
} }
@@ -1046,4 +1050,4 @@ bool Power::axpChipInit()
#else #else
return false; return false;
#endif #endif
} }

View File

@@ -9,7 +9,6 @@
*/ */
#include "PowerFSM.h" #include "PowerFSM.h"
#include "Default.h" #include "Default.h"
#include "Led.h"
#include "MeshService.h" #include "MeshService.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "PowerMon.h" #include "PowerMon.h"
@@ -22,15 +21,12 @@
#ifndef SLEEP_TIME #ifndef SLEEP_TIME
#define SLEEP_TIME 30 #define SLEEP_TIME 30
#endif #endif
#if EXCLUDE_POWER_FSM
FakeFsm powerFSM;
void PowerFSM_setup(){};
#else
/// Should we behave as if we have AC power now? /// Should we behave as if we have AC power now?
static bool isPowered() static bool isPowered()
{ {
// Circumvent the battery sensing logic and assumes constant power if no battery pin or power mgmt IC // Circumvent the battery sensing logic and assumes constant power if no battery pin or power mgmt IC
#if !defined(BATTERY_PIN) && !defined(HAS_AXP192) && !defined(HAS_AXP2101) && !defined(NRF_APM) #if !defined(BATTERY_PIN) && !defined(HAS_AXP192) && !defined(HAS_AXP2101)
return true; return true;
#endif #endif
@@ -54,6 +50,7 @@ static bool isPowered()
static void sdsEnter() static void sdsEnter()
{ {
LOG_DEBUG("Enter state: SDS\n"); LOG_DEBUG("Enter state: SDS\n");
powerMon->setState(meshtastic_PowerMon_State_CPU_DeepSleep);
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false); doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false);
} }
@@ -73,6 +70,7 @@ static uint32_t secsSlept;
static void lsEnter() static void lsEnter()
{ {
LOG_INFO("lsEnter begin, ls_secs=%u\n", config.power.ls_secs); LOG_INFO("lsEnter begin, ls_secs=%u\n", config.power.ls_secs);
powerMon->clearState(meshtastic_PowerMon_State_Screen_On);
screen->setOn(false); screen->setOn(false);
secsSlept = 0; // How long have we been sleeping this time secsSlept = 0; // How long have we been sleeping this time
@@ -93,7 +91,7 @@ static void lsIdle()
uint32_t sleepTime = SLEEP_TIME; uint32_t sleepTime = SLEEP_TIME;
powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep);
ledBlink.set(false); // Never leave led on while in light sleep setLed(false); // Never leave led on while in light sleep
esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL); esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL);
powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep);
@@ -101,7 +99,7 @@ static void lsIdle()
case ESP_SLEEP_WAKEUP_TIMER: case ESP_SLEEP_WAKEUP_TIMER:
// Normal case: timer expired, we should just go back to sleep ASAP // Normal case: timer expired, we should just go back to sleep ASAP
ledBlink.set(true); // briefly turn on led setLed(true); // briefly turn on led
wakeCause2 = doLightSleep(100); // leave led on for 1ms wakeCause2 = doLightSleep(100); // leave led on for 1ms
secsSlept += sleepTime; secsSlept += sleepTime;
@@ -136,7 +134,7 @@ static void lsIdle()
} }
} else { } else {
// Time to stop sleeping! // Time to stop sleeping!
ledBlink.set(false); setLed(false);
LOG_INFO("Reached ls_secs, servicing loop()\n"); LOG_INFO("Reached ls_secs, servicing loop()\n");
powerFSM.trigger(EVENT_WAKE_TIMER); powerFSM.trigger(EVENT_WAKE_TIMER);
} }
@@ -151,6 +149,7 @@ static void lsExit()
static void nbEnter() static void nbEnter()
{ {
LOG_DEBUG("Enter state: NB\n"); LOG_DEBUG("Enter state: NB\n");
powerMon->clearState(meshtastic_PowerMon_State_BT_On);
screen->setOn(false); screen->setOn(false);
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
// Only ESP32 should turn off bluetooth // Only ESP32 should turn off bluetooth
@@ -162,6 +161,8 @@ static void nbEnter()
static void darkEnter() static void darkEnter()
{ {
powerMon->clearState(meshtastic_PowerMon_State_BT_On);
powerMon->clearState(meshtastic_PowerMon_State_Screen_On);
setBluetoothEnable(true); setBluetoothEnable(true);
screen->setOn(false); screen->setOn(false);
} }
@@ -169,6 +170,8 @@ static void darkEnter()
static void serialEnter() static void serialEnter()
{ {
LOG_DEBUG("Enter state: SERIAL\n"); LOG_DEBUG("Enter state: SERIAL\n");
powerMon->clearState(meshtastic_PowerMon_State_BT_On);
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
setBluetoothEnable(false); setBluetoothEnable(false);
screen->setOn(true); screen->setOn(true);
screen->print("Serial connected\n"); screen->print("Serial connected\n");
@@ -177,6 +180,7 @@ static void serialEnter()
static void serialExit() static void serialExit()
{ {
// Turn bluetooth back on when we leave serial stream API // Turn bluetooth back on when we leave serial stream API
powerMon->setState(meshtastic_PowerMon_State_BT_On);
setBluetoothEnable(true); setBluetoothEnable(true);
screen->print("Serial disconnected\n"); screen->print("Serial disconnected\n");
} }
@@ -189,6 +193,8 @@ static void powerEnter()
LOG_INFO("Loss of power in Powered\n"); LOG_INFO("Loss of power in Powered\n");
powerFSM.trigger(EVENT_POWER_DISCONNECTED); powerFSM.trigger(EVENT_POWER_DISCONNECTED);
} else { } else {
powerMon->setState(meshtastic_PowerMon_State_BT_On);
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
screen->setOn(true); screen->setOn(true);
setBluetoothEnable(true); setBluetoothEnable(true);
// within enter() the function getState() returns the state we came from // within enter() the function getState() returns the state we came from
@@ -212,6 +218,8 @@ static void powerIdle()
static void powerExit() static void powerExit()
{ {
powerMon->setState(meshtastic_PowerMon_State_BT_On);
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
screen->setOn(true); screen->setOn(true);
setBluetoothEnable(true); setBluetoothEnable(true);
@@ -223,6 +231,8 @@ static void powerExit()
static void onEnter() static void onEnter()
{ {
LOG_DEBUG("Enter state: ON\n"); LOG_DEBUG("Enter state: ON\n");
powerMon->setState(meshtastic_PowerMon_State_BT_On);
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
screen->setOn(true); screen->setOn(true);
setBluetoothEnable(true); setBluetoothEnable(true);
} }
@@ -398,5 +408,4 @@ void PowerFSM_setup()
#endif #endif
powerFSM.run_machine(); // run one iteration of the state machine, so we run our on enter tasks for the initial DARK state powerFSM.run_machine(); // run one iteration of the state machine, so we run our on enter tasks for the initial DARK state
} }
#endif

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "configuration.h" #include <Fsm.h>
// See sw-design.md for documentation // See sw-design.md for documentation
@@ -22,30 +22,7 @@
#define EVENT_SHUTDOWN 16 // force a full shutdown now (not just sleep) #define EVENT_SHUTDOWN 16 // force a full shutdown now (not just sleep)
#define EVENT_INPUT 17 // input broker wants something, we need to wake up and enable screen #define EVENT_INPUT 17 // input broker wants something, we need to wake up and enable screen
#if EXCLUDE_POWER_FSM
class FakeFsm
{
public:
void trigger(int event)
{
if (event == EVENT_SERIAL_CONNECTED) {
serialConnected = true;
} else if (event == EVENT_SERIAL_DISCONNECTED) {
serialConnected = false;
}
};
bool getState() { return serialConnected; };
private:
bool serialConnected = false;
};
extern FakeFsm powerFSM;
void PowerFSM_setup();
#else
#include <Fsm.h>
extern Fsm powerFSM; extern Fsm powerFSM;
extern State stateON, statePOWER, stateSERIAL, stateDARK; extern State stateON, statePOWER, stateSERIAL, stateDARK;
void PowerFSM_setup(); void PowerFSM_setup();
#endif

View File

@@ -18,7 +18,6 @@ class PowerFSMThread : public OSThread
protected: protected:
int32_t runOnce() override int32_t runOnce() override
{ {
#if !EXCLUDE_POWER_FSM
powerFSM.run_machine(); powerFSM.run_machine();
/// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake /// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake
@@ -36,9 +35,6 @@ class PowerFSMThread : public OSThread
} }
return 100; return 100;
#else
return INT32_MAX;
#endif
} }
}; };

View File

@@ -2,11 +2,9 @@
#include "NodeDB.h" #include "NodeDB.h"
// Use the 'live' config flag to figure out if we should be showing this message // Use the 'live' config flag to figure out if we should be showing this message
bool PowerMon::is_power_enabled(uint64_t m) static bool is_power_enabled(uint64_t m)
{ {
// FIXME: VERY STRANGE BUG: if I or in "force_enabled || " the flashed image on a rak4631 is not accepted by the bootloader as return (m & config.power.powermon_enables) ? true : false;
// valid!!! Possibly a linker/gcc/bootloader bug somewhere?
return ((m & config.power.powermon_enables) ? true : false);
} }
void PowerMon::setState(_meshtastic_PowerMon_State state, const char *reason) void PowerMon::setState(_meshtastic_PowerMon_State state, const char *reason)

View File

@@ -17,13 +17,6 @@ class PowerMon
{ {
uint64_t states = 0UL; uint64_t states = 0UL;
friend class PowerStressModule;
/**
* If stress testing we always want all events logged
*/
bool force_enabled = false;
public: public:
PowerMon() {} PowerMon() {}
@@ -34,9 +27,6 @@ class PowerMon
private: private:
// Emit the coded log message // Emit the coded log message
void emitLog(const char *reason); void emitLog(const char *reason);
// Use the 'live' config flag to figure out if we should be showing this message
bool is_power_enabled(uint64_t m);
}; };
extern PowerMon *powerMon; extern PowerMon *powerMon;

View File

@@ -38,9 +38,8 @@ size_t RedirectablePrint::write(uint8_t c)
#ifdef USE_SEGGER #ifdef USE_SEGGER
SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c); SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c);
#endif #endif
// Account for legacy config transition
bool serialEnabled = config.has_security ? config.security.serial_enabled : config.device.serial_enabled; if (!config.has_lora || config.device.serial_enabled)
if (!config.has_lora || serialEnabled)
dest->write(c); dest->write(c);
return 1; // We always claim one was written, rather than trusting what the return 1; // We always claim one was written, rather than trusting what the
@@ -50,17 +49,7 @@ size_t RedirectablePrint::write(uint8_t c)
size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg) size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg)
{ {
va_list copy; va_list copy;
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
static char printBuf[512];
#else
static char printBuf[160]; static char printBuf[160];
#endif
#ifdef ARCH_PORTDUINO
bool color = !settingsMap[ascii_logs];
#else
bool color = true;
#endif
va_copy(copy, arg); va_copy(copy, arg);
size_t len = vsnprintf(printBuf, sizeof(printBuf), format, copy); size_t len = vsnprintf(printBuf, sizeof(printBuf), format, copy);
@@ -77,7 +66,7 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
if (!std::isprint(static_cast<unsigned char>(printBuf[f])) && printBuf[f] != '\n') if (!std::isprint(static_cast<unsigned char>(printBuf[f])) && printBuf[f] != '\n')
printBuf[f] = '#'; printBuf[f] = '#';
} }
if (color && logLevel != nullptr) { if (logLevel != nullptr) {
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
Print::write("\u001b[34m", 6); Print::write("\u001b[34m", 6);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
@@ -88,9 +77,7 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
Print::write("\u001b[31m", 6); Print::write("\u001b[31m", 6);
} }
len = Print::write(printBuf, len); len = Print::write(printBuf, len);
if (color && logLevel != nullptr) { Print::write("\u001b[0m", 5);
Print::write("\u001b[0m", 5);
}
return len; return len;
} }
@@ -100,27 +87,17 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
// Cope with 0 len format strings, but look for new line terminator // Cope with 0 len format strings, but look for new line terminator
bool hasNewline = *format && format[strlen(format) - 1] == '\n'; bool hasNewline = *format && format[strlen(format) - 1] == '\n';
#ifdef ARCH_PORTDUINO
bool color = !settingsMap[ascii_logs];
#else
bool color = true;
#endif
// If we are the first message on a report, include the header // If we are the first message on a report, include the header
if (!isContinuationMessage) { if (!isContinuationMessage) {
if (color) { if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) Print::write("\u001b[34m", 6);
Print::write("\u001b[34m", 6); if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) Print::write("\u001b[32m", 6);
Print::write("\u001b[32m", 6); if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) Print::write("\u001b[33m", 6);
Print::write("\u001b[33m", 6); if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) Print::write("\u001b[31m", 6);
Print::write("\u001b[31m", 6);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0)
Print::write("\u001b[35m", 6);
}
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
if (rtc_sec > 0) { if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY; long hms = rtc_sec % SEC_PER_DAY;
@@ -134,33 +111,17 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
::printf("%s ", logLevel); ::printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000);
if (color) {
::printf("\u001b[0m");
}
::printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
#else #else
printf("%s ", logLevel); printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000);
if (color) {
printf("\u001b[0m");
}
printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
#endif #endif
} else { } else
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
::printf("%s ", logLevel); ::printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000);
if (color) {
::printf("\u001b[0m");
}
::printf("| ??:??:?? %u ", millis() / 1000);
#else #else
printf("%s ", logLevel); printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000);
if (color) {
printf("\u001b[0m");
}
printf("| ??:??:?? %u ", millis() / 1000);
#endif #endif
}
auto thread = concurrency::OSThread::currentThread; auto thread = concurrency::OSThread::currentThread;
if (thread) { if (thread) {
print("["); print("[");
@@ -213,7 +174,7 @@ void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format,
void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg) void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg)
{ {
#if !MESHTASTIC_EXCLUDE_BLUETOOTH #if !MESHTASTIC_EXCLUDE_BLUETOOTH
if (config.security.debug_log_api_enabled && !pauseBluetoothLogging) { if (config.bluetooth.device_logging_enabled && !pauseBluetoothLogging) {
bool isBleConnected = false; bool isBleConnected = false;
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected();
@@ -283,21 +244,7 @@ meshtastic_LogRecord_Level RedirectablePrint::getLogLevel(const char *logLevel)
void RedirectablePrint::log(const char *logLevel, const char *format, ...) void RedirectablePrint::log(const char *logLevel, const char *format, ...)
{ {
#if ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
// level trace is special, two possible ways to handle it.
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
if (settingsStrings[traceFilename] != "") {
va_list arg;
va_start(arg, format);
try {
traceFile << va_arg(arg, char *) << std::endl;
} catch (const std::ios_base::failure &e) {
}
va_end(arg);
}
if (settingsMap[logoutputlevel] < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0)
return;
}
if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
return; return;
else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
@@ -383,4 +330,4 @@ std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
break; break;
} }
return std::string(formatted.get()); return std::string(formatted.get());
} }

View File

@@ -1,105 +0,0 @@
#include "SafeFile.h"
#ifdef FSCom
// Only way to work on both esp32 and nrf52
static File openFile(const char *filename, bool fullAtomic)
{
if (!fullAtomic)
FSCom.remove(filename); // Nuke the old file to make space (ignore if it !exists)
String filenameTmp = filename;
filenameTmp += ".tmp";
// clear any previous LFS errors
lfs_assert_failed = false;
return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE);
}
SafeFile::SafeFile(const char *_filename, bool fullAtomic)
: filename(_filename), f(openFile(_filename, fullAtomic)), fullAtomic(fullAtomic)
{
}
size_t SafeFile::write(uint8_t ch)
{
if (!f)
return 0;
hash ^= ch;
return f.write(ch);
}
size_t SafeFile::write(const uint8_t *buffer, size_t size)
{
if (!f)
return 0;
for (size_t i = 0; i < size; i++) {
hash ^= buffer[i];
}
return f.write((uint8_t const *)buffer, size); // This nasty cast is _IMPORTANT_ otherwise the correct adafruit method does
// not get used (they made a mistake in their typing)
}
/**
* Atomically close the file (deleting any old versions) and readback the contents to confirm the hash matches
*
* @return false for failure
*/
bool SafeFile::close()
{
if (!f)
return false;
f.close();
if (!testReadback())
return false;
// brief window of risk here ;-)
if (fullAtomic && FSCom.exists(filename.c_str()) && !FSCom.remove(filename.c_str())) {
LOG_ERROR("Can't remove old pref file\n");
return false;
}
String filenameTmp = filename;
filenameTmp += ".tmp";
if (!renameFile(filenameTmp.c_str(), filename.c_str())) {
LOG_ERROR("Error: can't rename new pref file\n");
return false;
}
return true;
}
/// Read our (closed) tempfile back in and compare the hash
bool SafeFile::testReadback()
{
bool lfs_failed = lfs_assert_failed;
lfs_assert_failed = false;
String filenameTmp = filename;
filenameTmp += ".tmp";
auto f2 = FSCom.open(filenameTmp.c_str(), FILE_O_READ);
if (!f2) {
LOG_ERROR("Can't open tmp file for readback\n");
return false;
}
int c = 0;
uint8_t test_hash = 0;
while ((c = f2.read()) >= 0) {
test_hash ^= (uint8_t)c;
}
f2.close();
if (test_hash != hash) {
LOG_ERROR("Readback failed hash mismatch\n");
return false;
}
return !lfs_failed;
}
#endif

View File

@@ -1,49 +0,0 @@
#pragma once
#include "FSCommon.h"
#include "configuration.h"
#ifdef FSCom
/**
* This class provides 'safe'/paranoid file writing.
*
* Some of our filesystems (in particular the nrf52) may have bugs beneath our layer. Therefore we want to
* be very careful about how we write files. This class provides a restricted (Stream only) writing API for writing to files.
*
* Notably:
* - we keep a simple xor hash of all characters that were written.
* - We do not allow seeking (because we want to maintain our hash)
* - we provide an close() method which is similar to close but returns false if we were unable to successfully write the
* file. Also this method
* - atomically replaces any old version of the file on the disk with our new file (after first rereading the file from the disk
* to confirm the hash matches)
* - Some files are super huge so we can't do the full atomic rename/copy (because of filesystem size limits). If !fullAtomic
* then we still do the readback to verify file is valid so higher level code can handle failures.
*/
class SafeFile : public Print
{
public:
SafeFile(char const *filepath, bool fullAtomic = false);
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t *buffer, size_t size);
/**
* Atomically close the file (deleting any old versions) and readback the contents to confirm the hash matches
*
* @return false for failure
*/
bool close();
private:
/// Read our (closed) tempfile back in and compare the hash
bool testReadback();
String filename;
File f;
bool fullAtomic;
uint8_t hash = 0;
};
#endif

View File

@@ -83,7 +83,7 @@ bool SerialConsole::checkIsConnected()
bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
{ {
// only talk to the API once the configuration has been loaded and we're sure the serial port is not disabled. // only talk to the API once the configuration has been loaded and we're sure the serial port is not disabled.
if (config.has_lora && config.security.serial_enabled) { if (config.has_lora && config.device.serial_enabled) {
// Switch to protobufs for log messages // Switch to protobufs for log messages
usingProtobufs = true; usingProtobufs = true;
canWrite = true; canWrite = true;
@@ -96,7 +96,7 @@ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg) void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg)
{ {
if (usingProtobufs && config.security.debug_log_api_enabled) { if (usingProtobufs) {
meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset
switch (logLevel[0]) { switch (logLevel[0]) {
case 'D': case 'D':
@@ -120,4 +120,4 @@ void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_l
emitLogRecord(ll, thread ? thread->ThreadName.c_str() : "", format, arg); emitLogRecord(ll, thread ? thread->ThreadName.c_str() : "", format, arg);
} else } else
RedirectablePrint::log_to_serial(logLevel, format, arg); RedirectablePrint::log_to_serial(logLevel, format, arg);
} }

0
src/StatusHandler.h Normal file
View File

View File

@@ -52,6 +52,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Configuration // Configuration
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// If we are using the JTAG port for debugging, some pins must be left free for that (and things like GPS have to be disabled)
// we don't support jtag on the ttgo - access to gpio 12 is a PITA
#define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found
/// Convert a preprocessor name into a quoted string /// Convert a preprocessor name into a quoted string
#define xstr(s) ystr(s) #define xstr(s) ystr(s)
#define ystr(s) #s #define ystr(s) #s
@@ -193,10 +197,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define DEFAULT_SHUTDOWN_SECONDS 2 #define DEFAULT_SHUTDOWN_SECONDS 2
#endif #endif
#ifndef MINIMUM_SAFE_FREE_HEAP
#define MINIMUM_SAFE_FREE_HEAP 1500
#endif
/* Step #3: mop up with disabled values for HAS_ options not handled by the above two */ /* Step #3: mop up with disabled values for HAS_ options not handled by the above two */
#ifndef HAS_WIFI #ifndef HAS_WIFI
@@ -259,10 +259,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_SCREEN 1 #define MESHTASTIC_EXCLUDE_SCREEN 1
#define MESHTASTIC_EXCLUDE_MQTT 1 #define MESHTASTIC_EXCLUDE_MQTT 1
#define MESHTASTIC_EXCLUDE_POWERMON 1 #define MESHTASTIC_EXCLUDE_POWERMON 1
#define MESHTASTIC_EXCLUDE_I2C 1
#define MESHTASTIC_EXCLUDE_PKI 1
#define MESHTASTIC_EXCLUDE_POWER_FSM 1
#define MESHTASTIC_EXCLUDE_TZ 1
#endif #endif
// Turn off all optional modules // Turn off all optional modules
@@ -276,7 +272,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_RANGETEST 1 #define MESHTASTIC_EXCLUDE_RANGETEST 1
#define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1 #define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1
#define MESHTASTIC_EXCLUDE_STOREFORWARD 1 #define MESHTASTIC_EXCLUDE_STOREFORWARD 1
#define MESHTASTIC_EXCLUDE_TEXTMESSAGE 1
#define MESHTASTIC_EXCLUDE_ATAK 1 #define MESHTASTIC_EXCLUDE_ATAK 1
#define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1 #define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1
#define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1 #define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1
@@ -285,7 +280,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_INPUTBROKER 1 #define MESHTASTIC_EXCLUDE_INPUTBROKER 1
#define MESHTASTIC_EXCLUDE_SERIAL 1 #define MESHTASTIC_EXCLUDE_SERIAL 1
#define MESHTASTIC_EXCLUDE_POWERSTRESS 1 #define MESHTASTIC_EXCLUDE_POWERSTRESS 1
#define MESHTASTIC_EXCLUDE_ADMIN 1
#endif #endif
// // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled) // // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled)
@@ -319,4 +313,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif #endif
#include "DebugConfiguration.h" #include "DebugConfiguration.h"
#include "RF95Configuration.h" #include "RF95Configuration.h"

View File

@@ -1,8 +1,7 @@
#include "ScanI2CTwoWire.h" #include "ScanI2CTwoWire.h"
#if !MESHTASTIC_EXCLUDE_I2C
#include "concurrency/LockGuard.h" #include "concurrency/LockGuard.h"
#include "configuration.h"
#if defined(ARCH_PORTDUINO) #if defined(ARCH_PORTDUINO)
#include "linux/LinuxHardwareI2C.h" #include "linux/LinuxHardwareI2C.h"
#endif #endif
@@ -315,7 +314,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case SHT31_4x_ADDR: case SHT31_4x_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) { if (registerValue == 0x11a2 || registerValue == 0x11da) {
type = SHT4X; type = SHT4X;
LOG_INFO("SHT4X sensor found\n"); LOG_INFO("SHT4X sensor found\n");
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) { } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
@@ -404,4 +403,3 @@ size_t ScanI2CTwoWire::countDevices() const
{ {
return foundDevices.size(); return foundDevices.size();
} }
#endif

View File

@@ -1,8 +1,5 @@
#pragma once #pragma once
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_I2C
#include <map> #include <map>
#include <memory> #include <memory>
#include <stddef.h> #include <stddef.h>
@@ -58,5 +55,4 @@ class ScanI2CTwoWire : public ScanI2C
uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const; uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const;
DeviceType probeOLED(ScanI2C::DeviceAddress) const; DeviceType probeOLED(ScanI2C::DeviceAddress) const;
}; };
#endif

View File

@@ -12,7 +12,7 @@
#include <freertos/task.h> #include <freertos/task.h>
#endif #endif
#if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_RP2040) #if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_RP2040)
#define HAS_FREE_RTOS #define HAS_FREE_RTOS
#include <FreeRTOS.h> #include <FreeRTOS.h>

View File

@@ -400,6 +400,7 @@ bool GPS::setup()
int msglen = 0; int msglen = 0;
if (!didSerialInit) { if (!didSerialInit) {
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
// if GPS_BAUDRATE is specified in variant (i.e. not 9600), skip to the specified rate. // if GPS_BAUDRATE is specified in variant (i.e. not 9600), skip to the specified rate.
@@ -504,22 +505,6 @@ bool GPS::setup()
delay(250); delay(250);
_serial_gps->write("$CFGMSG,6,1,0\r\n"); _serial_gps->write("$CFGMSG,6,1,0\r\n");
delay(250); delay(250);
} else if (gnssModel == GNSS_MODEL_AG3335) {
_serial_gps->write("$PAIR066,1,0,1,0,0,1*3B"); // Enable GPS+GALILEO+NAVIC
// Configure NMEA (sentences will output once per fix)
_serial_gps->write("$PAIR062,0,0*3F"); // GGA ON
_serial_gps->write("$PAIR062,1,0*3F"); // GLL OFF
_serial_gps->write("$PAIR062,2,1*3D"); // GSA ON
_serial_gps->write("$PAIR062,3,0*3D"); // GSV OFF
_serial_gps->write("$PAIR062,4,0*3B"); // RMC ON
_serial_gps->write("$PAIR062,5,0*3B"); // VTG OFF
_serial_gps->write("$PAIR062,6,1*39"); // ZDA ON
delay(250);
_serial_gps->write("$PAIR513*3D"); // save configuration
} else if (gnssModel == GNSS_MODEL_UBLOX) { } else if (gnssModel == GNSS_MODEL_UBLOX) {
// Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command) // Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command)
// We need set it because by default it is GPS only, and we want to use GLONASS too // We need set it because by default it is GPS only, and we want to use GLONASS too
@@ -802,6 +787,7 @@ GPS::~GPS()
// we really should unregister our sleep observer // we really should unregister our sleep observer
notifyDeepSleepObserver.unobserve(&notifyDeepSleep); notifyDeepSleepObserver.unobserve(&notifyDeepSleep);
} }
// Put the GPS hardware into a specified state // Put the GPS hardware into a specified state
void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
{ {
@@ -810,13 +796,6 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
powerState = newState; powerState = newState;
LOG_INFO("GPS power state moving from %s to %s\n", getGPSPowerStateString(oldState), getGPSPowerStateString(newState)); LOG_INFO("GPS power state moving from %s to %s\n", getGPSPowerStateString(oldState), getGPSPowerStateString(newState));
#ifdef HELTEC_MESH_NODE_T114
if ((oldState == GPS_OFF || oldState == GPS_HARDSLEEP) && (newState != GPS_OFF && newState != GPS_HARDSLEEP)) {
_serial_gps->begin(serialSpeeds[speedSelect]);
} else if ((newState == GPS_OFF || newState == GPS_HARDSLEEP) && (oldState != GPS_OFF && oldState != GPS_HARDSLEEP)) {
_serial_gps->end();
}
#endif
switch (newState) { switch (newState) {
case GPS_ACTIVE: case GPS_ACTIVE:
case GPS_IDLE: case GPS_IDLE:
@@ -845,11 +824,6 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
setPowerPMU(false); // Power (PMU): off setPowerPMU(false); // Power (PMU): off
writePinStandby(true); // Standby (pin): asleep (not awake) writePinStandby(true); // Standby (pin): asleep (not awake)
setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed
#ifdef GNSS_AIROHA
if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) {
digitalWrite(PIN_GPS_EN, LOW);
}
#endif
break; break;
case GPS_OFF: case GPS_OFF:
@@ -859,11 +833,6 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
setPowerPMU(false); // Power (PMU): off setPowerPMU(false); // Power (PMU): off
writePinStandby(true); // Standby (pin): asleep writePinStandby(true); // Standby (pin): asleep
setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely
#ifdef GNSS_AIROHA
if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) {
digitalWrite(PIN_GPS_EN, LOW);
}
#endif
break; break;
} }
} }
@@ -1113,7 +1082,7 @@ int32_t GPS::runOnce()
if (devicestate.did_gps_reset && scheduling.elapsedSearchMs() > 60 * 1000UL && !hasFlow()) { if (devicestate.did_gps_reset && scheduling.elapsedSearchMs() > 60 * 1000UL && !hasFlow()) {
LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n"); LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n");
devicestate.did_gps_reset = false; devicestate.did_gps_reset = false;
nodeDB->saveToDisk(SEGMENT_DEVICESTATE); nodeDB->saveDeviceStateToDisk();
return disable(); // Stop the GPS thread as it can do nothing useful until next reboot. return disable(); // Stop the GPS thread as it can do nothing useful until next reboot.
} }
} }
@@ -1193,7 +1162,7 @@ int GPS::prepareDeepSleep(void *unused)
GnssModel_t GPS::probe(int serialSpeed) GnssModel_t GPS::probe(int serialSpeed)
{ {
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) || defined(ARCH_STM32WL) #if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040)
_serial_gps->end(); _serial_gps->end();
_serial_gps->begin(serialSpeed); _serial_gps->begin(serialSpeed);
#else #else
@@ -1202,9 +1171,9 @@ GnssModel_t GPS::probe(int serialSpeed)
_serial_gps->updateBaudRate(serialSpeed); _serial_gps->updateBaudRate(serialSpeed);
} }
#endif #endif
#ifdef GNSS_AIROHA #ifdef GNSS_Airoha // add by WayenWeng
return GNSS_MODEL_AG3335; return GNSS_MODEL_UNKNOWN;
#endif #else
#ifdef GPS_DEBUG #ifdef GPS_DEBUG
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
getACK("$GP", 200); getACK("$GP", 200);
@@ -1227,15 +1196,7 @@ GnssModel_t GPS::probe(int serialSpeed)
return GNSS_MODEL_UC6580; return GNSS_MODEL_UC6580;
} }
clearBuffer(); // Get version information
_serial_gps->write("$PDTINFO\r\n");
delay(750);
if (getACK("UM600", 500) == GNSS_RESPONSE_OK) {
LOG_INFO("UM600 detected, using UC6580 Module\n");
return GNSS_MODEL_UC6580;
}
// Get version information for ATGM336H
clearBuffer(); clearBuffer();
_serial_gps->write("$PCAS06,1*1A\r\n"); _serial_gps->write("$PCAS06,1*1A\r\n");
if (getACK("$GPTXT,01,01,02,HW=ATGM336H", 500) == GNSS_RESPONSE_OK) { if (getACK("$GPTXT,01,01,02,HW=ATGM336H", 500) == GNSS_RESPONSE_OK) {
@@ -1243,26 +1204,6 @@ GnssModel_t GPS::probe(int serialSpeed)
return GNSS_MODEL_ATGM336H; return GNSS_MODEL_ATGM336H;
} }
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS))
based on AT6558 */
clearBuffer();
_serial_gps->write("$PCAS06,1*1A\r\n");
if (getACK("$GPTXT,01,01,02,HW=ATGM332D", 500) == GNSS_RESPONSE_OK) {
LOG_INFO("ATGM332D detected, using ATGM336H Module\n");
return GNSS_MODEL_ATGM336H;
}
/* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
clearBuffer();
_serial_gps->write("PAIR020*38\r\n");
if (getACK("$PAIR020,AG3335", 500) == GNSS_RESPONSE_OK) {
LOG_INFO("Aioha AG3335 detected, using AG3335 Module\n");
return GNSS_MODEL_AG3335;
}
// Get version information for Airoha AG3335
clearBuffer();
_serial_gps->write("$PMTK605*31\r\n");
// Get version information // Get version information
clearBuffer(); clearBuffer();
_serial_gps->write("$PCAS06,0*1B\r\n"); _serial_gps->write("$PCAS06,0*1B\r\n");
@@ -1308,7 +1249,7 @@ GnssModel_t GPS::probe(int serialSpeed)
_serial_gps->write(_message_prt, sizeof(_message_prt)); _serial_gps->write(_message_prt, sizeof(_message_prt));
delay(500); delay(500);
serialSpeed = 9600; serialSpeed = 9600;
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) || defined(ARCH_STM32WL) #if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040)
_serial_gps->end(); _serial_gps->end();
_serial_gps->begin(serialSpeed); _serial_gps->begin(serialSpeed);
#else #else
@@ -1388,6 +1329,7 @@ GnssModel_t GPS::probe(int serialSpeed)
} }
return GNSS_MODEL_UBLOX; return GNSS_MODEL_UBLOX;
#endif // !GNSS_Airoha
} }
GPS *GPS::createGps() GPS *GPS::createGps()
@@ -1542,25 +1484,11 @@ bool GPS::factoryReset()
*/ */
bool GPS::lookForTime() bool GPS::lookForTime()
{ {
#ifdef GNSS_Airoha // add by WayenWeng
#ifdef GNSS_AIROHA
uint8_t fix = reader.fixQuality(); uint8_t fix = reader.fixQuality();
uint32_t now = millis(); uint32_t now = millis();
if (fix > 0) {
if (lastFixStartMsec > 0) {
if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) {
return false;
} else {
clearBuffer();
}
} else {
lastFixStartMsec = now;
return false;
}
} else {
return false;
}
#endif #endif
auto ti = reader.time; auto ti = reader.time;
auto d = reader.date; auto d = reader.date;
if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed
@@ -1595,26 +1523,13 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
*/ */
bool GPS::lookForLocation() bool GPS::lookForLocation()
{ {
#ifdef GNSS_AIROHA #ifdef GNSS_Airoha // add by WayenWeng
if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) {
uint8_t fix = reader.fixQuality(); uint8_t fix = reader.fixQuality();
uint32_t now = millis(); uint32_t now = millis();
if (fix > 0) {
if (lastFixStartMsec > 0) {
if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) {
return false;
} else {
clearBuffer();
}
} else {
lastFixStartMsec = now;
return false;
}
} else {
return false;
}
} }
#endif #endif
// By default, TinyGPS++ does not parse GPGSA lines, which give us // By default, TinyGPS++ does not parse GPGSA lines, which give us
// the 2D/3D fixType (see NMEAGPS.h) // the 2D/3D fixType (see NMEAGPS.h)
// At a minimum, use the fixQuality indicator in GPGGA (FIXME?) // At a minimum, use the fixQuality indicator in GPGGA (FIXME?)
@@ -1824,12 +1739,6 @@ void GPS::toggleGpsMode()
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED;
LOG_INFO("User toggled GpsMode. Now DISABLED.\n"); LOG_INFO("User toggled GpsMode. Now DISABLED.\n");
#ifdef GNSS_AIROHA
if (powerState == GPS_ACTIVE) {
LOG_DEBUG("User power Off GPS\n");
digitalWrite(PIN_GPS_EN, LOW);
}
#endif
disable(); disable();
} else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) { } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) {
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;

View File

@@ -28,8 +28,7 @@ typedef enum {
GNSS_MODEL_UBLOX, GNSS_MODEL_UBLOX,
GNSS_MODEL_UC6580, GNSS_MODEL_UC6580,
GNSS_MODEL_UNKNOWN, GNSS_MODEL_UNKNOWN,
GNSS_MODEL_MTK_L76B, GNSS_MODEL_MTK_L76B
GNSS_MODEL_AG3335
} GnssModel_t; } GnssModel_t;
typedef enum { typedef enum {
@@ -51,7 +50,7 @@ enum GPSPowerState : uint8_t {
const char *getDOPString(uint32_t dop); const char *getDOPString(uint32_t dop);
/** /**
* A gps class that only reads from the GPS periodically and keeps the gps powered down except when reading * A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
* *
* When new data is available it will notify observers. * When new data is available it will notify observers.
*/ */
@@ -70,7 +69,7 @@ class GPS : private concurrency::OSThread
#endif #endif
private: private:
const int serialSpeeds[6] = {9600, 4800, 38400, 57600, 115200, 9600}; const int serialSpeeds[6] = {9600, 4800, 38400, 57600, 115200, 9600};
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0;
uint32_t rx_gpio = 0; uint32_t rx_gpio = 0;
uint32_t tx_gpio = 0; uint32_t tx_gpio = 0;
uint32_t en_gpio = 0; uint32_t en_gpio = 0;
@@ -303,4 +302,4 @@ class GPS : private concurrency::OSThread
}; };
extern GPS *gps; extern GPS *gps;
#endif // Exclude GPS #endif // Exclude GPS

View File

@@ -493,7 +493,7 @@ std::shared_ptr<GeoCoord> GeoCoord::pointAtDistance(double bearing, double range
* The bearing in string format * The bearing in string format
* @return Bearing in degrees * @return Bearing in degrees
*/ */
unsigned int GeoCoord::bearingToDegrees(const char *bearing) uint GeoCoord::bearingToDegrees(const char *bearing)
{ {
if (strcmp(bearing, "N") == 0) if (strcmp(bearing, "N") == 0)
return 0; return 0;
@@ -537,7 +537,7 @@ unsigned int GeoCoord::bearingToDegrees(const char *bearing)
* The bearing in degrees * The bearing in degrees
* @return Bearing in string format * @return Bearing in string format
*/ */
const char *GeoCoord::degreesToBearing(unsigned int degrees) const char *GeoCoord::degreesToBearing(uint degrees)
{ {
if (degrees >= 348 || degrees < 11) if (degrees >= 348 || degrees < 11)
return "N"; return "N";

View File

@@ -117,8 +117,8 @@ class GeoCoord
static float bearing(double lat1, double lon1, double lat2, double lon2); static float bearing(double lat1, double lon1, double lat2, double lon2);
static float rangeRadiansToMeters(double range_radians); static float rangeRadiansToMeters(double range_radians);
static float rangeMetersToRadians(double range_meters); static float rangeMetersToRadians(double range_meters);
static unsigned int bearingToDegrees(const char *bearing); static uint bearingToDegrees(const char *bearing);
static const char *degreesToBearing(unsigned int degrees); static const char *degreesToBearing(uint degrees);
// Point to point conversions // Point to point conversions
int32_t distanceTo(const GeoCoord &pointB); int32_t distanceTo(const GeoCoord &pointB);

View File

@@ -6,7 +6,6 @@
#include <time.h> #include <time.h>
static RTCQuality currentQuality = RTCQualityNone; static RTCQuality currentQuality = RTCQualityNone;
uint32_t lastSetFromPhoneNtpOrGps = 0;
RTCQuality getRTCQuality() RTCQuality getRTCQuality()
{ {
@@ -122,9 +121,6 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
if (shouldSet) { if (shouldSet) {
currentQuality = q; currentQuality = q;
lastSetMsec = now; lastSetMsec = now;
if (currentQuality >= RTCQualityNTP) {
lastSetFromPhoneNtpOrGps = now;
}
// This delta value works on all platforms // This delta value works on all platforms
timeStartMsec = now; timeStartMsec = now;
@@ -260,7 +256,6 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
time_t gm_mktime(struct tm *tm) time_t gm_mktime(struct tm *tm)
{ {
#if !MESHTASTIC_EXCLUDE_TZ
setenv("TZ", "GMT0", 1); setenv("TZ", "GMT0", 1);
time_t res = mktime(tm); time_t res = mktime(tm);
if (*config.device.tzdef) { if (*config.device.tzdef) {
@@ -269,7 +264,4 @@ time_t gm_mktime(struct tm *tm)
setenv("TZ", "UTC0", 1); setenv("TZ", "UTC0", 1);
} }
return res; return res;
#else
return mktime(tm);
#endif
} }

View File

@@ -24,8 +24,6 @@ enum RTCQuality {
RTCQuality getRTCQuality(); RTCQuality getRTCQuality();
extern uint32_t lastSetFromPhoneNtpOrGps;
/// If we haven't yet set our RTC this boot, set it from a GPS derived time /// If we haven't yet set our RTC this boot, set it from a GPS derived time
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false); bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
bool perhapsSetRTC(RTCQuality q, struct tm &t); bool perhapsSetRTC(RTCQuality q, struct tm &t);
@@ -45,4 +43,4 @@ time_t gm_mktime(struct tm *tm);
#define SEC_PER_DAY 86400 #define SEC_PER_DAY 86400
#define SEC_PER_HOUR 3600 #define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60 #define SEC_PER_MIN 60

View File

@@ -174,13 +174,13 @@ bool EInkDisplay::connect()
adafruitDisplay->init(); adafruitDisplay->init();
adafruitDisplay->setRotation(3); adafruitDisplay->setRotation(3);
} }
#elif defined(PCA10059) || defined(ME25LS01) #elif defined(PCA10059)
{ {
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel); adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
adafruitDisplay->init(115200, true, 40, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
adafruitDisplay->setRotation(0); adafruitDisplay->setRotation(3);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
} }
#elif defined(M5_COREINK) #elif defined(M5_COREINK)
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);

View File

@@ -375,7 +375,7 @@ void EInkDynamicDisplay::hashImage()
// Sum all bytes of the image buffer together // Sum all bytes of the image buffer together
for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) { for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
imageHash ^= buffer[b] << b; imageHash += buffer[b];
} }
} }

View File

@@ -20,8 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "Screen.h" #include "Screen.h"
#include "../userPrefs.h"
#include "PowerMon.h"
#include "configuration.h" #include "configuration.h"
#if HAS_SCREEN #if HAS_SCREEN
#include <OLEDDisplay.h> #include <OLEDDisplay.h>
@@ -37,7 +35,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "gps/RTC.h" #include "gps/RTC.h"
#include "graphics/ScreenFonts.h" #include "graphics/ScreenFonts.h"
#include "graphics/images.h" #include "graphics/images.h"
#include "input/ScanAndSelect.h"
#include "input/TouchScreenImpl1.h" #include "input/TouchScreenImpl1.h"
#include "main.h" #include "main.h"
#include "mesh-pb-constants.h" #include "mesh-pb-constants.h"
@@ -159,11 +156,7 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
display->setFont(FONT_MEDIUM); display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT); display->setTextAlignment(TEXT_ALIGN_LEFT);
#ifdef SPLASH_TITLE_USERPREFS
const char *title = SPLASH_TITLE_USERPREFS;
#else
const char *title = "meshtastic.org"; const char *title = "meshtastic.org";
#endif
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
@@ -1576,7 +1569,6 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
if (on != screenOn) { if (on != screenOn) {
if (on) { if (on) {
LOG_INFO("Turning on screen\n"); LOG_INFO("Turning on screen\n");
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
PMU->enablePowerOutput(XPOWERS_ALDO2); PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif #endif
@@ -1591,9 +1583,6 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
dispdev->displayOn(); dispdev->displayOn();
#ifdef USE_ST7789 #ifdef USE_ST7789
pinMode(VTFT_CTRL, OUTPUT);
digitalWrite(VTFT_CTRL, LOW);
ui->init();
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT); analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT);
#else #else
@@ -1605,28 +1594,16 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
setInterval(0); // Draw ASAP setInterval(0); // Draw ASAP
runASAP = true; runASAP = true;
} else { } else {
powerMon->clearState(meshtastic_PowerMon_State_Screen_On);
#ifdef USE_EINK #ifdef USE_EINK
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead // eInkScreensaver parameter is usually NULL (default argument), default frame used instead
setScreensaverFrames(einkScreensaver); setScreensaverFrames(einkScreensaver);
#endif #endif
LOG_INFO("Turning off screen\n"); LOG_INFO("Turning off screen\n");
dispdev->displayOff(); dispdev->displayOff();
#ifdef USE_ST7789 #ifdef USE_ST7789
SPI1.end(); pinMode(VTFT_LEDA, OUTPUT);
#if defined(ARCH_ESP32) digitalWrite(VTFT_LEDA, !TFT_BACKLIGHT_ON);
pinMode(VTFT_LEDA, ANALOG);
pinMode(VTFT_CTRL, ANALOG);
pinMode(ST7789_RESET, ANALOG);
pinMode(ST7789_RS, ANALOG);
pinMode(ST7789_NSS, ANALOG);
#else
nrf_gpio_cfg_default(VTFT_LEDA);
nrf_gpio_cfg_default(VTFT_CTRL);
nrf_gpio_cfg_default(ST7789_RESET);
nrf_gpio_cfg_default(ST7789_RS);
nrf_gpio_cfg_default(ST7789_NSS);
#endif
#endif #endif
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
@@ -1920,13 +1897,6 @@ int32_t Screen::runOnce()
// standard screen loop handling here // standard screen loop handling here
if (config.display.auto_screen_carousel_secs > 0 && if (config.display.auto_screen_carousel_secs > 0 &&
(millis() - lastScreenTransition) > (config.display.auto_screen_carousel_secs * 1000)) { (millis() - lastScreenTransition) > (config.display.auto_screen_carousel_secs * 1000)) {
// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
// Carousel is potentially a major source of E-Ink display wear
#if !defined(EINK_BACKGROUND_USES_FAST)
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC);
#endif
LOG_DEBUG("LastScreenTransition exceeded %ums transitioning to next frame\n", (millis() - lastScreenTransition)); LOG_DEBUG("LastScreenTransition exceeded %ums transitioning to next frame\n", (millis() - lastScreenTransition));
handleOnPress(); handleOnPress();
} }
@@ -2306,11 +2276,6 @@ void Screen::handlePrint(const char *text)
void Screen::handleOnPress() void Screen::handleOnPress()
{ {
// If Canned Messages is using the "Scan and Select" input, dismiss the canned message frame when user button is pressed
// Minimize impact as a courtesy, as "scan and select" may be used as default config for some boards
if (scanAndSelectInput != nullptr && scanAndSelectInput->dismissCannedMessageFrame())
return;
// If screen was off, just wake it, otherwise advance to next frame // If screen was off, just wake it, otherwise advance to next frame
// If we are in a transition, the press must have bounced, drop it. // If we are in a transition, the press must have bounced, drop it.
if (ui->getUiState()->frameState == FIXED) { if (ui->getUiState()->frameState == FIXED) {

View File

@@ -1,4 +1,3 @@
#ifndef HAS_USERPREFS_SPLASH
#define icon_width 50 #define icon_width 50
#define icon_height 28 #define icon_height 28
static uint8_t icon_bits[] = { static uint8_t icon_bits[] = {
@@ -18,5 +17,4 @@ static uint8_t icon_bits[] = {
0xFE, 0x00, 0x00, 0xFC, 0x01, 0x7E, 0x00, 0x7F, 0x00, 0x00, 0xF8, 0x01, 0xFE, 0x00, 0x00, 0xFC, 0x01, 0x7E, 0x00, 0x7F, 0x00, 0x00, 0xF8, 0x01,
0x7E, 0x00, 0x3E, 0x00, 0x00, 0xF8, 0x01, 0x38, 0x00, 0x3C, 0x00, 0x00, 0x7E, 0x00, 0x3E, 0x00, 0x00, 0xF8, 0x01, 0x38, 0x00, 0x3C, 0x00, 0x00,
0x70, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, }; 0x00, 0x00, 0x00, 0x00, };
#endif

View File

@@ -1,260 +0,0 @@
#include "ExpressLRSFiveWay.h"
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
static const char inputSourceName[] = "ExpressLRS5Way"; // should match "allow input source" string
/**
* @brief Calculate fuzz: half the distance to the next nearest neighbor for each joystick position.
*
* The goal is to avoid collisions between joystick positions while still maintaining
* the widest tolerance for the analog value.
*
* Example: {10,50,800,1000,300,1600}
* If we just choose the minimum difference for this array the value would
* be 40/2 = 20.
*
* 20 does not leave enough room for the joystick position using 1600 which
* could have a +-100 offset.
*
* Example Fuzz values: {20, 20, 100, 100, 125, 300} now the fuzz for the 1600
* position is 300 instead of 20
*/
void ExpressLRSFiveWay::calcFuzzValues()
{
for (unsigned int i = 0; i < N_JOY_ADC_VALUES; i++) {
uint16_t closestDist = 0xffff;
uint16_t ival = joyAdcValues[i];
// Find the closest value to ival
for (unsigned int j = 0; j < N_JOY_ADC_VALUES; j++) {
// Don't compare value with itself
if (j == i)
continue;
uint16_t jval = joyAdcValues[j];
if (jval < ival && (ival - jval < closestDist))
closestDist = ival - jval;
if (jval > ival && (jval - ival < closestDist))
closestDist = jval - ival;
} // for j
// And the fuzz is half the distance to the closest value
fuzzValues[i] = closestDist / 2;
// DBG("joy%u=%u f=%u, ", i, ival, fuzzValues[i]);
} // for i
}
int ExpressLRSFiveWay::readKey()
{
uint16_t value = analogRead(PIN_JOYSTICK);
constexpr uint8_t IDX_TO_INPUT[N_JOY_ADC_VALUES - 1] = {UP, DOWN, LEFT, RIGHT, OK};
for (unsigned int i = 0; i < N_JOY_ADC_VALUES - 1; ++i) {
if (value < (joyAdcValues[i] + fuzzValues[i]) && value > (joyAdcValues[i] - fuzzValues[i]))
return IDX_TO_INPUT[i];
}
return NO_PRESS;
}
ExpressLRSFiveWay::ExpressLRSFiveWay() : concurrency::OSThread(inputSourceName)
{
// ExpressLRS: init values
isLongPressed = false;
keyInProcess = NO_PRESS;
keyDownStart = 0;
// Express LRS: calculate the threshold for interpreting ADC values as various buttons
calcFuzzValues();
// Meshtastic: register with canned messages
inputBroker->registerSource(this);
}
// ExpressLRS: interpret reading as key events
void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed)
{
*keyValue = NO_PRESS;
int newKey = readKey();
uint32_t now = millis();
if (keyInProcess == NO_PRESS) {
// New key down
if (newKey != NO_PRESS) {
keyDownStart = now;
// DBGLN("down=%u", newKey);
}
} else {
// if key released
if (newKey == NO_PRESS) {
// DBGLN("up=%u", keyInProcess);
if (!isLongPressed) {
if ((now - keyDownStart) > KEY_DEBOUNCE_MS) {
*keyValue = keyInProcess;
*keyLongPressed = false;
}
}
isLongPressed = false;
}
// else if the key has changed while down, reset state for next go-around
else if (newKey != keyInProcess) {
newKey = NO_PRESS;
}
// else still pressing, waiting for long if not already signaled
else if (!isLongPressed) {
if ((now - keyDownStart) > KEY_LONG_PRESS_MS) {
*keyValue = keyInProcess;
*keyLongPressed = true;
isLongPressed = true;
}
}
} // if keyInProcess != NO_PRESS
keyInProcess = newKey;
}
// Meshtastic: runs at regular intervals
int32_t ExpressLRSFiveWay::runOnce()
{
uint32_t now = millis();
// Dismiss any alert frames after 2 seconds
// Feedback for GPS toggle / adhoc ping
if (alerting && now > alertingSinceMs + 2000) {
alerting = false;
screen->endAlert();
}
// Get key events from ExpressLRS code
int keyValue;
bool longPressed;
update(&keyValue, &longPressed);
// Do something about this key press
determineAction((KeyType)keyValue, longPressed ? LONG : SHORT);
// If there has been recent key activity, poll the joystick slightly more frequently
if (now < keyDownStart + (20 * 1000UL)) // Within last 20 seconds
return 100;
// Otherwise, poll slightly less often
// Too many missed pressed if much slower than 250ms
return 250;
}
// Determine what action to take when a button press is detected
// Written verbose for easier remapping by user
void ExpressLRSFiveWay::determineAction(KeyType key, PressLength length)
{
switch (key) {
case LEFT:
if (inCannedMessageMenu()) // If in canned message menu
sendKey(CANCEL); // exit the menu (press imaginary cancel key)
else
sendKey(LEFT);
break;
case RIGHT:
if (inCannedMessageMenu()) // If in canned message menu:
sendKey(CANCEL); // exit the menu (press imaginary cancel key)
else
sendKey(RIGHT);
break;
case UP:
if (length == LONG)
toggleGPS();
else
sendKey(UP);
break;
case DOWN:
if (length == LONG)
sendAdhocPing();
else
sendKey(DOWN);
break;
case OK:
if (length == LONG)
shutdown();
else
click(); // Use instead of sendKey(OK). Works better when canned message module disabled
break;
default:
break;
}
}
// Feed input to the canned messages module
void ExpressLRSFiveWay::sendKey(KeyType key)
{
InputEvent e;
e.source = inputSourceName;
e.inputEvent = key;
notifyObservers(&e);
}
// Enable or Disable a connected GPS
// Contained as one method for easier remapping of buttons by user
void ExpressLRSFiveWay::toggleGPS()
{
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
if (!config.device.disable_triple_click && (gps != nullptr)) {
gps->toggleGpsMode();
screen->startAlert("GPS Toggled");
alerting = true;
alertingSinceMs = millis();
}
#endif
}
// Send either node-info or position, on demand
// Contained as one method for easier remapping of buttons by user
void ExpressLRSFiveWay::sendAdhocPing()
{
service->refreshLocalMeshNode();
bool sentPosition = service->trySendPosition(NODENUM_BROADCAST, true);
// Show custom alert frame, with multi-line centering
screen->startAlert([sentPosition](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
uint16_t x_offset = display->width() / 2;
uint16_t y_offset = 26; // Same constant as the default startAlert frame
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(x_offset + x, y_offset + y, "Sent ad-hoc");
display->drawString(x_offset + x, y_offset + FONT_HEIGHT_MEDIUM + y, sentPosition ? "position" : "nodeinfo");
});
alerting = true;
alertingSinceMs = millis();
}
// Shutdown the node (enter deep-sleep)
// Contained as one method for easier remapping of buttons by user
void ExpressLRSFiveWay::shutdown()
{
LOG_INFO("Shutdown from long press\n");
powerFSM.trigger(EVENT_PRESS);
screen->startAlert("Shutting down...");
// Don't set alerting = true. We don't want to auto-dismiss this alert.
playShutdownMelody(); // In case user adds a buzzer
shutdownAtMsec = millis() + 3000;
}
// Emulate user button, or canned message SELECT
// This is necessary as canned message module doesn't translate SELECT to user button presses if the module is disabled
// Contained as one method for easier remapping of buttons by user
void ExpressLRSFiveWay::click()
{
if (!moduleConfig.canned_message.enabled)
powerFSM.trigger(EVENT_PRESS);
else
sendKey(OK);
}
ExpressLRSFiveWay *expressLRSFiveWayInput = nullptr;
#endif

View File

@@ -1,85 +0,0 @@
/*
Input source for Radio Master Bandit Nano, and similar hardware.
Devices have a 5-button "resistor ladder" style joystick, read by ADC.
These devices do not use the ADC to monitor input voltage.
Much of this code taken directly from ExpressLRS FiveWayButton class:
https://github.com/ExpressLRS/ExpressLRS/tree/d9f56f8bd6f9f7144d5f01caaca766383e1e0950/src/lib/SCREEN/FiveWayButton
*/
#pragma once
#include "configuration.h"
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
#include <esp_adc_cal.h>
#include <soc/adc_channel.h>
#include "InputBroker.h"
#include "MeshService.h" // For adhoc ping action
#include "buzz.h"
#include "concurrency/OSThread.h"
#include "graphics/Screen.h" // Feedback for adhoc ping / toggle GPS
#include "main.h"
#include "modules/CannedMessageModule.h"
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h" // For toggle GPS action
#endif
class ExpressLRSFiveWay : public Observable<const InputEvent *>, public concurrency::OSThread
{
private:
// Number of values in JOY_ADC_VALUES, if defined
// These must be ADC readings for {UP, DOWN, LEFT, RIGHT, ENTER, IDLE}
static constexpr size_t N_JOY_ADC_VALUES = 6;
static constexpr uint32_t KEY_DEBOUNCE_MS = 25;
static constexpr uint32_t KEY_LONG_PRESS_MS = 3000; // How many milliseconds to hold key for a long press
// This merged an enum used by the ExpressLRS code, with meshtastic canned message values
// Key names are kept simple, to allow user customizaton
typedef enum {
UP = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP,
DOWN = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN,
LEFT = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT,
RIGHT = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT,
OK = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT,
CANCEL = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL,
NO_PRESS = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE
} KeyType;
typedef enum { SHORT, LONG } PressLength;
// From ExpressLRS
int keyInProcess;
uint32_t keyDownStart;
bool isLongPressed;
const uint16_t joyAdcValues[N_JOY_ADC_VALUES] = {JOYSTICK_ADC_VALS};
uint16_t fuzzValues[N_JOY_ADC_VALUES];
void calcFuzzValues();
int readKey();
void update(int *keyValue, bool *keyLongPressed);
// Meshtastic code
void determineAction(KeyType key, PressLength length);
void sendKey(KeyType key);
inline bool inCannedMessageMenu() { return cannedMessageModule->shouldDraw(); }
int32_t runOnce() override;
// Simplified Meshtastic actions, for easier remapping by user
void toggleGPS();
void sendAdhocPing();
void shutdown();
void click();
bool alerting = false; // Is the screen showing an alert frame? Feedback for GPS toggle / adhoc ping actions
uint32_t alertingSinceMs = 0; // When did screen begin showing an alert frame? Used to auto-dismiss
public:
ExpressLRSFiveWay();
};
extern ExpressLRSFiveWay *expressLRSFiveWayInput;
#endif

View File

@@ -1,204 +0,0 @@
#include "configuration.h"
// Normally these input methods are protected by guarding in setupModules
// In order to have the user button dismiss the canned message frame, this class lightly interacts with the Screen class
#if HAS_SCREEN
#include "ScanAndSelect.h"
#include "modules/CannedMessageModule.h"
// Config
static const char name[] = "scanAndSelect"; // should match "allow input source" string
static constexpr uint32_t durationShortMs = 50;
static constexpr uint32_t durationLongMs = 1500;
static constexpr uint32_t durationAlertMs = 2000;
// Constructor: init base class
ScanAndSelectInput::ScanAndSelectInput() : concurrency::OSThread(name) {}
// Attempt to setup class; true if success.
// Called by setupModules method. Instance deleted if setup fails.
bool ScanAndSelectInput::init()
{
// Short circuit: Canned messages enabled?
if (!moduleConfig.canned_message.enabled)
return false;
// Short circuit: Using correct "input source"?
// Todo: protobuf enum instead of string?
if (strcasecmp(moduleConfig.canned_message.allow_input_source, name) != 0)
return false;
// Use any available inputbroker pin as the button
if (moduleConfig.canned_message.inputbroker_pin_press)
pin = moduleConfig.canned_message.inputbroker_pin_press;
else if (moduleConfig.canned_message.inputbroker_pin_a)
pin = moduleConfig.canned_message.inputbroker_pin_a;
else if (moduleConfig.canned_message.inputbroker_pin_b)
pin = moduleConfig.canned_message.inputbroker_pin_b;
else
return false; // Short circuit: no button found
// Set-up the button
pinMode(pin, INPUT_PULLUP);
attachInterrupt(pin, handleChangeInterrupt, CHANGE);
// Connect our class to the canned message module
inputBroker->registerSource(this);
LOG_INFO("Initialized 'Scan and Select' input for Canned Messages, using pin %d\n", pin);
return true; // Init succeded
}
// Runs periodically, unless sleeping between presses
int32_t ScanAndSelectInput::runOnce()
{
uint32_t now = millis();
// If: "no messages added" alert screen currently shown
if (alertingNoMessage) {
// Dismiss the alert screen several seconds after it appears
if (now > alertingSinceMs + durationAlertMs) {
alertingNoMessage = false;
screen->endAlert();
}
}
// If: Button is pressed
if (digitalRead(pin) == LOW) {
// New press
if (!held) {
downSinceMs = now;
}
// Existing press
else {
// Duration enough for long press
// Long press not yet fired (prevent repeat firing while held)
if (!longPressFired && now - downSinceMs > durationLongMs) {
longPressFired = true;
longPress();
}
}
// Record the change of state: button is down
held = true;
}
// If: Button is not pressed
else {
// Button newly released
// Long press event didn't already fire
if (held && !longPressFired) {
// Duration enough for short press
if (now - downSinceMs > durationShortMs) {
shortPress();
}
}
// Record the change of state: button is up
held = false;
longPressFired = false; // Re-Arm: allow another long press
}
// If thread's job is done, let it sleep
if (!held && !alertingNoMessage) {
Thread::canSleep = true;
return OSThread::disable();
}
// Run this method again is a few ms
return durationShortMs;
}
void ScanAndSelectInput::longPress()
{
// (If canned messages set)
if (cannedMessageModule->hasMessages()) {
// If module frame displayed already, send the current message
if (cannedMessageModule->shouldDraw())
raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT);
// Otherwise, initial long press opens the module frame
else
raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN);
}
// (If canned messages not set) tell the user
else
alertNoMessage();
}
void ScanAndSelectInput::shortPress()
{
// (If canned messages set) scroll to next message
if (cannedMessageModule->hasMessages())
raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN);
// (If canned messages not yet set) tell the user
else
alertNoMessage();
}
// Begin running runOnce at regular intervals
// Called from pin change interrupt
void ScanAndSelectInput::enableThread()
{
Thread::canSleep = false;
OSThread::enabled = true;
OSThread::setIntervalFromNow(0);
}
// Inform user (screen) that no canned messages have been added
// Automatically dismissed after several seconds
void ScanAndSelectInput::alertNoMessage()
{
alertingNoMessage = true;
alertingSinceMs = millis();
// Graphics code: the alert frame to show on screen
screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
display->setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
display->setFont(FONT_SMALL);
int16_t textX = display->getWidth() / 2;
int16_t textY = display->getHeight() / 2;
display->drawString(textX + x, textY + y, "No Canned Messages");
});
}
// Remove the canned message frame from screen
// Used to dismiss the module frame when user button pressed
// Returns true if the frame was previously displayed, and has now been closed
// Return value consumed by Screen class when determining how to handle user button
bool ScanAndSelectInput::dismissCannedMessageFrame()
{
if (cannedMessageModule->shouldDraw()) {
raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL);
return true;
}
return false;
}
// Feed input to the canned messages module
void ScanAndSelectInput::raiseEvent(_meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar key)
{
InputEvent e;
e.source = name;
e.inputEvent = key;
notifyObservers(&e);
}
// Pin change interrupt
void ScanAndSelectInput::handleChangeInterrupt()
{
// Because we need to detect both press and release (rising and falling edge), the interrupt itself can't determine the
// action. Instead, we start up the thread and get it to read the button for us
// The instance we're referring to here is created in setupModules()
scanAndSelectInput->enableThread();
}
ScanAndSelectInput *scanAndSelectInput = nullptr; // Instantiated in setupModules method. Deleted if unused, or init() fails
#endif

View File

@@ -1,50 +0,0 @@
/*
A "single button" input method for Canned Messages
- Short press to cycle through messages
- Long Press to send
To use:
- set "allow input source" to "scanAndSelect"
- set the single button's GPIO as either pin A, pin B, or pin Press
Originally designed to make use of "extra" built-in button on some boards.
Non-intrusive; suitable for use as a default module config.
*/
#pragma once
#include "concurrency/OSThread.h"
#include "main.h"
// Normally these input methods are protected by guarding in setupModules
// In order to have the user button dismiss the canned message frame, this class lightly interacts with the Screen class
#if HAS_SCREEN
class ScanAndSelectInput : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
ScanAndSelectInput(); // No-op constructor, only initializes OSThread base class
bool init(); // Attempt to setup class; true if success. Instance deleted if setup fails
bool dismissCannedMessageFrame(); // Remove the canned message frame from screen. True if frame was open, and now closed.
void alertNoMessage(); // Inform user (screen) that no canned messages have been added
protected:
int32_t runOnce() override; // Runs at regular intervals, when enabled
void enableThread(); // Begin running runOnce at regular intervals
static void handleChangeInterrupt(); // Calls enableThread from pin change interrupt
void shortPress(); // Code to run when short press fires
void longPress(); // Code to run when long press fires
void raiseEvent(_meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar key); // Feed input to canned message module
bool held = false; // Have we handled a change in button state?
bool longPressFired = false; // Long press fires while button still held. This bool ensures the release is no-op
uint32_t downSinceMs = 0; // Debouncing for short press, timing for long press
uint8_t pin = -1; // Read from cannned message config during init
bool alertingNoMessage = false; // Is the "no canned messages" alert shown on screen?
uint32_t alertingSinceMs = 0; // Used to dismiss the "no canned message" alert several seconds
};
extern ScanAndSelectInput *scanAndSelectInput; // Instantiated in setupModules method. Deleted if unused, or init() fails
#endif

View File

@@ -1,170 +0,0 @@
#include "SerialKeyboard.h"
#include "configuration.h"
#ifdef INPUTBROKER_SERIAL_TYPE
#define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file
#if INPUTBROKER_SERIAL_TYPE == 1 // It's a Chatter
// 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number
unsigned char KeyMap[3][4][10] = {{{'.', 'a', 'd', 'g', 'j', 'm', 'p', 't', 'w', ' '},
{',', 'b', 'e', 'h', 'k', 'n', 'q', 'u', 'x', ' '},
{'?', 'c', 'f', 'i', 'l', 'o', 'r', 'v', 'y', ' '},
{'1', '2', '3', '4', '5', '6', 's', '8', 'z', ' '}}, // low case
{{'!', 'A', 'D', 'G', 'J', 'M', 'P', 'T', 'W', ' '},
{'+', 'B', 'E', 'H', 'K', 'N', 'Q', 'U', 'X', ' '},
{'-', 'C', 'F', 'I', 'L', 'O', 'R', 'V', 'Y', ' '},
{'1', '2', '3', '4', '5', '6', 'S', '8', 'Z', ' '}}, // upper case
{{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'},
{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'},
{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'},
{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}}}; // numbers
#endif
SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name)
{
this->_originName = name;
}
void SerialKeyboard::erase()
{
InputEvent e;
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK;
e.kbchar = 0x08;
e.source = this->_originName;
this->notifyObservers(&e);
}
int32_t SerialKeyboard::runOnce()
{
if (!INPUTBROKER_SERIAL_TYPE) {
// Input device is not requested.
return disable();
}
if (firstTime) {
// This is the first time the OSThread library has called this function, so do port setup
firstTime = 0;
pinMode(KB_LOAD, OUTPUT);
pinMode(KB_CLK, OUTPUT);
pinMode(KB_DATA, INPUT);
digitalWrite(KB_LOAD, HIGH);
digitalWrite(KB_CLK, LOW);
prevKeys = 0b1111111111111111;
LOG_DEBUG("Serial Keyboard setup\n");
}
if (INPUTBROKER_SERIAL_TYPE == 1) { // Chatter V1.0 & V2.0 keypads
// scan for keypresses
// Write pulse to load pin
digitalWrite(KB_LOAD, LOW);
delayMicroseconds(5);
digitalWrite(KB_LOAD, HIGH);
delayMicroseconds(5);
// Get data from 74HC165
byte shiftRegister1 = shiftIn(KB_DATA, KB_CLK, LSBFIRST);
byte shiftRegister2 = shiftIn(KB_DATA, KB_CLK, LSBFIRST);
keys = (shiftRegister1 << 8) + shiftRegister2;
// Print to serial monitor
// Serial.print (shiftRegister1, BIN);
// Serial.print ("X");
// Serial.println (shiftRegister2, BIN);
if (millis() - lastPressTime > 500) {
quickPress = 0;
}
if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but
// shouldn't be a limitation
InputEvent e;
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
e.source = this->_originName;
// SELECT OR SEND OR CANCEL EVENT
if (!(shiftRegister2 & (1 << 3))) {
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP;
} else if (!(shiftRegister2 & (1 << 2))) {
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
e.kbchar = 0xb7;
} else if (!(shiftRegister2 & (1 << 1))) {
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
} else if (!(shiftRegister2 & (1 << 0))) {
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
}
// TEXT INPUT EVENT
else if (!(shiftRegister1 & (1 << 4))) {
keyPressed = 0;
} else if (!(shiftRegister1 & (1 << 3))) {
keyPressed = 1;
} else if (!(shiftRegister2 & (1 << 4))) {
keyPressed = 2;
} else if (!(shiftRegister1 & (1 << 5))) {
keyPressed = 3;
} else if (!(shiftRegister1 & (1 << 2))) {
keyPressed = 4;
} else if (!(shiftRegister2 & (1 << 5))) {
keyPressed = 5;
} else if (!(shiftRegister1 & (1 << 6))) {
keyPressed = 6;
} else if (!(shiftRegister1 & (1 << 1))) {
keyPressed = 7;
} else if (!(shiftRegister2 & (1 << 6))) {
keyPressed = 8;
} else if (!(shiftRegister1 & (1 << 0))) {
keyPressed = 9;
}
// BACKSPACE or TAB
else if (!(shiftRegister1 & (1 << 7))) {
if (shift == 0 || shift == 2) { // BACKSPACE
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK;
e.kbchar = 0x08;
} else { // shift = 1 => TAB
e.inputEvent = ANYKEY;
e.kbchar = 0x09;
}
}
// SHIFT
else if (!(shiftRegister2 & (1 << 7))) {
keyPressed = 10;
}
if (keyPressed < 11) {
if (keyPressed == lastKeyPressed && millis() - lastPressTime < 500) {
quickPress += 1;
if (quickPress > 3) {
quickPress = 0;
}
}
if (keyPressed != lastKeyPressed) {
quickPress = 0;
}
if (keyPressed < 10) { // if it's a letter
if (keyPressed == lastKeyPressed && millis() - lastPressTime < 500) {
erase();
}
e.inputEvent = ANYKEY;
e.kbchar = char(KeyMap[shift][quickPress][keyPressed]);
} else { // then it's shift
shift += 1;
if (shift > 2) {
shift = 0;
}
}
lastPressTime = millis();
lastKeyPressed = keyPressed;
keyPressed = 13;
}
if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) {
this->notifyObservers(&e);
}
}
prevKeys = keys;
}
return 50;
}
#endif // INPUTBROKER_SERIAL_TYPE

View File

@@ -1,25 +0,0 @@
#pragma once
#include "InputBroker.h"
#include "concurrency/OSThread.h"
class SerialKeyboard : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
explicit SerialKeyboard(const char *name);
protected:
virtual int32_t runOnce() override;
void erase();
private:
const char *_originName;
bool firstTime = 1;
int prevKeys = 0;
int keys = 0;
int shift = 0;
int keyPressed = 13;
int lastKeyPressed = 13;
int quickPress = 0;
unsigned long lastPressTime = 0;
};

View File

@@ -1,21 +0,0 @@
#include "SerialKeyboardImpl.h"
#include "InputBroker.h"
#include "configuration.h"
#ifdef INPUTBROKER_SERIAL_TYPE
SerialKeyboardImpl *aSerialKeyboardImpl;
SerialKeyboardImpl::SerialKeyboardImpl() : SerialKeyboard("serialKB") {}
void SerialKeyboardImpl::init()
{
if (!INPUTBROKER_SERIAL_TYPE) {
disable();
return;
}
inputBroker->registerSource(this);
}
#endif // INPUTBROKER_SERIAL_TYPE

View File

@@ -1,19 +0,0 @@
#pragma once
#include "SerialKeyboard.h"
#include "main.h"
/**
* @brief The idea behind this class to have static methods for the event handlers.
* Check attachInterrupt() at RotaryEncoderInteruptBase.cpp
* Technically you can have as many rotary encoders hardver attached
* to your device as you wish, but you always need to have separate event
* handlers, thus you need to have a RotaryEncoderInterrupt implementation.
*/
class SerialKeyboardImpl : public SerialKeyboard
{
public:
SerialKeyboardImpl();
void init();
};
extern SerialKeyboardImpl *aSerialKeyboardImpl;

View File

@@ -15,17 +15,12 @@
#include "power.h" #include "power.h"
// #include "debug.h" // #include "debug.h"
#include "FSCommon.h" #include "FSCommon.h"
#include "Led.h"
#include "RTC.h" #include "RTC.h"
#include "SPILock.h" #include "SPILock.h"
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#include "concurrency/Periodic.h" #include "concurrency/Periodic.h"
#include "detect/ScanI2C.h" #include "detect/ScanI2C.h"
#if !MESHTASTIC_EXCLUDE_I2C
#include "detect/ScanI2CTwoWire.h" #include "detect/ScanI2CTwoWire.h"
#include <Wire.h>
#endif
#include "detect/axpDebug.h" #include "detect/axpDebug.h"
#include "detect/einkScan.h" #include "detect/einkScan.h"
#include "graphics/RAKled.h" #include "graphics/RAKled.h"
@@ -36,6 +31,7 @@
#include "shutdown.h" #include "shutdown.h"
#include "sleep.h" #include "sleep.h"
#include "target_specific.h" #include "target_specific.h"
#include <Wire.h>
#include <memory> #include <memory>
#include <utility> #include <utility>
// #include <driver/rtc_io.h> // #include <driver/rtc_io.h>
@@ -112,10 +108,6 @@ AccelerometerThread *accelerometerThread = nullptr;
AudioThread *audioThread = nullptr; AudioThread *audioThread = nullptr;
#endif #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; using namespace concurrency;
// We always create a screen object, but we only init it if we find the hardware // We always create a screen object, but we only init it if we find the hardware
@@ -167,10 +159,8 @@ bool pauseBluetoothLogging = false;
bool pmu_found; bool pmu_found;
#if !MESHTASTIC_EXCLUDE_I2C
// Array map of sensor types with i2c address and wire as we'll find in the i2c scan // Array map of sensor types with i2c address and wire as we'll find in the i2c scan
std::pair<uint8_t, TwoWire *> nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1] = {}; std::pair<uint8_t, TwoWire *> nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1] = {};
#endif
Router *router = NULL; // Users of router don't care what sort of subclass implements that API Router *router = NULL; // Users of router don't care what sort of subclass implements that API
@@ -202,7 +192,7 @@ static int32_t ledBlinker()
static bool ledOn; static bool ledOn;
ledOn ^= 1; ledOn ^= 1;
ledBlink.set(ledOn); setLed(ledOn);
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000); return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000);
@@ -213,6 +203,7 @@ uint32_t timeLastPowered = 0;
static Periodic *ledPeriodic; static Periodic *ledPeriodic;
static OSThread *powerFSMthread; static OSThread *powerFSMthread;
static OSThread *ambientLightingThread; static OSThread *ambientLightingThread;
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
RadioInterface *rIf = NULL; RadioInterface *rIf = NULL;
@@ -231,16 +222,10 @@ void printInfo()
{ {
LOG_INFO("S:B:%d,%s\n", HW_VENDOR, optstr(APP_VERSION)); LOG_INFO("S:B:%d,%s\n", HW_VENDOR, optstr(APP_VERSION));
} }
#ifndef PIO_UNIT_TESTING
void setup() void setup()
{ {
concurrency::hasBeenSetup = true; concurrency::hasBeenSetup = true;
#if ARCH_PORTDUINO
SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0);
#else
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
#endif
meshtastic_Config_DisplayConfig_OledType screen_model = meshtastic_Config_DisplayConfig_OledType screen_model =
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64; OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64;
@@ -298,11 +283,21 @@ void setup()
digitalWrite(VEXT_ENABLE, 0); // turn on the display power digitalWrite(VEXT_ENABLE, 0); // turn on the display power
#endif #endif
#if defined(VGNSS_CTRL_V03)
pinMode(VGNSS_CTRL_V03, OUTPUT);
digitalWrite(VGNSS_CTRL_V03, LOW);
#endif
#if defined(VTFT_CTRL_V03) #if defined(VTFT_CTRL_V03)
pinMode(VTFT_CTRL_V03, OUTPUT); pinMode(VTFT_CTRL_V03, OUTPUT);
digitalWrite(VTFT_CTRL_V03, LOW); digitalWrite(VTFT_CTRL_V03, LOW);
#endif #endif
#if defined(VGNSS_CTRL)
pinMode(VGNSS_CTRL, OUTPUT);
digitalWrite(VGNSS_CTRL, LOW);
#endif
#if defined(VTFT_CTRL) #if defined(VTFT_CTRL)
pinMode(VTFT_CTRL, OUTPUT); pinMode(VTFT_CTRL, OUTPUT);
digitalWrite(VTFT_CTRL, LOW); digitalWrite(VTFT_CTRL, LOW);
@@ -313,14 +308,6 @@ void setup()
digitalWrite(RESET_OLED, 1); digitalWrite(RESET_OLED, 1);
#endif #endif
#ifdef SENSOR_POWER_CTRL_PIN
pinMode(SENSOR_POWER_CTRL_PIN, OUTPUT);
digitalWrite(SENSOR_POWER_CTRL_PIN, SENSOR_POWER_ON);
#endif
#ifdef SENSOR_GPS_CONFLICT
bool sensor_detected = false;
#endif
#ifdef PERIPHERAL_WARMUP_MS #ifdef PERIPHERAL_WARMUP_MS
// Some peripherals may require additional time to stabilize after power is connected // Some peripherals may require additional time to stabilize after power is connected
// e.g. I2C on Heltec Vision Master // e.g. I2C on Heltec Vision Master
@@ -362,7 +349,6 @@ void setup()
#endif #endif
#if !MESHTASTIC_EXCLUDE_I2C
#if defined(I2C_SDA1) && defined(ARCH_RP2040) #if defined(I2C_SDA1) && defined(ARCH_RP2040)
Wire1.setSDA(I2C_SDA1); Wire1.setSDA(I2C_SDA1);
Wire1.setSCL(I2C_SCL1); Wire1.setSCL(I2C_SCL1);
@@ -387,7 +373,6 @@ void setup()
#elif HAS_WIRE #elif HAS_WIRE
Wire.begin(); Wire.begin();
#endif #endif
#endif
#ifdef PIN_LCD_RESET #ifdef PIN_LCD_RESET
// FIXME - move this someplace better, LCD is at address 0x3F // FIXME - move this someplace better, LCD is at address 0x3F
@@ -420,7 +405,6 @@ void setup()
powerStatus->observe(&power->newStatus); powerStatus->observe(&power->newStatus);
power->setup(); // Must be after status handler is installed, so that handler gets notified of the initial configuration power->setup(); // Must be after status handler is installed, so that handler gets notified of the initial configuration
#if !MESHTASTIC_EXCLUDE_I2C
// We need to scan here to decide if we have a screen for nodeDB.init() and because power has been applied to // We need to scan here to decide if we have a screen for nodeDB.init() and because power has been applied to
// accessories // accessories
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire()); auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
@@ -460,9 +444,6 @@ void setup()
LOG_INFO("No I2C devices found\n"); LOG_INFO("No I2C devices found\n");
} else { } else {
LOG_INFO("%i I2C devices found\n", i2cCount); LOG_INFO("%i I2C devices found\n", i2cCount);
#ifdef SENSOR_GPS_CONFLICT
sensor_detected = true;
#endif
} }
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@@ -579,7 +560,6 @@ void setup()
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK)
i2cScanner.reset(); i2cScanner.reset();
#endif
#ifdef HAS_SDCARD #ifdef HAS_SDCARD
setupSDCard(); setupSDCard();
@@ -589,7 +569,7 @@ void setup()
#ifdef LED_PIN #ifdef LED_PIN
pinMode(LED_PIN, OUTPUT); pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LED_STATE_ON); // turn on for now digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now
#endif #endif
// Hello // Hello
@@ -640,7 +620,6 @@ void setup()
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64 screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64
#endif #endif
#if !MESHTASTIC_EXCLUDE_I2C
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (acc_info.type != ScanI2C::DeviceType::NONE) { if (acc_info.type != ScanI2C::DeviceType::NONE) {
config.display.wake_on_tap_or_motion = true; config.display.wake_on_tap_or_motion = true;
@@ -656,7 +635,6 @@ void setup()
ambientLightingThread = new AmbientLightingThread(rgb_found.type); ambientLightingThread = new AmbientLightingThread(rgb_found.type);
} }
#endif #endif
#endif
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
drv.begin(); drv.begin();
@@ -694,7 +672,6 @@ void setup()
screen = new graphics::Screen(screen_found, screen_model, screen_geometry); screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
// setup TZ prior to time actions. // setup TZ prior to time actions.
#if !MESHTASTIC_EXCLUDE_TZ
if (*config.device.tzdef) { if (*config.device.tzdef) {
setenv("TZ", config.device.tzdef, 1); setenv("TZ", config.device.tzdef, 1);
} else { } else {
@@ -702,30 +679,22 @@ void setup()
} }
tzset(); tzset();
LOG_DEBUG("Set Timezone to %s\n", getenv("TZ")); LOG_DEBUG("Set Timezone to %s\n", getenv("TZ"));
#endif
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
// If we're taking on the repeater role, ignore GPS // If we're taking on the repeater role, ignore GPS
#ifdef SENSOR_GPS_CONFLICT if (HAS_GPS) {
if (sensor_detected == false) { if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
#endif config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
if (HAS_GPS) { gps = GPS::createGps();
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && if (gps) {
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { gpsStatus->observe(&gps->newStatus);
gps = GPS::createGps(); } else {
if (gps) { LOG_DEBUG("Running without GPS.\n");
gpsStatus->observe(&gps->newStatus);
} else {
LOG_DEBUG("Running without GPS.\n");
}
} }
} }
#ifdef SENSOR_GPS_CONFLICT
} }
#endif
#endif #endif
nodeStatus->observe(&nodeDB->newStatus); nodeStatus->observe(&nodeDB->newStatus);
@@ -734,8 +703,8 @@ void setup()
LOG_DEBUG("Starting audio thread\n"); LOG_DEBUG("Starting audio thread\n");
audioThread = new AudioThread(); audioThread = new AudioThread();
#endif #endif
service = new MeshService();
service->init(); service.init();
// Now that the mesh service is created, create any modules // Now that the mesh service is created, create any modules
setupModules(); setupModules();
@@ -743,7 +712,7 @@ void setup()
#ifdef LED_PIN #ifdef LED_PIN
// Turn LED off after boot, if heartbeat by config // Turn LED off after boot, if heartbeat by config
if (config.device.led_heartbeat_disabled) if (config.device.led_heartbeat_disabled)
digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON); digitalWrite(LED_PIN, LOW ^ LED_INVERTED);
#endif #endif
// Do this after service.init (because that clears error_code) // Do this after service.init (because that clears error_code)
@@ -752,7 +721,6 @@ void setup()
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_NO_AXP192); // Record a hardware fault for missing hardware RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_NO_AXP192); // Record a hardware fault for missing hardware
#endif #endif
#if !MESHTASTIC_EXCLUDE_I2C
// Don't call screen setup until after nodedb is setup (because we need // Don't call screen setup until after nodedb is setup (because we need
// the current region name) // the current region name)
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \ #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \
@@ -765,7 +733,6 @@ void setup()
#else #else
if (screen_found.port != ScanI2C::I2CPort::NO_I2C) if (screen_found.port != ScanI2C::I2CPort::NO_I2C)
screen->setup(); screen->setup();
#endif
#endif #endif
screen->print("Started...\n"); screen->print("Started...\n");
@@ -894,7 +861,7 @@ void setup()
} }
#endif #endif
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) #if defined(USE_SX1262) && !defined(ARCH_PORTDUINO)
if (!rIf) { if (!rIf) {
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) { if (!rIf->init()) {
@@ -908,40 +875,6 @@ void setup()
} }
#endif #endif
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
if (!rIf) {
// Try using the specified TCXO voltage
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("Failed to find SX1262 radio with TCXO using DIO3 reference voltage at %f V\n", tcxoVoltage);
delete rIf;
rIf = NULL;
tcxoVoltage = 0; // if it fails, set the TCXO voltage to zero for the next attempt
} else {
LOG_INFO("SX1262 Radio init succeeded, using ");
LOG_WARN("SX1262 Radio with TCXO");
LOG_INFO(", reference voltage at %f V\n", tcxoVoltage);
radioType = SX1262_RADIO;
}
}
if (!rIf) {
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instea
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("Failed to find SX1262 radio with XTAL using DIO3 reference voltage at %f V\n", tcxoVoltage);
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 Radio init succeeded, using ");
LOG_WARN("SX1262 Radio with XTAL");
LOG_INFO(", reference voltage at %f V\n", tcxoVoltage);
radioType = SX1262_RADIO;
}
}
#endif
#if defined(USE_SX1268) #if defined(USE_SX1268)
if (!rIf) { if (!rIf) {
rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
@@ -1027,16 +960,9 @@ void setup()
mqttInit(); mqttInit();
#endif #endif
#ifdef RF95_FAN_EN
// Ability to disable FAN if PIN has been set with RF95_FAN_EN.
// Make sure LoRa has been started before disabling FAN.
if (config.lora.pa_fan_disabled)
digitalWrite(RF95_FAN_EN, LOW ^ 0);
#endif
#ifndef ARCH_PORTDUINO #ifndef ARCH_PORTDUINO
// Initialize Wifi // Initialize Wifi
#if HAS_WIFI #if HAS_WIFI
initWifi(); initWifi();
#endif #endif
@@ -1080,7 +1006,7 @@ void setup()
powerFSMthread = new PowerFSMThread(); powerFSMthread = new PowerFSMThread();
setCPUFast(false); // 80MHz is fine for our slow peripherals setCPUFast(false); // 80MHz is fine for our slow peripherals
} }
#endif
uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes) uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client) uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client)
@@ -1103,7 +1029,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled; deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled;
return deviceMetadata; return deviceMetadata;
} }
#ifndef PIO_UNIT_TESTING
void loop() void loop()
{ {
runASAP = false; runASAP = false;
@@ -1134,7 +1060,7 @@ void loop()
// TODO: This should go into a thread handled by FreeRTOS. // TODO: This should go into a thread handled by FreeRTOS.
// handleWebResponse(); // handleWebResponse();
service->loop(); service.loop();
long delayMsec = mainController.runOrDelay(); long delayMsec = mainController.runOrDelay();
@@ -1148,5 +1074,4 @@ void loop()
mainDelay.delay(delayMsec); mainDelay.delay(delayMsec);
} }
// if (didWake) LOG_DEBUG("wake!\n"); // if (didWake) LOG_DEBUG("wake!\n");
} }
#endif

View File

@@ -65,6 +65,12 @@ extern bool isVibrating;
extern int TCPPort; // set by Portduino extern int TCPPort; // set by Portduino
// extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
// extern meshtastic::PowerStatus *powerStatus;
// extern meshtastic::GPSStatus *gpsStatus;
// extern meshtastic::NodeStatusHandler *nodeStatusHandler;
// Return a human readable string of the form "Meshtastic_ab13" // Return a human readable string of the form "Meshtastic_ab13"
const char *getDeviceName(); const char *getDeviceName();
@@ -85,5 +91,5 @@ void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearB
meshtastic_DeviceMetadata getDeviceMetadata(); meshtastic_DeviceMetadata getDeviceMetadata();
// We default to 4MHz SPI, SPI mode 0 // FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that
extern SPISettings spiSettings; extern SPISettings spiSettings;

View File

@@ -1,7 +1,5 @@
#include "Channels.h" #include "Channels.h"
#include "../userPrefs.h"
#include "CryptoEngine.h" #include "CryptoEngine.h"
#include "Default.h"
#include "DisplayFormatters.h" #include "DisplayFormatters.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "RadioInterface.h" #include "RadioInterface.h"
@@ -92,39 +90,15 @@ void Channels::initDefaultChannel(ChannelIndex chIndex)
loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; // Default to Long Range & Fast loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; // Default to Long Range & Fast
loraConfig.use_preset = true; loraConfig.use_preset = true;
loraConfig.tx_power = 0; // default loraConfig.tx_power = 0; // default
loraConfig.channel_num = 0;
uint8_t defaultpskIndex = 1; uint8_t defaultpskIndex = 1;
channelSettings.psk.bytes[0] = defaultpskIndex; channelSettings.psk.bytes[0] = defaultpskIndex;
channelSettings.psk.size = 1; channelSettings.psk.size = 1;
strncpy(channelSettings.name, "", sizeof(channelSettings.name)); strncpy(channelSettings.name, "", sizeof(channelSettings.name));
channelSettings.module_settings.position_precision = 13; // default to sending location on the primary channel channelSettings.module_settings.position_precision = 32; // default to sending location on the primary channel
channelSettings.has_module_settings = true; channelSettings.has_module_settings = true;
ch.has_settings = true; ch.has_settings = true;
ch.role = meshtastic_Channel_Role_PRIMARY; ch.role = meshtastic_Channel_Role_PRIMARY;
#ifdef LORACONFIG_MODEM_PRESET_USERPREFS
loraConfig.modem_preset = LORACONFIG_MODEM_PRESET_USERPREFS;
#endif
#ifdef LORACONFIG_CHANNEL_NUM_USERPREFS
loraConfig.channel_num = LORACONFIG_CHANNEL_NUM_USERPREFS;
#endif
// Install custom defaults. Will eventually support setting multiple default channels
if (chIndex == 0) {
#ifdef CHANNEL_0_PSK_USERPREFS
static const uint8_t defaultpsk[] = CHANNEL_0_PSK_USERPREFS;
memcpy(channelSettings.psk.bytes, defaultpsk, sizeof(defaultpsk));
channelSettings.psk.size = sizeof(defaultpsk);
#endif
#ifdef CHANNEL_0_NAME_USERPREFS
strcpy(channelSettings.name, CHANNEL_0_NAME_USERPREFS);
#endif
#ifdef CHANNEL_0_PRECISION_USERPREFS
channelSettings.module_settings.position_precision = CHANNEL_0_PRECISION_USERPREFS;
#endif
}
} }
CryptoKey Channels::getKey(ChannelIndex chIndex) CryptoKey Channels::getKey(ChannelIndex chIndex)
@@ -277,12 +251,6 @@ void Channels::setChannel(const meshtastic_Channel &c)
bool Channels::anyMqttEnabled() bool Channels::anyMqttEnabled()
{ {
#if EVENT_MODE
// Don't publish messages on the public MQTT broker if we are in event mode
if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0) {
return false;
}
#endif
for (int i = 0; i < getNumChannels(); i++) for (int i = 0; i < getNumChannels(); i++)
if (channelFile.channels[i].role != meshtastic_Channel_Role_DISABLED && channelFile.channels[i].has_settings && if (channelFile.channels[i].role != meshtastic_Channel_Role_DISABLED && channelFile.channels[i].has_settings &&
(channelFile.channels[i].settings.downlink_enabled || channelFile.channels[i].settings.uplink_enabled)) (channelFile.channels[i].settings.downlink_enabled || channelFile.channels[i].settings.uplink_enabled))
@@ -309,14 +277,12 @@ const char *Channels::getName(size_t chIndex)
return channelName; return channelName;
} }
bool Channels::isDefaultChannel(ChannelIndex chIndex) bool Channels::isDefaultChannel(const meshtastic_Channel &ch)
{ {
const auto &ch = getByIndex(chIndex);
if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) {
const char *name = getName(chIndex);
const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
// Check if the name is the default derived from the modem preset // Check if the name is the default derived from the modem preset
if (strcmp(name, presetName) == 0) if (strcmp(ch.settings.name, presetName) == 0)
return true; return true;
} }
return false; return false;
@@ -329,7 +295,8 @@ bool Channels::hasDefaultChannel()
return false; return false;
// Check if any of the channels are using the default name and PSK // Check if any of the channels are using the default name and PSK
for (size_t i = 0; i < getNumChannels(); i++) { for (size_t i = 0; i < getNumChannels(); i++) {
if (isDefaultChannel(i)) const auto &ch = getByIndex(i);
if (isDefaultChannel(ch))
return true; return true;
} }
return false; return false;

View File

@@ -84,7 +84,7 @@ class Channels
int16_t setActiveByIndex(ChannelIndex channelIndex); int16_t setActiveByIndex(ChannelIndex channelIndex);
// Returns true if the channel has the default name and PSK // Returns true if the channel has the default name and PSK
bool isDefaultChannel(ChannelIndex chIndex); bool isDefaultChannel(const meshtastic_Channel &ch);
// Returns true if we can be reached via a channel with the default settings given a region and modem preset // Returns true if we can be reached via a channel with the default settings given a region and modem preset
bool hasDefaultChannel(); bool hasDefaultChannel();
@@ -129,4 +129,4 @@ class Channels
}; };
/// Singleton channel table /// Singleton channel table
extern Channels channels; extern Channels channels;

View File

@@ -1,191 +1,6 @@
#include "CryptoEngine.h" #include "CryptoEngine.h"
#include "NodeDB.h"
#include "RadioInterface.h"
#include "architecture.h"
#include "configuration.h" #include "configuration.h"
#if !(MESHTASTIC_EXCLUDE_PKI)
#include "aes-ccm.h"
#include "meshUtils.h"
#include <Crypto.h>
#include <Curve25519.h>
#include <SHA256.h>
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
/**
* Create a public/private key pair with Curve25519.
*
* @param pubKey The destination for the public key.
* @param privKey The destination for the private key.
*/
void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey)
{
LOG_DEBUG("Generating Curve25519 key pair...\n");
Curve25519::dh1(public_key, private_key);
memcpy(pubKey, public_key, sizeof(public_key));
memcpy(privKey, private_key, sizeof(private_key));
}
#endif
void CryptoEngine::clearKeys()
{
memset(public_key, 0, sizeof(public_key));
memset(private_key, 0, sizeof(private_key));
}
/**
* Encrypt a packet's payload using a key generated with Curve25519 and SHA256
* for a specific node.
*
* @param bytes is updated in place
*/
bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes,
uint8_t *bytesOut)
{
uint8_t *auth;
uint32_t *extraNonce;
long extraNonceTmp = random();
auth = bytesOut + numBytes;
extraNonce = (uint32_t *)(auth + 8);
*extraNonce = extraNonceTmp;
LOG_INFO("Random nonce value: %d\n", *extraNonce);
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(toNode);
if (node->num < 1 || node->user.public_key.size == 0) {
LOG_DEBUG("Node %d or their public_key not found\n", toNode);
return false;
}
if (!crypto->setDHKey(toNode)) {
return false;
}
initNonce(fromNode, packetNum, *extraNonce);
// Calculate the shared secret with the destination node and encrypt
printBytes("Attempting encrypt using nonce: ", nonce, 13);
printBytes("Attempting encrypt using shared_key: ", shared_key, 32);
aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut,
auth); // this can write up to 15 bytes longer than numbytes past bytesOut
*extraNonce = extraNonceTmp;
return true;
}
/**
* Decrypt a packet's payload using a key generated with Curve25519 and SHA256
* for a specific node.
*
* @param bytes is updated in place
*/
bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut)
{
uint8_t *auth; // set to last 8 bytes of text?
uint32_t *extraNonce;
auth = bytes + numBytes - 12;
extraNonce = (uint32_t *)(auth + 8);
LOG_INFO("Random nonce value: %d\n", *extraNonce);
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode);
if (node == nullptr || node->num < 1 || node->user.public_key.size == 0) {
LOG_DEBUG("Node or its public key not found in database\n");
return false;
}
// Calculate the shared secret with the sending node and decrypt
if (!crypto->setDHKey(fromNode)) {
return false;
}
initNonce(fromNode, packetNum, *extraNonce);
printBytes("Attempting decrypt using nonce: ", nonce, 13);
printBytes("Attempting decrypt using shared_key: ", shared_key, 32);
return aes_ccm_ad(shared_key, 32, nonce, 8, bytes, numBytes - 12, nullptr, 0, auth, bytesOut);
}
void CryptoEngine::setDHPrivateKey(uint8_t *_private_key)
{
memcpy(private_key, _private_key, 32);
}
/**
* Set the PKI key used for encrypt, decrypt.
*
* @param nodeNum the node number of the node who's public key we want to use
*/
bool CryptoEngine::setDHKey(uint32_t nodeNum)
{
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
if (node->num < 1 || node->user.public_key.size == 0) {
LOG_DEBUG("Node %d or their public_key not found\n", nodeNum);
return false;
}
if (!setDHPublicKey(node->user.public_key.bytes))
return false;
printBytes("DH Output: ", shared_key, 32);
/**
* D.J. Bernstein reccomends hashing the shared key. We want to do this because there are
* at least 128 bits of entropy in the 256-bit output of the DH key exchange, but we don't
* really know where. If you extract, for instance, the first 128 bits with basic truncation,
* then you don't know if you got all of your 128 entropy bits, or less, possibly much less.
*
* No exploitable bias is really known at that point, but we know enough to be wary.
* Hashing the DH output is a simple and safe way to gather all the entropy and spread
* it around as needed.
*/
crypto->hash(shared_key, 32);
return true;
}
/**
* Hash arbitrary data using SHA256.
*
* @param bytes
* @param numBytes
*/
void CryptoEngine::hash(uint8_t *bytes, size_t numBytes)
{
SHA256 hash;
size_t posn, len;
uint8_t size = numBytes;
uint8_t inc = 16;
hash.reset();
for (posn = 0; posn < size; posn += inc) {
len = size - posn;
if (len > inc)
len = inc;
hash.update(bytes + posn, len);
}
hash.finalize(bytes, 32);
}
void CryptoEngine::aesSetKey(const uint8_t *key_bytes, size_t key_len)
{
if (aes) {
delete aes;
aes = nullptr;
}
if (key_len != 0) {
aes = new AESSmall256();
aes->setKey(key_bytes, key_len);
}
}
void CryptoEngine::aesEncrypt(uint8_t *in, uint8_t *out)
{
aes->encryptBlock(out, in);
}
bool CryptoEngine::setDHPublicKey(uint8_t *pubKey)
{
uint8_t local_priv[32];
memcpy(shared_key, pubKey, 32);
memcpy(local_priv, private_key, 32);
// Calculate the shared secret with the specified node's public key and our private key
// This includes an internal weak key check, which among other things looks for an all 0 public key and shared key.
if (!Curve25519::dh2(shared_key, local_priv)) {
LOG_WARN("Curve25519DH step 2 failed!\n");
return false;
}
return true;
}
#endif
concurrency::Lock *cryptLock; concurrency::Lock *cryptLock;
void CryptoEngine::setKey(const CryptoKey &k) void CryptoEngine::setKey(const CryptoKey &k)
@@ -199,59 +14,24 @@ void CryptoEngine::setKey(const CryptoKey &k)
* *
* @param bytes is updated in place * @param bytes is updated in place
*/ */
void CryptoEngine::encryptPacket(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes)
{ {
if (key.length > 0) { LOG_WARN("noop encryption!\n");
initNonce(fromNode, packetId);
if (numBytes <= MAX_BLOCKSIZE) {
encryptAESCtr(key, nonce, numBytes, bytes);
} else {
LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes);
}
}
} }
void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes)
{ {
// For CTR, the implementation is the same LOG_WARN("noop decryption!\n");
encryptPacket(fromNode, packetId, numBytes, bytes);
}
// Generic implementation of AES-CTR encryption.
void CryptoEngine::encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes)
{
if (ctr) {
delete ctr;
ctr = nullptr;
}
if (_key.length == 16)
ctr = new CTR<AES128>();
else
ctr = new CTR<AES256>();
ctr->setKey(_key.bytes, _key.length);
static uint8_t scratch[MAX_BLOCKSIZE];
memcpy(scratch, bytes, numBytes);
memset(scratch + numBytes, 0,
sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it)
ctr->setIV(_nonce, 16);
ctr->setCounterSize(4);
ctr->encrypt(bytes, scratch, numBytes);
} }
/** /**
* Init our 128 bit nonce for a new packet * Init our 128 bit nonce for a new packet
*/ */
void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetId, uint32_t extraNonce) void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetId)
{ {
memset(nonce, 0, sizeof(nonce)); memset(nonce, 0, sizeof(nonce));
// use memcpy to avoid breaking strict-aliasing // use memcpy to avoid breaking strict-aliasing
memcpy(nonce, &packetId, sizeof(uint64_t)); memcpy(nonce, &packetId, sizeof(uint64_t));
memcpy(nonce + sizeof(uint64_t), &fromNode, sizeof(uint32_t)); memcpy(nonce + sizeof(uint64_t), &fromNode, sizeof(uint32_t));
if (extraNonce) }
memcpy(nonce + sizeof(uint32_t), &extraNonce, sizeof(uint32_t));
}
#ifndef HAS_CUSTOM_CRYPTO_ENGINE
CryptoEngine *crypto = new CryptoEngine;
#endif

View File

@@ -1,9 +1,6 @@
#pragma once #pragma once
#include "AES.h"
#include "CTR.h"
#include "concurrency/LockGuard.h" #include "concurrency/LockGuard.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
#include <Arduino.h> #include <Arduino.h>
extern concurrency::Lock *cryptLock; extern concurrency::Lock *cryptLock;
@@ -24,31 +21,14 @@ struct CryptoKey {
class CryptoEngine class CryptoEngine
{ {
protected:
/** Our per packet nonce */
uint8_t nonce[16] = {0};
CryptoKey key = {};
public: public:
#if !(MESHTASTIC_EXCLUDE_PKI)
uint8_t public_key[32] = {0};
#endif
virtual ~CryptoEngine() {} virtual ~CryptoEngine() {}
#if !(MESHTASTIC_EXCLUDE_PKI)
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey);
#endif
void clearKeys();
void setDHPrivateKey(uint8_t *_private_key);
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes,
uint8_t *bytesOut);
virtual bool decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut);
bool setDHKey(uint32_t nodeNum);
virtual bool setDHPublicKey(uint8_t *publicKey);
virtual void hash(uint8_t *bytes, size_t numBytes);
virtual void aesSetKey(const uint8_t *key, size_t key_len);
virtual void aesEncrypt(uint8_t *in, uint8_t *out);
AESSmall256 *aes = NULL;
#endif
/** /**
* Set the key used for encrypt, decrypt. * Set the key used for encrypt, decrypt.
@@ -66,20 +46,10 @@ class CryptoEngine
* *
* @param bytes is updated in place * @param bytes is updated in place
*/ */
virtual void encryptPacket(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes);
virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes);
virtual void encryptAESCtr(CryptoKey key, uint8_t *nonce, size_t numBytes, uint8_t *bytes);
#ifndef PIO_UNIT_TESTING
protected: protected:
#endif
/** Our per packet nonce */
uint8_t nonce[16] = {0};
CryptoKey key = {};
CTRCommon *ctr = NULL;
#if !(MESHTASTIC_EXCLUDE_PKI)
uint8_t shared_key[32] = {0};
uint8_t private_key[32] = {0};
#endif
/** /**
* Init our 128 bit nonce for a new packet * Init our 128 bit nonce for a new packet
* *
@@ -88,7 +58,7 @@ class CryptoEngine
* a 32 bit sending node number (stored in little endian order) * a 32 bit sending node number (stored in little endian order)
* a 32 bit block counter (starts at zero) * a 32 bit block counter (starts at zero)
*/ */
void initNonce(uint32_t fromNode, uint64_t packetId, uint32_t extraNonce = 0); void initNonce(uint32_t fromNode, uint64_t packetId);
}; };
extern CryptoEngine *crypto; extern CryptoEngine *crypto;

View File

@@ -1,5 +1,4 @@
#include "Default.h" #include "Default.h"
#include "../userPrefs.h"
uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval) uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval)
{ {
@@ -41,13 +40,4 @@ uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t d
return getConfiguredOrDefaultMs(configured, defaultValue); return getConfiguredOrDefaultMs(configured, defaultValue);
return getConfiguredOrDefaultMs(configured, defaultValue) * congestionScalingCoefficient(numOnlineNodes); return getConfiguredOrDefaultMs(configured, defaultValue) * congestionScalingCoefficient(numOnlineNodes);
}
uint8_t Default::getConfiguredOrDefaultHopLimit(uint8_t configured)
{
#if EVENT_MODE
return (configured > HOP_RELIABLE) ? HOP_RELIABLE : config.lora.hop_limit;
#else
return (configured >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit;
#endif
} }

View File

@@ -13,9 +13,7 @@
#define default_min_wake_secs 10 #define default_min_wake_secs 10
#define default_screen_on_secs IF_ROUTER(1, 60 * 10) #define default_screen_on_secs IF_ROUTER(1, 60 * 10)
#define default_node_info_broadcast_secs 3 * 60 * 60 #define default_node_info_broadcast_secs 3 * 60 * 60
#define default_neighbor_info_broadcast_secs 6 * 60 * 60
#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour #define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
#define min_neighbor_info_broadcast_secs 2 * 60 * 60
#define default_mqtt_address "mqtt.meshtastic.org" #define default_mqtt_address "mqtt.meshtastic.org"
#define default_mqtt_username "meshdev" #define default_mqtt_username "meshdev"
@@ -32,7 +30,6 @@ class Default
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval); static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue); static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes); static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes);
static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured);
private: private:
static float congestionScalingCoefficient(int numOnlineNodes) static float congestionScalingCoefficient(int numOnlineNodes)

View File

@@ -1,5 +1,4 @@
#include "FloodingRouter.h" #include "FloodingRouter.h"
#include "../userPrefs.h"
#include "configuration.h" #include "configuration.h"
#include "mesh-pb-constants.h" #include "mesh-pb-constants.h"
@@ -47,13 +46,6 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
tosend->hop_limit--; // bump down the hop count tosend->hop_limit--; // bump down the hop count
#if EVENT_MODE
if (tosend->hop_limit > 2) {
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
tosend->hop_start -= (tosend->hop_limit - 2);
tosend->hop_limit = 2;
}
#endif
LOG_INFO("Rebroadcasting received floodmsg to neighbors\n"); LOG_INFO("Rebroadcasting received floodmsg to neighbors\n");
// Note: we are careful to resend using the original senders node id // Note: we are careful to resend using the original senders node id

View File

@@ -100,13 +100,7 @@ template <typename T> bool LR11x0Interface<T>::init()
// FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option // FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option
if (res == RADIOLIB_ERR_NONE) if (res == RADIOLIB_ERR_NONE)
res = lora.setRegulatorDCDC(); res = lora.setRegulatorDCDC();
#ifdef TRACKER_T1000_E
#ifdef LR11X0_DIO_RF_SWITCH_CONFIG
res = lora.setDioAsRfSwitch(LR11X0_DIO_RF_SWITCH_CONFIG);
#else
res = lora.setDioAsRfSwitch(0x03, 0x0, 0x01, 0x03, 0x02, 0x0, 0x0, 0x0);
#endif
#endif
if (res == RADIOLIB_ERR_NONE) { if (res == RADIOLIB_ERR_NONE) {
if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate
res = lora.setRxBoostedGainMode(true); res = lora.setRxBoostedGainMode(true);
@@ -285,15 +279,17 @@ template <typename T> bool LR11x0Interface<T>::isActivelyReceiving()
template <typename T> bool LR11x0Interface<T>::sleep() template <typename T> bool LR11x0Interface<T>::sleep()
{ {
// Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet
// \todo Display actual typename of the adapter, not just `LR11x0` // \todo Display actual typename of the adapter, not just `LR11x0`
LOG_DEBUG("LR11x0 entering sleep mode\n"); LOG_DEBUG("LR11x0 entering sleep mode (FIXME, don't keep config)\n");
setStandby(); // Stop any pending operations setStandby(); // Stop any pending operations
// turn off TCXO if it was powered // turn off TCXO if it was powered
lora.setTCXO(0); // FIXME - this isn't correct
// lora.setTCXO(0);
// put chipset into sleep mode (we've already disabled interrupts by now) // put chipset into sleep mode (we've already disabled interrupts by now)
bool keepConfig = false; bool keepConfig = true;
lora.sleep(keepConfig, 0); // Note: we do not keep the config, full reinit will be needed lora.sleep(keepConfig, 0); // Note: we do not keep the config, full reinit will be needed
#ifdef LR11X0_POWER_EN #ifdef LR11X0_POWER_EN

View File

@@ -55,7 +55,7 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod
p->decoded.request_id = idFrom; p->decoded.request_id = idFrom;
p->channel = chIndex; p->channel = chIndex;
if (err != meshtastic_Routing_Error_NONE) if (err != meshtastic_Routing_Error_NONE)
LOG_WARN("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); LOG_ERROR("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id);
return p; return p;
} }
@@ -170,7 +170,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
if (isDecoded && mp.decoded.want_response && toUs) { if (isDecoded && mp.decoded.want_response && toUs) {
if (currentReply) { if (currentReply) {
printPacket("Sending response", currentReply); printPacket("Sending response", currentReply);
service->sendToMesh(currentReply); service.sendToMesh(currentReply);
currentReply = NULL; currentReply = NULL;
} else if (mp.from != ourNodeNum && !ignoreRequest) { } else if (mp.from != ourNodeNum && !ignoreRequest) {
// Note: if the message started with the local node or a module asked to ignore the request, we don't want to send a // Note: if the message started with the local node or a module asked to ignore the request, we don't want to send a

View File

@@ -19,8 +19,8 @@
#include <assert.h> #include <assert.h>
#include <string> #include <string>
#if ARCH_PORTDUINO #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
#include "PortduinoGlue.h" #include "nimble/NimbleBluetooth.h"
#endif #endif
/* /*
@@ -40,33 +40,41 @@ arbitrating to select a node number and keeping the current nodedb.
The algorithm is as follows: The algorithm is as follows:
* when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so * when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so
the new node can build its node db) the new node can build its node db)
* If a node ever receives a User (not just the first broadcast) message where the sender node number equals our node number, that
indicates a collision has occurred and the following steps should happen:
If the receiving node (that was already in the mesh)'s macaddr is LOWER than the new User who just tried to sign in: it gets to
keep its nodenum. We send a broadcast message of OUR User (we use a broadcast so that the other node can receive our message,
considering we have the same id - it also serves to let observers correct their nodedb) - this case is rare so it should be okay.
If any node receives a User where the macaddr is GTE than their local macaddr, they have been vetoed and should pick a new random
nodenum (filtering against whatever it knows about the nodedb) and rebroadcast their User.
FIXME in the initial proof of concept we just skip the entire want/deny flow and just hand pick node numbers at first.
*/ */
MeshService *service; MeshService service;
static MemoryDynamic<meshtastic_MqttClientProxyMessage> staticMqttClientProxyMessagePool; static MemoryDynamic<meshtastic_MqttClientProxyMessage> staticMqttClientProxyMessagePool;
static MemoryDynamic<meshtastic_QueueStatus> staticQueueStatusPool; static MemoryDynamic<meshtastic_QueueStatus> staticQueueStatusPool;
static MemoryDynamic<meshtastic_ClientNotification> staticClientNotificationPool;
Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool; Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool;
Allocator<meshtastic_ClientNotification> &clientNotificationPool = staticClientNotificationPool;
Allocator<meshtastic_QueueStatus> &queueStatusPool = staticQueueStatusPool; Allocator<meshtastic_QueueStatus> &queueStatusPool = staticQueueStatusPool;
#include "Router.h" #include "Router.h"
MeshService::MeshService() MeshService::MeshService()
: toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE), : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE)
toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2)
{ {
lastQueueStatus = {0, 0, 16, 0}; lastQueueStatus = {0, 0, 16, 0};
} }
void MeshService::init() void MeshService::init()
{ {
// moved much earlier in boot (called from setup())
// nodeDB.init();
#if HAS_GPS #if HAS_GPS
if (gps) if (gps)
gpsObserver.observe(&gps->newStatus); gpsObserver.observe(&gps->newStatus);
@@ -329,20 +337,6 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage
fromNum++; fromNum++;
} }
void MeshService::sendClientNotification(meshtastic_ClientNotification *n)
{
LOG_DEBUG("Sending client notification to phone\n");
if (toPhoneClientNotificationQueue.numFree() == 0) {
LOG_WARN("ClientNotification queue is full, discarding oldest\n");
meshtastic_ClientNotification *d = toPhoneClientNotificationQueue.dequeuePtr(0);
if (d)
releaseClientNotificationToPool(d);
}
assert(toPhoneClientNotificationQueue.enqueue(n, 0));
fromNum++;
}
meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode() meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode()
{ {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
@@ -407,4 +401,4 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
bool MeshService::isToPhoneQueueEmpty() bool MeshService::isToPhoneQueueEmpty()
{ {
return toPhoneQueue.isEmpty(); return toPhoneQueue.isEmpty();
} }

View File

@@ -21,7 +21,6 @@
extern Allocator<meshtastic_QueueStatus> &queueStatusPool; extern Allocator<meshtastic_QueueStatus> &queueStatusPool;
extern Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool; extern Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool;
extern Allocator<meshtastic_ClientNotification> &clientNotificationPool;
/** /**
* Top level app for this service. keeps the mesh, the radio config and the queue of received packets. * Top level app for this service. keeps the mesh, the radio config and the queue of received packets.
@@ -45,9 +44,6 @@ class MeshService
// keep list of MqttClientProxyMessages to be send to the client for delivery // keep list of MqttClientProxyMessages to be send to the client for delivery
PointerQueue<meshtastic_MqttClientProxyMessage> toPhoneMqttProxyQueue; PointerQueue<meshtastic_MqttClientProxyMessage> toPhoneMqttProxyQueue;
// keep list of ClientNotifications to be send to the client (phone)
PointerQueue<meshtastic_ClientNotification> toPhoneClientNotificationQueue;
// This holds the last QueueStatus send // This holds the last QueueStatus send
meshtastic_QueueStatus lastQueueStatus; meshtastic_QueueStatus lastQueueStatus;
@@ -101,9 +97,6 @@ class MeshService
// Release MqttClientProxyMessage packet to pool // Release MqttClientProxyMessage packet to pool
void releaseMqttClientProxyMessageToPool(meshtastic_MqttClientProxyMessage *p) { mqttClientProxyMessagePool.release(p); } void releaseMqttClientProxyMessageToPool(meshtastic_MqttClientProxyMessage *p) { mqttClientProxyMessagePool.release(p); }
/// Release the next ClientNotification packet to pool.
void releaseClientNotificationToPool(meshtastic_ClientNotification *p) { clientNotificationPool.release(p); }
/** /**
* Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) * Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh)
* Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep
@@ -141,9 +134,6 @@ class MeshService
/// Send an MQTT message to the phone for client proxying /// Send an MQTT message to the phone for client proxying
void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m); void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m);
/// Send a ClientNotification to the phone
void sendClientNotification(meshtastic_ClientNotification *cn);
bool isToPhoneQueueEmpty(); bool isToPhoneQueueEmpty();
ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id); ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id);
@@ -160,4 +150,4 @@ class MeshService
friend class RoutingModule; friend class RoutingModule;
}; };
extern MeshService *service; extern MeshService service;

View File

@@ -1,4 +1,3 @@
#include "../userPrefs.h"
#include "configuration.h" #include "configuration.h"
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h" #include "GPS.h"
@@ -14,12 +13,10 @@
#include "PowerFSM.h" #include "PowerFSM.h"
#include "RTC.h" #include "RTC.h"
#include "Router.h" #include "Router.h"
#include "SafeFile.h"
#include "TypeConversions.h" #include "TypeConversions.h"
#include "error.h" #include "error.h"
#include "main.h" #include "main.h"
#include "mesh-pb-constants.h" #include "mesh-pb-constants.h"
#include "meshUtils.h"
#include "modules/NeighborInfoModule.h" #include "modules/NeighborInfoModule.h"
#include <ErriezCRC32.h> #include <ErriezCRC32.h>
#include <algorithm> #include <algorithm>
@@ -55,7 +52,6 @@ meshtastic_LocalConfig config;
meshtastic_LocalModuleConfig moduleConfig; meshtastic_LocalModuleConfig moduleConfig;
meshtastic_ChannelFile channelFile; meshtastic_ChannelFile channelFile;
meshtastic_OEMStore oemStore; meshtastic_OEMStore oemStore;
static bool hasOemStore = false;
bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field) bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
{ {
@@ -124,37 +120,7 @@ NodeDB::NodeDB()
// Include our owner in the node db under our nodenum // Include our owner in the node db under our nodenum
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
if (!config.has_security) { info->user = owner;
config.has_security = true;
config.security.serial_enabled = config.device.serial_enabled;
config.security.is_managed = config.device.is_managed;
}
#if !(MESHTASTIC_EXCLUDE_PKI)
// Calculate Curve25519 public and private keys
printBytes("Old Pubkey", config.security.public_key.bytes, 32);
if (config.security.private_key.size == 32 && config.security.public_key.size == 32) {
LOG_INFO("Using saved PKI keys\n");
owner.public_key.size = config.security.public_key.size;
memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size);
crypto->setDHPrivateKey(config.security.private_key.bytes);
} else {
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
LOG_INFO("Generating new PKI keys\n");
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
config.security.public_key.size = 32;
config.security.private_key.size = 32;
printBytes("New Pubkey", config.security.public_key.bytes, 32);
owner.public_key.size = 32;
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
#else
LOG_INFO("No PKI keys set, and generation disabled!\n");
#endif
}
#endif
info->user = TypeConversions::ConvertToUserLite(owner);
info->has_user = true; info->has_user = true;
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@@ -221,16 +187,14 @@ bool NodeDB::resetRadioConfig(bool factory_reset)
return didFactoryReset; return didFactoryReset;
} }
bool NodeDB::factoryReset(bool eraseBleBonds) bool NodeDB::factoryReset()
{ {
LOG_INFO("Performing factory reset!\n"); LOG_INFO("Performing factory reset!\n");
// first, remove the "/prefs" (this removes most prefs) // first, remove the "/prefs" (this removes most prefs)
rmDir("/prefs"); rmDir("/prefs");
#ifdef FSCom
if (FSCom.exists("/static/rangetest.csv") && !FSCom.remove("/static/rangetest.csv")) { if (FSCom.exists("/static/rangetest.csv") && !FSCom.remove("/static/rangetest.csv")) {
LOG_ERROR("Could not remove rangetest.csv file\n"); LOG_ERROR("Could not remove rangetest.csv file\n");
} }
#endif
// second, install default state (this will deal with the duplicate mac address issue) // second, install default state (this will deal with the duplicate mac address issue)
installDefaultDeviceState(); installDefaultDeviceState();
installDefaultConfig(); installDefaultConfig();
@@ -238,21 +202,18 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
installDefaultChannels(); installDefaultChannels();
// third, write everything to disk // third, write everything to disk
saveToDisk(); saveToDisk();
if (eraseBleBonds) {
LOG_INFO("Erasing BLE bonds\n");
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
// This will erase what's in NVS including ssl keys, persistent variables and ble pairing // This will erase what's in NVS including ssl keys, persistent variables and ble pairing
nvs_flash_erase(); nvs_flash_erase();
#endif #endif
#ifdef ARCH_NRF52 #ifdef ARCH_NRF52
Bluefruit.begin(); Bluefruit.begin();
LOG_INFO("Clearing bluetooth bonds!\n"); LOG_INFO("Clearing bluetooth bonds!\n");
bond_print_list(BLE_GAP_ROLE_PERIPH); bond_print_list(BLE_GAP_ROLE_PERIPH);
bond_print_list(BLE_GAP_ROLE_CENTRAL); bond_print_list(BLE_GAP_ROLE_CENTRAL);
Bluefruit.Periph.clearBonds(); Bluefruit.Periph.clearBonds();
Bluefruit.Central.clearBonds(); Bluefruit.Central.clearBonds();
#endif #endif
}
return true; return true;
} }
@@ -268,37 +229,16 @@ void NodeDB::installDefaultConfig()
config.has_power = true; config.has_power = true;
config.has_network = true; config.has_network = true;
config.has_bluetooth = (HAS_BLUETOOTH ? true : false); config.has_bluetooth = (HAS_BLUETOOTH ? true : false);
config.has_security = true;
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL; config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
config.lora.sx126x_rx_boosted_gain = true; config.lora.sx126x_rx_boosted_gain = true;
config.lora.tx_enabled = config.lora.tx_enabled =
true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off) true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off)
config.lora.override_duty_cycle = false; config.lora.override_duty_cycle = false;
#ifdef CONFIG_LORA_REGION_USERPREFS
config.lora.region = CONFIG_LORA_REGION_USERPREFS;
#else
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
#endif
#ifdef LORACONFIG_MODEM_PRESET_USERPREFS
config.lora.modem_preset = LORACONFIG_MODEM_PRESET_USERPREFS;
#else
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
#endif
config.lora.hop_limit = HOP_RELIABLE; config.lora.hop_limit = HOP_RELIABLE;
#ifdef CONFIG_LORA_IGNORE_MQTT_USERPREFS
config.lora.ignore_mqtt = CONFIG_LORA_IGNORE_MQTT_USERPREFS;
#else
config.lora.ignore_mqtt = false; config.lora.ignore_mqtt = false;
#endif
#ifdef ADMIN_KEY_USERPREFS
memcpy(config.security.admin_key.bytes, admin_key_userprefs, 32);
config.security.admin_key.size = 32;
#else
config.security.admin_key.size = 0;
#endif
config.security.public_key.size = 0;
config.security.private_key.size = 0;
#ifdef PIN_GPS_EN #ifdef PIN_GPS_EN
config.position.gps_en_gpio = PIN_GPS_EN; config.position.gps_en_gpio = PIN_GPS_EN;
#endif #endif
@@ -322,8 +262,7 @@ void NodeDB::installDefaultConfig()
config.position.broadcast_smart_minimum_interval_secs = 30; config.position.broadcast_smart_minimum_interval_secs = 30;
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER) if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER)
config.device.node_info_broadcast_secs = default_node_info_broadcast_secs; config.device.node_info_broadcast_secs = default_node_info_broadcast_secs;
config.security.serial_enabled = true; config.device.serial_enabled = true;
config.security.admin_channel_enabled = false;
resetRadioConfig(); resetRadioConfig();
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32); strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
// FIXME: Default to bluetooth capability of platform as default // FIXME: Default to bluetooth capability of platform as default
@@ -349,17 +288,13 @@ void NodeDB::installDefaultConfig()
meshtastic_Config_PositionConfig_PositionFlags_SPEED | meshtastic_Config_PositionConfig_PositionFlags_HEADING | meshtastic_Config_PositionConfig_PositionFlags_SPEED | meshtastic_Config_PositionConfig_PositionFlags_HEADING |
meshtastic_Config_PositionConfig_PositionFlags_DOP | meshtastic_Config_PositionConfig_PositionFlags_SATINVIEW); meshtastic_Config_PositionConfig_PositionFlags_DOP | meshtastic_Config_PositionConfig_PositionFlags_SATINVIEW);
#ifdef DISPLAY_FLIP_SCREEN #ifdef RADIOMASTER_900_BANDIT_NANO
config.display.flip_screen = true; config.display.flip_screen = true;
#endif #endif
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
config.display.screen_on_secs = 30; config.display.screen_on_secs = 30;
config.display.wake_on_tap_or_motion = true; config.display.wake_on_tap_or_motion = true;
#endif #endif
#ifdef HELTEC_VISION_MASTER_E290
// Orient so that LoRa antenna faces up
config.display.flip_screen = true;
#endif
initConfigIntervals(); initConfigIntervals();
} }
@@ -417,13 +352,6 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.output_ms = 100; moduleConfig.external_notification.output_ms = 100;
moduleConfig.external_notification.active = true; moduleConfig.external_notification.active = true;
#endif #endif
#ifdef BUTTON_SECONDARY_CANNEDMESSAGES
// Use a board's second built-in button as input source for canned messages
moduleConfig.canned_message.enabled = true;
moduleConfig.canned_message.inputbroker_pin_press = BUTTON_PIN_SECONDARY;
strcpy(moduleConfig.canned_message.allow_input_source, "scanAndSelect");
#endif
moduleConfig.has_canned_message = true; moduleConfig.has_canned_message = true;
strncpy(moduleConfig.mqtt.address, default_mqtt_address, sizeof(moduleConfig.mqtt.address)); strncpy(moduleConfig.mqtt.address, default_mqtt_address, sizeof(moduleConfig.mqtt.address));
@@ -553,16 +481,10 @@ void NodeDB::cleanupMeshDB()
{ {
int newPos = 0, removed = 0; int newPos = 0, removed = 0;
for (int i = 0; i < numMeshNodes; i++) { for (int i = 0; i < numMeshNodes; i++) {
if (meshNodes->at(i).has_user) { if (meshNodes->at(i).has_user)
if (meshNodes->at(i).user.public_key.size > 0) {
if (memfll(meshNodes->at(i).user.public_key.bytes, 0, meshNodes->at(i).user.public_key.size)) {
meshNodes->at(i).user.public_key.size = 0;
}
}
meshNodes->at(newPos++) = meshNodes->at(i); meshNodes->at(newPos++) = meshNodes->at(i);
} else { else
removed++; removed++;
}
} }
numMeshNodes -= removed; numMeshNodes -= removed;
std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + removed, std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + removed,
@@ -589,16 +511,8 @@ void NodeDB::installDefaultDeviceState()
// Set default owner name // Set default owner name
pickNewNodeNum(); // based on macaddr now pickNewNodeNum(); // based on macaddr now
#ifdef CONFIG_OWNER_LONG_NAME_USERPREFS
snprintf(owner.long_name, sizeof(owner.long_name), CONFIG_OWNER_LONG_NAME_USERPREFS);
#else
snprintf(owner.long_name, sizeof(owner.long_name), "Meshtastic %02x%02x", ourMacAddr[4], ourMacAddr[5]); snprintf(owner.long_name, sizeof(owner.long_name), "Meshtastic %02x%02x", ourMacAddr[4], ourMacAddr[5]);
#endif
#ifdef CONFIG_OWNER_SHORT_NAME_USERPREFS
snprintf(owner.short_name, sizeof(owner.short_name), CONFIG_OWNER_SHORT_NAME_USERPREFS);
#else
snprintf(owner.short_name, sizeof(owner.short_name), "%02x%02x", ourMacAddr[4], ourMacAddr[5]); snprintf(owner.short_name, sizeof(owner.short_name), "%02x%02x", ourMacAddr[4], ourMacAddr[5]);
#endif
snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum()); // Default node ID now based on nodenum snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum()); // Default node ID now based on nodenum
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
} }
@@ -643,6 +557,11 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t
LoadFileResult state = LoadFileResult::OTHER_FAILURE; LoadFileResult state = LoadFileResult::OTHER_FAILURE;
#ifdef FSCom #ifdef FSCom
if (!FSCom.exists(filename)) {
LOG_INFO("File %s not found\n", filename);
return LoadFileResult::NOT_FOUND;
}
auto f = FSCom.open(filename, FILE_O_READ); auto f = FSCom.open(filename, FILE_O_READ);
if (f) { if (f) {
@@ -655,7 +574,7 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t
state = LoadFileResult::DECODE_FAILED; state = LoadFileResult::DECODE_FAILED;
} else { } else {
LOG_INFO("Loaded %s successfully\n", filename); LOG_INFO("Loaded %s successfully\n", filename);
state = LoadFileResult::LOAD_SUCCESS; state = LoadFileResult::SUCCESS;
} }
f.close(); f.close();
} else { } else {
@@ -663,43 +582,35 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t
} }
#else #else
LOG_ERROR("ERROR: Filesystem not implemented\n"); LOG_ERROR("ERROR: Filesystem not implemented\n");
state = LoadFileResult::NO_FILESYSTEM; state = LoadFileState::NO_FILESYSTEM;
#endif #endif
return state; return state;
} }
void NodeDB::loadFromDisk() void NodeDB::loadFromDisk()
{ {
devicestate.version =
0; // Mark the current device state as completely unusable, so that if we fail reading the entire file from
// disk we will still factoryReset to restore things.
// static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM
auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES * sizeof(meshtastic_NodeInfo), auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES * sizeof(meshtastic_NodeInfo),
sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate); sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate);
// See https://github.com/meshtastic/firmware/issues/4184#issuecomment-2269390786 if (state != LoadFileResult::SUCCESS) {
// It is very important to try and use the saved prefs even if we fail to read meshtastic_DeviceState. Because most of our installDefaultDeviceState(); // Our in RAM copy might now be corrupt
// critical config may still be valid (in the other files - loaded next).
// Also, if we did fail on reading we probably failed on the enormous (and non critical) nodeDB. So DO NOT install default
// device state.
// if (state != LoadFileResult::LOAD_SUCCESS) {
// installDefaultDeviceState(); // Our in RAM copy might now be corrupt
//} else {
if (devicestate.version < DEVICESTATE_MIN_VER) {
LOG_WARN("Devicestate %d is old, discarding\n", devicestate.version);
factoryReset();
} else { } else {
LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d\n", devicestate.version, if (devicestate.version < DEVICESTATE_MIN_VER) {
devicestate.node_db_lite.size()); LOG_WARN("Devicestate %d is old, discarding\n", devicestate.version);
meshNodes = &devicestate.node_db_lite; factoryReset();
numMeshNodes = devicestate.node_db_lite.size(); } else {
LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d\n", devicestate.version,
devicestate.node_db_lite.size());
meshNodes = &devicestate.node_db_lite;
numMeshNodes = devicestate.node_db_lite.size();
}
} }
meshNodes->resize(MAX_NUM_NODES); meshNodes->resize(MAX_NUM_NODES);
state = loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg, state = loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg,
&config); &config);
if (state != LoadFileResult::LOAD_SUCCESS) { if (state != LoadFileResult::SUCCESS) {
installDefaultConfig(); // Our in RAM copy might now be corrupt installDefaultConfig(); // Our in RAM copy might now be corrupt
} else { } else {
if (config.version < DEVICESTATE_MIN_VER) { if (config.version < DEVICESTATE_MIN_VER) {
@@ -712,7 +623,7 @@ void NodeDB::loadFromDisk()
state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig), state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig),
&meshtastic_LocalModuleConfig_msg, &moduleConfig); &meshtastic_LocalModuleConfig_msg, &moduleConfig);
if (state != LoadFileResult::LOAD_SUCCESS) { if (state != LoadFileResult::SUCCESS) {
installDefaultModuleConfig(); // Our in RAM copy might now be corrupt installDefaultModuleConfig(); // Our in RAM copy might now be corrupt
} else { } else {
if (moduleConfig.version < DEVICESTATE_MIN_VER) { if (moduleConfig.version < DEVICESTATE_MIN_VER) {
@@ -725,7 +636,7 @@ void NodeDB::loadFromDisk()
state = loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg, state = loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg,
&channelFile); &channelFile);
if (state != LoadFileResult::LOAD_SUCCESS) { if (state != LoadFileResult::SUCCESS) {
installDefaultChannels(); // Our in RAM copy might now be corrupt installDefaultChannels(); // Our in RAM copy might now be corrupt
} else { } else {
if (channelFile.version < DEVICESTATE_MIN_VER) { if (channelFile.version < DEVICESTATE_MIN_VER) {
@@ -737,9 +648,8 @@ void NodeDB::loadFromDisk()
} }
state = loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore); state = loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore);
if (state == LoadFileResult::LOAD_SUCCESS) { if (state == LoadFileResult::SUCCESS) {
LOG_INFO("Loaded OEMStore\n"); LOG_INFO("Loaded OEMStore\n");
hasOemStore = true;
} }
// 2.4.X - configuration migration to update new default intervals // 2.4.X - configuration migration to update new default intervals
@@ -764,26 +674,48 @@ void NodeDB::loadFromDisk()
} }
/** Save a protobuf from a file, return true for success */ /** Save a protobuf from a file, return true for success */
bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct)
bool fullAtomic)
{ {
bool okay = false; bool okay = false;
#ifdef FSCom #ifdef FSCom
auto f = SafeFile(filename, fullAtomic); // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM
String filenameTmp = filename;
filenameTmp += ".tmp";
auto f = FSCom.open(filenameTmp.c_str(), FILE_O_WRITE);
if (f) {
LOG_INFO("Saving %s\n", filename);
pb_ostream_t stream = {&writecb, &f, protoSize};
LOG_INFO("Saving %s\n", filename); if (!pb_encode(&stream, fields, dest_struct)) {
pb_ostream_t stream = {&writecb, static_cast<Print *>(&f), protoSize}; LOG_ERROR("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream));
} else {
okay = true;
}
f.flush();
f.close();
if (!pb_encode(&stream, fields, dest_struct)) { // brief window of risk here ;-)
LOG_ERROR("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream)); if (FSCom.exists(filename) && !FSCom.remove(filename)) {
LOG_WARN("Can't remove old pref file\n");
}
if (!renameFile(filenameTmp.c_str(), filename)) {
LOG_ERROR("Error: can't rename new pref file\n");
}
} else { } else {
okay = true; LOG_ERROR("Can't write prefs\n");
} #ifdef ARCH_NRF52
static uint8_t failedCounter = 0;
bool writeSucceeded = f.close(); failedCounter++;
if (failedCounter >= 2) {
if (!okay || !writeSucceeded) { LOG_ERROR("Failed to save file twice. Rebooting...\n");
LOG_ERROR("Can't write prefs!\n"); delay(100);
NVIC_SystemReset();
// We used to blow away the filesystem here, but that's a bit extreme
// FSCom.format();
// // After formatting, the device needs to be restarted
// nodeDB->resetRadioConfig(true);
}
#endif
} }
#else #else
LOG_ERROR("ERROR: Filesystem not implemented\n"); LOG_ERROR("ERROR: Filesystem not implemented\n");
@@ -791,32 +723,32 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_
return okay; return okay;
} }
bool NodeDB::saveChannelsToDisk() void NodeDB::saveChannelsToDisk()
{ {
#ifdef FSCom #ifdef FSCom
FSCom.mkdir("/prefs"); FSCom.mkdir("/prefs");
#endif #endif
return saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile); saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile);
} }
bool NodeDB::saveDeviceStateToDisk() void NodeDB::saveDeviceStateToDisk()
{ {
#ifdef FSCom #ifdef FSCom
FSCom.mkdir("/prefs"); FSCom.mkdir("/prefs");
#endif #endif
// Note: if MAX_NUM_NODES=100 and meshtastic_NodeInfoLite_size=166, so will be approximately 17KB saveProto(prefFileName, sizeof(devicestate) + numMeshNodes * meshtastic_NodeInfoLite_size, &meshtastic_DeviceState_msg,
// Because so huge we _must_ not use fullAtomic, because the filesystem is probably too small to hold two copies of this &devicestate);
return saveProto(prefFileName, sizeof(devicestate) + numMeshNodes * meshtastic_NodeInfoLite_size, &meshtastic_DeviceState_msg,
&devicestate, false);
} }
bool NodeDB::saveToDiskNoRetry(int saveWhat) void NodeDB::saveToDisk(int saveWhat)
{ {
bool success = true;
#ifdef FSCom #ifdef FSCom
FSCom.mkdir("/prefs"); FSCom.mkdir("/prefs");
#endif #endif
if (saveWhat & SEGMENT_DEVICESTATE) {
saveDeviceStateToDisk();
}
if (saveWhat & SEGMENT_CONFIG) { if (saveWhat & SEGMENT_CONFIG) {
config.has_device = true; config.has_device = true;
config.has_display = true; config.has_display = true;
@@ -825,9 +757,8 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
config.has_power = true; config.has_power = true;
config.has_network = true; config.has_network = true;
config.has_bluetooth = true; config.has_bluetooth = true;
config.has_security = true;
success &= saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config); saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config);
} }
if (saveWhat & SEGMENT_MODULECONFIG) { if (saveWhat & SEGMENT_MODULECONFIG) {
@@ -844,45 +775,12 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
moduleConfig.has_audio = true; moduleConfig.has_audio = true;
moduleConfig.has_paxcounter = true; moduleConfig.has_paxcounter = true;
success &= saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
}
// We might need to rewrite the OEM data if we are reformatting the FS
if ((saveWhat & SEGMENT_OEM) && hasOemStore) {
success &= saveProto(oemConfigFile, meshtastic_OEMStore_size, &meshtastic_OEMStore_msg, &oemStore);
} }
if (saveWhat & SEGMENT_CHANNELS) { if (saveWhat & SEGMENT_CHANNELS) {
success &= saveChannelsToDisk(); saveChannelsToDisk();
} }
if (saveWhat & SEGMENT_DEVICESTATE) {
success &= saveDeviceStateToDisk();
}
return success;
}
bool NodeDB::saveToDisk(int saveWhat)
{
bool success = saveToDiskNoRetry(saveWhat);
if (!success) {
LOG_ERROR("Failed to save to disk, retrying...\n");
#ifdef ARCH_NRF52 // @geeksville is not ready yet to say we should do this on other platforms. See bug #4184 discussion
FSCom.format();
// We need to rewrite the OEM data if we are reformatting the FS
saveWhat |= SEGMENT_OEM;
#endif
success = saveToDiskNoRetry(saveWhat);
RECORD_CRITICALERROR(success ? meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE
: meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE);
}
return success;
} }
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex) const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
@@ -1005,39 +903,23 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
/** Update user info and channel for this node based on received user data /** Update user info and channel for this node based on received user data
*/ */
bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex) bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t channelIndex)
{ {
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
if (!info) { if (!info) {
return false; return false;
} }
LOG_DEBUG("old user %s/%s, channel=%d\n", info->user.long_name, info->user.short_name, info->channel); LOG_DEBUG("old user %s/%s/%s, channel=%d\n", info->user.id, info->user.long_name, info->user.short_name, info->channel);
#if !(MESHTASTIC_EXCLUDE_PKI)
if (p.public_key.size > 0) {
printBytes("Incoming Pubkey: ", p.public_key.bytes, 32);
if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one
LOG_INFO("Public Key set for node, not updateing!\n");
// we copy the key into the incoming packet, to prevent overwrite
memcpy(p.public_key.bytes, info->user.public_key.bytes, 32);
} else {
LOG_INFO("Updating Node Pubkey!\n");
}
}
#endif
// Both of info->user and p start as filled with zero so I think this is okay // Both of info->user and p start as filled with zero so I think this is okay
auto lite = TypeConversions::ConvertToUserLite(p); bool changed = memcmp(&info->user, &p, sizeof(info->user)) || (info->channel != channelIndex);
bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex);
info->user = lite; info->user = p;
if (info->user.public_key.size == 32) {
printBytes("Saved Pubkey: ", info->user.public_key.bytes, 32);
}
if (nodeId != getNodeNum()) if (nodeId != getNodeNum())
info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel) info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel)
LOG_DEBUG("updating changed=%d user %s/%s, channel=%d\n", changed, info->user.long_name, info->user.short_name, LOG_DEBUG("updating changed=%d user %s/%s/%s, channel=%d\n", changed, info->user.id, info->user.long_name,
info->channel); info->user.short_name, info->channel);
info->has_user = true; info->has_user = true;
if (changed) { if (changed) {
@@ -1106,32 +988,18 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
meshtastic_NodeInfoLite *lite = getMeshNode(n); meshtastic_NodeInfoLite *lite = getMeshNode(n);
if (!lite) { if (!lite) {
if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < MINIMUM_SAFE_FREE_HEAP)) { if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) {
if (screen) if (screen)
screen->print("Warn: node database full!\nErasing oldest entry\n"); screen->print("Warn: node database full!\nErasing oldest entry\n");
LOG_WARN("Node database full with %i nodes and %i bytes free! Erasing oldest entry\n", numMeshNodes, LOG_WARN("Node database full! Erasing oldest entry\n");
memGet.getFreeHeap());
// look for oldest node and erase it // look for oldest node and erase it
uint32_t oldest = UINT32_MAX; uint32_t oldest = UINT32_MAX;
uint32_t oldestBoring = UINT32_MAX;
int oldestIndex = -1; int oldestIndex = -1;
int oldestBoringIndex = -1;
for (int i = 1; i < numMeshNodes; i++) { for (int i = 1; i < numMeshNodes; i++) {
// Simply the oldest non-favorite node
if (!meshNodes->at(i).is_favorite && meshNodes->at(i).last_heard < oldest) { if (!meshNodes->at(i).is_favorite && meshNodes->at(i).last_heard < oldest) {
oldest = meshNodes->at(i).last_heard; oldest = meshNodes->at(i).last_heard;
oldestIndex = i; oldestIndex = i;
} }
// The oldest "boring" node
if (!meshNodes->at(i).is_favorite && meshNodes->at(i).user.public_key.size == 0 &&
meshNodes->at(i).last_heard < oldestBoring) {
oldestBoring = meshNodes->at(i).last_heard;
oldestBoringIndex = i;
}
}
// if we found a "boring" node, evict it
if (oldestBoringIndex != -1) {
oldestIndex = oldestBoringIndex;
} }
// Shove the remaining nodes down the chain // Shove the remaining nodes down the chain
for (int i = oldestIndex; i < numMeshNodes - 1; i++) { for (int i = oldestIndex; i < numMeshNodes - 1; i++) {
@@ -1145,7 +1013,6 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
// everything is missing except the nodenum // everything is missing except the nodenum
memset(lite, 0, sizeof(*lite)); memset(lite, 0, sizeof(*lite));
lite->num = n; lite->num = n;
LOG_INFO("Adding node to database with %i nodes and %i bytes free!\n", numMeshNodes, memGet.getFreeHeap());
} }
return lite; return lite;

View File

@@ -19,7 +19,6 @@ DeviceState versions used to be defined in the .proto file but really only this
#define SEGMENT_MODULECONFIG 2 #define SEGMENT_MODULECONFIG 2
#define SEGMENT_DEVICESTATE 4 #define SEGMENT_DEVICESTATE 4
#define SEGMENT_CHANNELS 8 #define SEGMENT_CHANNELS 8
#define SEGMENT_OEM 16
#define DEVICESTATE_CUR_VER 23 #define DEVICESTATE_CUR_VER 23
#define DEVICESTATE_MIN_VER 22 #define DEVICESTATE_MIN_VER 22
@@ -41,7 +40,7 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p);
enum LoadFileResult { enum LoadFileResult {
// Successfully opened the file // Successfully opened the file
LOAD_SUCCESS = 1, SUCCESS = 1,
// File does not exist // File does not exist
NOT_FOUND = 2, NOT_FOUND = 2,
// Device does not have a filesystem // Device does not have a filesystem
@@ -73,8 +72,8 @@ class NodeDB
NodeDB(); NodeDB();
/// write to flash /// write to flash
/// @return true if the save was successful void saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS),
bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS); saveChannelsToDisk(), saveDeviceStateToDisk();
/** Reinit radio config if needed, because either: /** Reinit radio config if needed, because either:
* a) sometimes a buggy android app might send us bogus settings or * a) sometimes a buggy android app might send us bogus settings or
@@ -98,7 +97,7 @@ class NodeDB
/** Update user info and channel for this node based on received user data /** Update user info and channel for this node based on received user data
*/ */
bool updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex = 0); bool updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t channelIndex = 0);
/// @return our node number /// @return our node number
NodeNum getNodeNum() { return myNodeInfo.my_node_num; } NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
@@ -127,12 +126,11 @@ class NodeDB
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum); void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum);
bool factoryReset(bool eraseBleBonds = false); bool factoryReset();
LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
void *dest_struct); void *dest_struct);
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct);
bool fullAtomic = true);
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role); void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role);
@@ -183,13 +181,6 @@ class NodeDB
/// Reinit device state from scratch (not loading from disk) /// Reinit device state from scratch (not loading from disk)
void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(), installDefaultModuleConfig(); void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(), installDefaultModuleConfig();
/// write to flash
/// @return true if the save was successful
bool saveToDiskNoRetry(int saveWhat);
bool saveChannelsToDisk();
bool saveDeviceStateToDisk();
}; };
extern NodeDB *nodeDB; extern NodeDB *nodeDB;
@@ -213,6 +204,9 @@ extern NodeDB *nodeDB;
prefs.is_power_saving = True prefs.is_power_saving = True
*/ */
// Our delay functions check for this for times that should never expire
#define NODE_DELAY_FOREVER 0xffffffff
/// Sometimes we will have Position objects that only have a time, so check for /// Sometimes we will have Position objects that only have a time, so check for
/// valid lat/lon /// valid lat/lon
static inline bool hasValidPosition(const meshtastic_NodeInfoLite *n) static inline bool hasValidPosition(const meshtastic_NodeInfoLite *n)

View File

@@ -41,10 +41,8 @@ void PhoneAPI::handleStartConfig()
// Must be before setting state (because state is how we know !connected) // Must be before setting state (because state is how we know !connected)
if (!isConnected()) { if (!isConnected()) {
onConnectionChanged(true); onConnectionChanged(true);
observe(&service->fromNumChanged); observe(&service.fromNumChanged);
#ifdef FSCom
observe(&xModem.packetReady); observe(&xModem.packetReady);
#endif
} }
// even if we were already connected - restart our state machine // even if we were already connected - restart our state machine
@@ -63,10 +61,8 @@ void PhoneAPI::close()
if (state != STATE_SEND_NOTHING) { if (state != STATE_SEND_NOTHING) {
state = STATE_SEND_NOTHING; state = STATE_SEND_NOTHING;
unobserve(&service->fromNumChanged); unobserve(&service.fromNumChanged);
#ifdef FSCom
unobserve(&xModem.packetReady); unobserve(&xModem.packetReady);
#endif
releasePhonePacket(); // Don't leak phone packets on shutdown releasePhonePacket(); // Don't leak phone packets on shutdown
releaseQueueStatusPhonePacket(); releaseQueueStatusPhonePacket();
releaseMqttClientProxyPhonePacket(); releaseMqttClientProxyPhonePacket();
@@ -114,9 +110,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
break; break;
case meshtastic_ToRadio_xmodemPacket_tag: case meshtastic_ToRadio_xmodemPacket_tag:
LOG_INFO("Got xmodem packet\n"); LOG_INFO("Got xmodem packet\n");
#ifdef FSCom
xModem.handlePacket(toRadioScratch.xmodemPacket); xModem.handlePacket(toRadioScratch.xmodemPacket);
#endif
break; break;
#if !MESHTASTIC_EXCLUDE_MQTT #if !MESHTASTIC_EXCLUDE_MQTT
case meshtastic_ToRadio_mqttClientProxyMessage_tag: case meshtastic_ToRadio_mqttClientProxyMessage_tag:
@@ -186,7 +180,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.my_info = myNodeInfo; fromRadioScratch.my_info = myNodeInfo;
state = STATE_SEND_OWN_NODEINFO; state = STATE_SEND_OWN_NODEINFO;
service->refreshLocalMeshNode(); // Update my NodeInfo because the client will be asking for it soon. service.refreshLocalMeshNode(); // Update my NodeInfo because the client will be asking for it soon.
break; break;
case STATE_SEND_OWN_NODEINFO: { case STATE_SEND_OWN_NODEINFO: {
@@ -255,10 +249,6 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.config.which_payload_variant = meshtastic_Config_bluetooth_tag; fromRadioScratch.config.which_payload_variant = meshtastic_Config_bluetooth_tag;
fromRadioScratch.config.payload_variant.bluetooth = config.bluetooth; fromRadioScratch.config.payload_variant.bluetooth = config.bluetooth;
break; break;
case meshtastic_Config_security_tag:
fromRadioScratch.config.which_payload_variant = meshtastic_Config_security_tag;
fromRadioScratch.config.payload_variant.security = config.security;
break;
default: default:
LOG_ERROR("Unknown config type %d\n", config_state); LOG_ERROR("Unknown config type %d\n", config_state);
} }
@@ -447,7 +437,7 @@ void PhoneAPI::handleDisconnect()
void PhoneAPI::releasePhonePacket() void PhoneAPI::releasePhonePacket()
{ {
if (packetForPhone) { if (packetForPhone) {
service->releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore
packetForPhone = NULL; packetForPhone = NULL;
} }
} }
@@ -455,7 +445,7 @@ void PhoneAPI::releasePhonePacket()
void PhoneAPI::releaseQueueStatusPhonePacket() void PhoneAPI::releaseQueueStatusPhonePacket()
{ {
if (queueStatusPacketForPhone) { if (queueStatusPacketForPhone) {
service->releaseQueueStatusToPool(queueStatusPacketForPhone); service.releaseQueueStatusToPool(queueStatusPacketForPhone);
queueStatusPacketForPhone = NULL; queueStatusPacketForPhone = NULL;
} }
} }
@@ -463,7 +453,7 @@ void PhoneAPI::releaseQueueStatusPhonePacket()
void PhoneAPI::releaseMqttClientProxyPhonePacket() void PhoneAPI::releaseMqttClientProxyPhonePacket()
{ {
if (mqttClientProxyMessageForPhone) { if (mqttClientProxyMessageForPhone) {
service->releaseMqttClientProxyMessageToPool(mqttClientProxyMessageForPhone); service.releaseMqttClientProxyMessageToPool(mqttClientProxyMessageForPhone);
mqttClientProxyMessageForPhone = NULL; mqttClientProxyMessageForPhone = NULL;
} }
} }
@@ -499,21 +489,19 @@ bool PhoneAPI::available()
return true; // Always say we have something, because we might need to advance our state machine return true; // Always say we have something, because we might need to advance our state machine
case STATE_SEND_PACKETS: { case STATE_SEND_PACKETS: {
if (!queueStatusPacketForPhone) if (!queueStatusPacketForPhone)
queueStatusPacketForPhone = service->getQueueStatusForPhone(); queueStatusPacketForPhone = service.getQueueStatusForPhone();
if (!mqttClientProxyMessageForPhone) if (!mqttClientProxyMessageForPhone)
mqttClientProxyMessageForPhone = service->getMqttClientProxyMessageForPhone(); mqttClientProxyMessageForPhone = service.getMqttClientProxyMessageForPhone();
bool hasPacket = !!queueStatusPacketForPhone || !!mqttClientProxyMessageForPhone; bool hasPacket = !!queueStatusPacketForPhone || !!mqttClientProxyMessageForPhone;
if (hasPacket) if (hasPacket)
return true; return true;
#ifdef FSCom
if (xmodemPacketForPhone.control == meshtastic_XModem_Control_NUL) if (xmodemPacketForPhone.control == meshtastic_XModem_Control_NUL)
xmodemPacketForPhone = xModem.getForPhone(); xmodemPacketForPhone = xModem.getForPhone();
if (xmodemPacketForPhone.control != meshtastic_XModem_Control_NUL) { if (xmodemPacketForPhone.control != meshtastic_XModem_Control_NUL) {
xModem.resetForPhone(); xModem.resetForPhone();
return true; return true;
} }
#endif
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
#if !MESHTASTIC_EXCLUDE_STOREFORWARD #if !MESHTASTIC_EXCLUDE_STOREFORWARD
@@ -524,7 +512,7 @@ bool PhoneAPI::available()
#endif #endif
if (!packetForPhone) if (!packetForPhone)
packetForPhone = service->getForPhone(); packetForPhone = service.getForPhone();
hasPacket = !!packetForPhone; hasPacket = !!packetForPhone;
// LOG_DEBUG("available hasPacket=%d\n", hasPacket); // LOG_DEBUG("available hasPacket=%d\n", hasPacket);
return hasPacket; return hasPacket;
@@ -542,7 +530,7 @@ bool PhoneAPI::available()
bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
{ {
printPacket("PACKET FROM PHONE", &p); printPacket("PACKET FROM PHONE", &p);
service->handleToRadio(p); service.handleToRadio(p);
return true; return true;
} }

View File

@@ -66,9 +66,6 @@ class PhoneAPI
// Keep MqttClientProxyMessage packet just as packetForPhone // Keep MqttClientProxyMessage packet just as packetForPhone
meshtastic_MqttClientProxyMessage *mqttClientProxyMessageForPhone = NULL; meshtastic_MqttClientProxyMessage *mqttClientProxyMessageForPhone = NULL;
// Keep ClientNotification packet just as packetForPhone
meshtastic_ClientNotification *clientNotification = NULL;
/// We temporarily keep the nodeInfo here between the call to available and getFromRadio /// We temporarily keep the nodeInfo here between the call to available and getFromRadio
meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default; meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default;

View File

@@ -38,7 +38,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
/** /**
* Return a mesh packet which has been preinited with a particular protobuf data payload and port number. * Return a mesh packet which has been preinited with a particular protobuf data payload and port number.
* You can then send this packet (after customizing any of the payload fields you might need) with * You can then send this packet (after customizing any of the payload fields you might need) with
* service->sendToMesh() * service.sendToMesh()
*/ */
meshtastic_MeshPacket *allocDataProtobuf(const T &payload) meshtastic_MeshPacket *allocDataProtobuf(const T &payload)
{ {

View File

@@ -16,7 +16,8 @@
// In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING // In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING
// if you set power to something higher than 17 or 20 you might fry your board. // if you set power to something higher than 17 or 20 you might fry your board.
#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) #define POWER_DEFAULT 17 // How much power to use if the user hasn't set a power level
#ifdef RADIOMASTER_900_BANDIT_NANO
// Structure to hold DAC and DB values // Structure to hold DAC and DB values
typedef struct { typedef struct {
uint8_t dac; uint8_t dac;
@@ -40,23 +41,12 @@ DACDB getDACandDB(uint8_t dbm)
static const struct { static const struct {
uint8_t dbm; uint8_t dbm;
DACDB values; DACDB values;
} } dbmToDACDB[] = {
#ifdef RADIOMASTER_900_BANDIT_NANO
dbmToDACDB[] = {
{20, {168, 2}}, // 100mW {20, {168, 2}}, // 100mW
{24, {148, 6}}, // 250mW {24, {148, 6}}, // 250mW
{27, {128, 9}}, // 500mW {27, {128, 9}}, // 500mW
{30, {90, 12}} // 1000mW {30, {90, 12}} // 1000mW
}; };
#endif
#ifdef RADIOMASTER_900_BANDIT
dbmToDACDB[] = {
{20, {165, 2}}, // 100mW
{24, {155, 6}}, // 250mW
{27, {142, 9}}, // 500mW
{30, {110, 10}} // 1000mW
};
#endif
const int numValues = sizeof(dbmToDACDB) / sizeof(dbmToDACDB[0]); const int numValues = sizeof(dbmToDACDB) / sizeof(dbmToDACDB[0]);
// Find the interval dbm falls within and interpolate // Find the interval dbm falls within and interpolate
@@ -67,12 +57,7 @@ DACDB getDACandDB(uint8_t dbm)
} }
// Return a default value if no match is found and default to 100mW // Return a default value if no match is found and default to 100mW
#ifdef RADIOMASTER_900_BANDIT_NANO
DACDB defaultValue = {168, 2}; DACDB defaultValue = {168, 2};
#endif
#ifdef RADIOMASTER_900_BANDIT
DACDB defaultValue = {165, 2};
#endif
return defaultValue; return defaultValue;
} }
#endif #endif
@@ -111,7 +96,7 @@ bool RF95Interface::init()
{ {
RadioLibInterface::init(); RadioLibInterface::init();
#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) #ifdef RADIOMASTER_900_BANDIT_NANO
// DAC and DB values based on dBm using interpolation // DAC and DB values based on dBm using interpolation
DACDB dacDbValues = getDACandDB(power); DACDB dacDbValues = getDACandDB(power);
int8_t powerDAC = dacDbValues.dac; int8_t powerDAC = dacDbValues.dac;
@@ -133,7 +118,7 @@ bool RF95Interface::init()
// enable PA // enable PA
#ifdef RF95_PA_EN #ifdef RF95_PA_EN
#if defined(RF95_PA_DAC_EN) #if defined(RF95_PA_DAC_EN)
#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) #ifdef RADIOMASTER_900_BANDIT_NANO
// Use calculated DAC value // Use calculated DAC value
dacWrite(RF95_PA_EN, powerDAC); dacWrite(RF95_PA_EN, powerDAC);
#else #else
@@ -179,7 +164,7 @@ bool RF95Interface::init()
LOG_INFO("Frequency set to %f\n", getFreq()); LOG_INFO("Frequency set to %f\n", getFreq());
LOG_INFO("Bandwidth set to %f\n", bw); LOG_INFO("Bandwidth set to %f\n", bw);
LOG_INFO("Power output set to %d\n", power); LOG_INFO("Power output set to %d\n", power);
#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) #ifdef RADIOMASTER_900_BANDIT_NANO
LOG_INFO("DAC output set to %d\n", powerDAC); LOG_INFO("DAC output set to %d\n", powerDAC);
#endif #endif

View File

@@ -53,10 +53,8 @@ const RegionInfo regions[] = {
/* /*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
https://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_5-E1.pdf
https://qiita.com/ammo0613/items/d952154f1195b64dc29f
*/ */
RDEF(JP, 920.5f, 923.5f, 100, 0, 13, true, false, false), RDEF(JP, 920.8f, 927.8f, 100, 0, 16, true, false, false),
/* /*
https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf
@@ -179,6 +177,9 @@ The band is from 902 to 928 MHz. It mentions channel number and its respective c
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency. separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
*/ */
// 1kb was too small
#define RADIO_STACK_SIZE 4096
/** /**
* Calculate airtime per * Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf * https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
@@ -285,9 +286,6 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
if (s.want_response) if (s.want_response)
out += DEBUG_PORT.mt_sprintf(" WANTRESP"); out += DEBUG_PORT.mt_sprintf(" WANTRESP");
if (p->pki_encrypted)
out += DEBUG_PORT.mt_sprintf(" PKI");
if (s.source != 0) if (s.source != 0)
out += DEBUG_PORT.mt_sprintf(" source=%08x", s.source); out += DEBUG_PORT.mt_sprintf(" source=%08x", s.source);
@@ -339,7 +337,7 @@ bool RadioInterface::init()
{ {
LOG_INFO("Starting meshradio init...\n"); LOG_INFO("Starting meshradio init...\n");
configChangedObserver.observe(&service->configChanged); configChangedObserver.observe(&service.configChanged);
preflightSleepObserver.observe(&preflightSleep); preflightSleepObserver.observe(&preflightSleep);
notifyDeepSleepObserver.observe(&notifyDeepSleep); notifyDeepSleepObserver.observe(&notifyDeepSleep);
@@ -414,93 +412,67 @@ void RadioInterface::applyModemConfig()
// Set up default configuration // Set up default configuration
// No Sync Words in LORA mode // No Sync Words in LORA mode
meshtastic_Config_LoRaConfig &loraConfig = config.lora; meshtastic_Config_LoRaConfig &loraConfig = config.lora;
bool validConfig = false; // We need to check for a valid configuration if (loraConfig.use_preset) {
while (!validConfig) {
if (loraConfig.use_preset) {
switch (loraConfig.modem_preset) { switch (loraConfig.modem_preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
bw = (myRegion->wideLora) ? 812.5 : 500; bw = (myRegion->wideLora) ? 812.5 : 250;
cr = 5; cr = 5;
sf = 7; sf = 7;
break; break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
bw = (myRegion->wideLora) ? 812.5 : 250; bw = (myRegion->wideLora) ? 812.5 : 250;
cr = 5; cr = 5;
sf = 7; sf = 8;
break; break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
bw = (myRegion->wideLora) ? 812.5 : 250; bw = (myRegion->wideLora) ? 812.5 : 250;
cr = 5; cr = 5;
sf = 8; sf = 9;
break; break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
bw = (myRegion->wideLora) ? 812.5 : 250; bw = (myRegion->wideLora) ? 812.5 : 250;
cr = 5; cr = 5;
sf = 9; sf = 10;
break; break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal.
bw = (myRegion->wideLora) ? 812.5 : 250; bw = (myRegion->wideLora) ? 812.5 : 250;
cr = 5; cr = 5;
sf = 10; sf = 11;
break; break;
default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal. case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
bw = (myRegion->wideLora) ? 812.5 : 250; bw = (myRegion->wideLora) ? 406.25 : 125;
cr = 5; cr = 8;
sf = 11; sf = 11;
break; break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
bw = (myRegion->wideLora) ? 406.25 : 125; bw = (myRegion->wideLora) ? 406.25 : 125;
cr = 8; cr = 8;
sf = 11; sf = 12;
break; break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: case meshtastic_Config_LoRaConfig_ModemPreset_VERY_LONG_SLOW:
bw = (myRegion->wideLora) ? 406.25 : 125; bw = (myRegion->wideLora) ? 203.125 : 62.5;
cr = 8; cr = 8;
sf = 12; sf = 12;
break; break;
case meshtastic_Config_LoRaConfig_ModemPreset_VERY_LONG_SLOW:
bw = (myRegion->wideLora) ? 203.125 : 62.5;
cr = 8;
sf = 12;
break;
}
} else {
sf = loraConfig.spread_factor;
cr = loraConfig.coding_rate;
bw = loraConfig.bandwidth;
if (bw == 31) // This parameter is not an integer
bw = 31.25;
if (bw == 62) // Fix for 62.5Khz bandwidth
bw = 62.5;
if (bw == 200)
bw = 203.125;
if (bw == 400)
bw = 406.25;
if (bw == 800)
bw = 812.5;
if (bw == 1600)
bw = 1625.0;
} }
} else {
sf = loraConfig.spread_factor;
cr = loraConfig.coding_rate;
bw = loraConfig.bandwidth;
if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) { if (bw == 31) // This parameter is not an integer
static const char *err_string = bw = 31.25;
"Regional frequency range is smaller than bandwidth. Falling back to default preset.\n"; if (bw == 62) // Fix for 62.5Khz bandwidth
LOG_ERROR(err_string); bw = 62.5;
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); if (bw == 200)
bw = 203.125;
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); if (bw == 400)
cn->level = meshtastic_LogRecord_Level_ERROR; bw = 406.25;
sprintf(cn->message, err_string); if (bw == 800)
service->sendClientNotification(cn); bw = 812.5;
if (bw == 1600)
// Set to default modem preset bw = 1625.0;
loraConfig.use_preset = true;
loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
} else {
validConfig = true;
}
} }
power = loraConfig.tx_power; power = loraConfig.tx_power;
@@ -543,7 +515,6 @@ void RadioInterface::applyModemConfig()
saveChannelNum(channel_num); saveChannelNum(channel_num);
saveFreq(freq + loraConfig.frequency_offset); saveFreq(freq + loraConfig.frequency_offset);
slotTimeMsec = computeSlotTimeMsec(bw, sf);
preambleTimeMsec = getPacketTime((uint32_t)0); preambleTimeMsec = getPacketTime((uint32_t)0);
maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader)); maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));

View File

@@ -5,7 +5,6 @@
#include "Observer.h" #include "Observer.h"
#include "PointerQueue.h" #include "PointerQueue.h"
#include "airtime.h" #include "airtime.h"
#include "error.h"
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
@@ -72,20 +71,18 @@ class RadioInterface
- roundtrip air propagation time (assuming max. 30km between nodes); - roundtrip air propagation time (assuming max. 30km between nodes);
- Tx/Rx turnaround time (maximum of SX126x and SX127x); - Tx/Rx turnaround time (maximum of SX126x and SX127x);
- MAC processing time (measured on T-beam) */ - MAC processing time (measured on T-beam) */
uint32_t slotTimeMsec = computeSlotTimeMsec(bw, sf); uint32_t slotTimeMsec = 8.5 * pow(2, sf) / bw + 0.2 + 0.4 + 7;
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast
const uint32_t PROCESSING_TIME_MSEC = const uint32_t PROCESSING_TIME_MSEC =
4500; // time to construct, process and construct a packet again (empirically determined) 4500; // time to construct, process and construct a packet again (empirically determined)
const uint8_t CWmin = 2; // minimum CWsize const uint8_t CWmin = 2; // minimum CWsize
const uint8_t CWmax = 7; // maximum CWsize const uint8_t CWmax = 8; // maximum CWsize
meshtastic_MeshPacket *sendingPacket = NULL; // The packet we are currently sending meshtastic_MeshPacket *sendingPacket = NULL; // The packet we are currently sending
uint32_t lastTxStart = 0L; uint32_t lastTxStart = 0L;
uint32_t computeSlotTimeMsec(float bw, float sf) { return 8.5 * pow(2, sf) / bw + 0.2 + 0.4 + 7; }
/** /**
* A temporary buffer used for sending/receiving packets, sized to hold the biggest buffer we might need * A temporary buffer used for sending/receiving packets, sized to hold the biggest buffer we might need
* */ * */

Some files were not shown because too many files have changed in this diff Show More