Merge branch 'master' into portexpander-keyboard

This commit is contained in:
Thomas Göttgens
2024-09-04 14:48:39 +02:00
committed by GitHub
422 changed files with 47169 additions and 4636 deletions

25
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
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 \
pipx \
pkg-config \
python3 \
python3-pip \
python3-venv \
python3-wheel \
wget \
zip \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN pipx install platformio==6.1.15

View File

@@ -0,0 +1,28 @@
// 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",
}

3
.devcontainer/setup.sh Executable file
View File

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

View File

@@ -52,6 +52,7 @@ body:
- Raspberry Pi Pico (W) - Raspberry Pi Pico (W)
- Relay v1 - Relay v1
- Relay v2 - Relay v2
- Seeed Wio Tracker 1110
- DIY - DIY
- Other - Other
validations: validations:

View File

@@ -14,7 +14,7 @@ runs:
- name: Install dependencies - name: Install dependencies
shell: bash shell: bash
run: | run: |
sudo apt-get -y update sudo apt-get -y update --fix-missing
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev
- name: Setup Python - name: Setup Python
@@ -22,19 +22,21 @@ 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 platformio adafruit-nrfutil pip install -U --no-build-isolation --no-cache-dir "setuptools<72"
pip install -U meshtastic --pre pip install -U platformio adafruit-nrfutil --no-build-isolation
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

@@ -10,10 +10,10 @@ jobs:
build-native: build-native:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install libbluetooth - name: Install libs needed for native build
shell: bash shell: bash
run: | run: |
sudo apt-get update 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 sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
- name: Checkout code - name: Checkout code

View File

@@ -29,6 +29,7 @@ jobs:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
overwrite: true overwrite: true
path: | path: |
release/*.hex
release/*.uf2 release/*.uf2
release/*.elf release/*.elf
release/*.zip release/*.zip

View File

@@ -13,6 +13,7 @@ jobs:
- name: Install libbluetooth - name: Install libbluetooth
shell: bash shell: bash
run: | run: |
apt-get update -y --fix-missing
apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
- name: Checkout code - name: Checkout code

View File

@@ -13,6 +13,7 @@ jobs:
- name: Install libbluetooth - name: Install libbluetooth
shell: bash shell: bash
run: | run: |
apt-get update -y --fix-missing
apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
- name: Checkout code - name: Checkout code

33
.github/workflows/build_stm32.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
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, check] arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, stm32, check]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- id: checkout - id: checkout
@@ -41,6 +41,7 @@ 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:
@@ -103,6 +104,15 @@ 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
@@ -134,9 +144,10 @@ 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,
] ]
steps: steps:
- name: Checkout code - name: Checkout code
@@ -168,6 +179,7 @@ jobs:
path: | path: |
./firmware-*.bin ./firmware-*.bin
./firmware-*.uf2 ./firmware-*.uf2
./firmware-*.hex
./firmware-*-ota.zip ./firmware-*-ota.zip
./device-*.sh ./device-*.sh
./device-*.bat ./device-*.bat

91
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,91 @@
name: End to end tests
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
hardware-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Upgrade python tools
shell: bash
run: |
python -m pip install --upgrade pip
pip install -U --no-build-isolation --no-cache-dir "setuptools<72"
pip install -U platformio adafruit-nrfutil --no-build-isolation
pip install -U poetry --no-build-isolation
pip install -U meshtastic --pre --no-build-isolation
- name: Upgrade platformio
shell: bash
run: |
pio upgrade
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: Install Dependencies
run: pnpm install
- name: Setup devices
run: pnpm run setup
- name: Execute end to end tests on connected hardware
run: pnpm run test

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "protobufs"] [submodule "protobufs"]
path = protobufs path = protobufs
url = https://github.com/meshtastic/protobufs.git url = https://github.com/meshtastic/protobufs.git
[submodule "meshtestic"]
path = meshtestic
url = https://github.com/meshtastic/meshTestic

2
.gitpod.yml Normal file
View File

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

View File

@@ -1,2 +1,2 @@
.github/workflows/main_matrix.yml .github/workflows/main_matrix.yml
src/mesh/compression/unishox2.c src/mesh/compression/unishox2.cpp

View File

@@ -1,36 +1,40 @@
version: 0.1 version: 0.1
cli: cli:
version: 1.22.1 version: 1.22.3
plugins: plugins:
sources: sources:
- id: trunk - id: trunk
ref: v1.5.0 ref: v1.6.2
uri: https://github.com/trunk-io/plugins uri: https://github.com/trunk-io/plugins
lint: lint:
enabled: enabled:
- trufflehog@3.76.3 - trufflehog@3.81.9
- yamllint@1.35.1 - yamllint@1.35.1
- bandit@1.7.8 - bandit@1.7.9
- checkov@3.2.95 - checkov@3.2.238
- terrascan@1.19.1 - terrascan@1.19.1
- trivy@0.51.1 - trivy@0.54.1
#- trufflehog@3.63.2-rc0 #- trufflehog@3.63.2-rc0
- taplo@0.8.1 - taplo@0.9.3
- ruff@0.4.4 - ruff@0.6.2
- isort@5.13.2 - isort@5.13.2
- markdownlint@0.40.0 - markdownlint@0.41.0
- oxipng@9.1.1 - oxipng@9.1.2
- svgo@3.3.2 - svgo@3.3.2
- actionlint@1.7.0 - actionlint@1.7.1
- flake8@7.0.0 - flake8@7.1.1
- hadolint@2.12.0 - hadolint@2.12.0
- shfmt@3.6.0 - shfmt@3.6.0
- shellcheck@0.10.0 - shellcheck@0.10.0
- black@24.4.2 - black@24.8.0
- git-diff-check - git-diff-check
- gitleaks@8.18.2 - gitleaks@8.18.4
- clang-format@16.0.3 - clang-format@16.0.3
- prettier@3.2.5 - prettier@3.3.3
ignore:
- linters: [ALL]
paths:
- bin/**
runtimes: runtimes:
enabled: enabled:
- python@3.10.8 - python@3.10.8

View File

@@ -2,8 +2,9 @@
// See http://go.microsoft.com/fwlink/?LinkId=827846 // See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format // for the documentation about the extensions.json format
"recommendations": [ "recommendations": [
"ms-vscode.cpptools", "platformio.platformio-ide"
"platformio.platformio-ide",
"trunk.io"
], ],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
} }

View File

@@ -4,5 +4,8 @@
"trunk.enableWindows": true, "trunk.enableWindows": true,
"files.insertFinalNewline": false, "files.insertFinalNewline": false,
"files.trimFinalNewlines": false, "files.trimFinalNewlines": false,
"cmake.configureOnOpen": false "cmake.configureOnOpen": false,
"[cpp]": {
"editor.defaultFormatter": "trunk.io"
}
} }

View File

@@ -7,8 +7,6 @@ ENV TZ=Etc/UTC
# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK. # > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK.
ENV LANG C.UTF-8 ENV LANG C.UTF-8
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Install build deps # Install build deps
USER root USER root
@@ -24,10 +22,10 @@ USER mesh
WORKDIR /tmp/firmware WORKDIR /tmp/firmware
RUN python3 -m venv /tmp/firmware RUN python3 -m venv /tmp/firmware
RUN source ./bin/activate && pip3 install --no-cache-dir -U platformio==6.1.14 RUN bash -o pipefail -c "source bin/activate; pip3 install --no-cache-dir -U platformio==6.1.15"
# trunk-ignore(terrascan/AC_DOCKER_00024): We would actually like these files to be owned by mesh tyvm # trunk-ignore(terrascan/AC_DOCKER_00024): We would actually like these files to be owned by mesh tyvm
COPY --chown=mesh:mesh . /tmp/firmware COPY --chown=mesh:mesh . /tmp/firmware
RUN source ./bin/activate && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh RUN bash -o pipefail -c "source ./bin/activate && bash ./bin/build-native.sh"
RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd" RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd"

View File

@@ -44,10 +44,11 @@ lib_deps =
${networking_base.lib_deps} ${networking_base.lib_deps}
${environmental_base.lib_deps} ${environmental_base.lib_deps}
https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2
h2zero/NimBLE-Arduino@^1.4.1 h2zero/NimBLE-Arduino@^1.4.2
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

@@ -0,0 +1,208 @@
/*
* 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,9 +2,13 @@
; 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
@@ -16,6 +20,7 @@ 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,3 +7,72 @@ 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

37
arch/stm32/stm32.ini Normal file
View File

@@ -0,0 +1,37 @@
[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

View File

@@ -1,28 +0,0 @@
[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.

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 platformio pkg update -e $1
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

@@ -2,8 +2,8 @@
set -e set -e
VERSION=`bin/buildinfo.py long` VERSION=$(bin/buildinfo.py long)
SHORT_VERSION=`bin/buildinfo.py short` SHORT_VERSION=$(bin/buildinfo.py short)
OUTDIR=release/ OUTDIR=release/
@@ -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 platformio pkg update -e $1
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.*
@@ -23,14 +23,31 @@ basename=firmware-$1-$VERSION
pio run --environment $1 # -v pio run --environment $1 # -v
SRCELF=.pio/build/$1/firmware.elf SRCELF=.pio/build/$1/firmware.elf
DFUPKG=.pio/build/$1/firmware.zip
cp $SRCELF $OUTDIR/$basename.elf cp $SRCELF $OUTDIR/$basename.elf
echo "Generating NRF52 dfu file"
DFUPKG=.pio/build/$1/firmware.zip
cp $DFUPKG $OUTDIR/$basename-ota.zip cp $DFUPKG $OUTDIR/$basename-ota.zip
echo "Generating NRF52 uf2 file" echo "Generating NRF52 uf2 file"
SRCHEX=.pio/build/$1/firmware.hex SRCHEX=.pio/build/$1/firmware.hex
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840
cp bin/device-install.* $OUTDIR # if WM1110 target, merge hex with softdevice 7.3.0
cp bin/device-update.* $OUTDIR if (echo $1 | grep -q "wio-sdk-wm1110"); then
cp bin/*.uf2 $OUTDIR echo "Merging with softdevice"
bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/$basename.hex
SRCHEX=.pio/build/$1/$basename.hex
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840
cp $SRCHEX $OUTDIR
cp bin/*.uf2 $OUTDIR
else
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840
cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR
cp bin/*.uf2 $OUTDIR
fi
if (echo $1 | grep -q "rak4631"); then
echo "Copying hex file"
cp .pio/build/$1/firmware.hex $OUTDIR/$basename.hex
fi

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 platformio pkg update -e $1
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.*

29
bin/build-stm32.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/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,6 +54,8 @@ 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
@@ -111,6 +113,9 @@ 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.
@@ -126,15 +131,21 @@ Touchscreen:
# CS: 7 # CS: 7
# IRQ: 17 # IRQ: 17
### Configure device for direct keyboard input ### You can also specify the spi device for the touchscreen to use
# spidev: spidev0.0
Input: Input:
### Configure device for direct keyboard 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
@@ -142,3 +153,4 @@ Webserver:
General: General:
MaxNodes: 200 MaxNodes: 200
MaxMessageQueue: 100

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
bin/mergehex Executable file

Binary file not shown.

View File

@@ -1,3 +1,4 @@
#!/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
@@ -78,6 +79,11 @@ if platform.name == "espressif32":
# 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 platform.name == "nordicnrf52":
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex",
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")
prefsLoc = projenv["PROJECT_DIR"] + "/version.properties" prefsLoc = projenv["PROJECT_DIR"] + "/version.properties"

View File

@@ -1,9 +1,5 @@
import subprocess
import configparser import configparser
import traceback import subprocess
import sys
def readProps(prefsLoc): def readProps(prefsLoc):
@@ -11,27 +7,36 @@ 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(short = "{}.{}.{}".format(version["major"], version["minor"], version["build"]), verObj = dict(
long = "unset") short="{}.{}.{}".format(version["major"], version["minor"], version["build"]),
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 = subprocess.check_output( sha = (
['git', 'rev-parse', '--short', 'HEAD']).decode("utf-8").strip() subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
isDirty = subprocess.check_output( .decode("utf-8")
['git', 'diff', 'HEAD']).decode("utf-8").strip() .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))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
# shellcheck shell=bash
# (this minor script is actually shell agnostic, and is intended to be sourced rather than run in a subshell)
# This is a little script you can source if you want to make ESP debugging work on a modern (24.04) ubuntu machine
# It assumes you have built and installed python 2.7 from source with:
# ./configure --enable-optimizations --enable-shared --enable-unicode=ucs4
# sudo make clean
# make
# sudo make altinstall
export LD_LIBRARY_PATH=$HOME/packages/python-2.7.18/
export PYTHON_HOME=/usr/local/lib/python2.7/

View File

@@ -1,39 +1,38 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import argparse
import struct
import subprocess
import re
import os import os
import os.path import os.path
import argparse import re
import struct
import subprocess
import sys
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected UF2_MAGIC_END = 0x0AB16F30 # Ditto
UF2_MAGIC_END = 0x0AB16F30 # Ditto
families = { families = {
'SAMD21': 0x68ed2b88, "SAMD21": 0x68ED2B88,
'SAML21': 0x1851780a, "SAML21": 0x1851780A,
'SAMD51': 0x55114460, "SAMD51": 0x55114460,
'NRF52': 0x1b57745f, "NRF52": 0x1B57745F,
'STM32F0': 0x647824b6, "STM32F0": 0x647824B6,
'STM32F1': 0x5ee21072, "STM32F1": 0x5EE21072,
'STM32F2': 0x5d1a0a2e, "STM32F2": 0x5D1A0A2E,
'STM32F3': 0x6b846188, "STM32F3": 0x6B846188,
'STM32F4': 0x57755a57, "STM32F4": 0x57755A57,
'STM32F7': 0x53b80f00, "STM32F7": 0x53B80F00,
'STM32G0': 0x300f5633, "STM32G0": 0x300F5633,
'STM32G4': 0x4c71240a, "STM32G4": 0x4C71240A,
'STM32H7': 0x6db66082, "STM32H7": 0x6DB66082,
'STM32L0': 0x202e3a91, "STM32L0": 0x202E3A91,
'STM32L1': 0x1e1f432d, "STM32L1": 0x1E1F432D,
'STM32L4': 0x00ff6919, "STM32L4": 0x00FF6919,
'STM32L5': 0x04240bdf, "STM32L5": 0x04240BDF,
'STM32WB': 0x70d16653, "STM32WB": 0x70D16653,
'STM32WL': 0x21460ff0, "STM32WL": 0x21460FF0,
'ATMEGA32': 0x16573617, "ATMEGA32": 0x16573617,
'MIMXRT10XX': 0x4FB2D5BD "MIMXRT10XX": 0x4FB2D5BD,
} }
INFO_FILE = "/INFO_UF2.TXT" INFO_FILE = "/INFO_UF2.TXT"
@@ -46,15 +45,17 @@ def is_uf2(buf):
w = struct.unpack("<II", buf[0:8]) w = struct.unpack("<II", buf[0:8])
return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1 return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
def is_hex(buf): def is_hex(buf):
try: try:
w = buf[0:30].decode("utf-8") w = buf[0:30].decode("utf-8")
except UnicodeDecodeError: except UnicodeDecodeError:
return False return False
if w[0] == ':' and re.match(b"^[:0-9a-fA-F\r\n]+$", buf): if w[0] == ":" and re.match(b"^[:0-9a-fA-F\r\n]+$", buf):
return True return True
return False return False
def convert_from_uf2(buf): def convert_from_uf2(buf):
global appstartaddr global appstartaddr
numblocks = len(buf) // 512 numblocks = len(buf) // 512
@@ -62,7 +63,7 @@ def convert_from_uf2(buf):
outp = b"" outp = b""
for blockno in range(numblocks): for blockno in range(numblocks):
ptr = blockno * 512 ptr = blockno * 512
block = buf[ptr:ptr + 512] block = buf[ptr : ptr + 512]
hd = struct.unpack(b"<IIIIIIII", block[0:32]) hd = struct.unpack(b"<IIIIIIII", block[0:32])
if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1: if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
print("Skipping block at " + ptr + "; bad magic") print("Skipping block at " + ptr + "; bad magic")
@@ -80,7 +81,7 @@ def convert_from_uf2(buf):
padding = newaddr - curraddr padding = newaddr - curraddr
if padding < 0: if padding < 0:
assert False, "Block out of order at " + ptr assert False, "Block out of order at " + ptr
if padding > 10*1024*1024: if padding > 10 * 1024 * 1024:
assert False, "More than 10M of padding needed at " + ptr assert False, "More than 10M of padding needed at " + ptr
if padding % 4 != 0: if padding % 4 != 0:
assert False, "Non-word padding size at " + ptr assert False, "Non-word padding size at " + ptr
@@ -91,6 +92,7 @@ def convert_from_uf2(buf):
curraddr = newaddr + datalen curraddr = newaddr + datalen
return outp return outp
def convert_to_carray(file_content): def convert_to_carray(file_content):
outp = "const unsigned char bindata[] __attribute__((aligned(16))) = {" outp = "const unsigned char bindata[] __attribute__((aligned(16))) = {"
for i in range(len(file_content)): for i in range(len(file_content)):
@@ -100,6 +102,7 @@ def convert_to_carray(file_content):
outp += "\n};\n" outp += "\n};\n"
return outp return outp
def convert_to_uf2(file_content): def convert_to_uf2(file_content):
global familyid global familyid
datapadding = b"" datapadding = b""
@@ -109,13 +112,21 @@ def convert_to_uf2(file_content):
outp = b"" outp = b""
for blockno in range(numblocks): for blockno in range(numblocks):
ptr = 256 * blockno ptr = 256 * blockno
chunk = file_content[ptr:ptr + 256] chunk = file_content[ptr : ptr + 256]
flags = 0x0 flags = 0x0
if familyid: if familyid:
flags |= 0x2000 flags |= 0x2000
hd = struct.pack(b"<IIIIIIII", hd = struct.pack(
UF2_MAGIC_START0, UF2_MAGIC_START1, b"<IIIIIIII",
flags, ptr + appstartaddr, 256, blockno, numblocks, familyid) UF2_MAGIC_START0,
UF2_MAGIC_START1,
flags,
ptr + appstartaddr,
256,
blockno,
numblocks,
familyid,
)
while len(chunk) < 256: while len(chunk) < 256:
chunk += b"\x00" chunk += b"\x00"
block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END) block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
@@ -123,6 +134,7 @@ def convert_to_uf2(file_content):
outp += block outp += block
return outp return outp
class Block: class Block:
def __init__(self, addr): def __init__(self, addr):
self.addr = addr self.addr = addr
@@ -133,35 +145,44 @@ class Block:
flags = 0x0 flags = 0x0
if familyid: if familyid:
flags |= 0x2000 flags |= 0x2000
hd = struct.pack("<IIIIIIII", hd = struct.pack(
UF2_MAGIC_START0, UF2_MAGIC_START1, "<IIIIIIII",
flags, self.addr, 256, blockno, numblocks, familyid) UF2_MAGIC_START0,
UF2_MAGIC_START1,
flags,
self.addr,
256,
blockno,
numblocks,
familyid,
)
hd += self.bytes[0:256] hd += self.bytes[0:256]
while len(hd) < 512 - 4: while len(hd) < 512 - 4:
hd += b"\x00" hd += b"\x00"
hd += struct.pack("<I", UF2_MAGIC_END) hd += struct.pack("<I", UF2_MAGIC_END)
return hd return hd
def convert_from_hex_to_uf2(buf): def convert_from_hex_to_uf2(buf):
global appstartaddr global appstartaddr
appstartaddr = None appstartaddr = None
upper = 0 upper = 0
currblock = None currblock = None
blocks = [] blocks = []
for line in buf.split('\n'): for line in buf.split("\n"):
if line[0] != ":": if line[0] != ":":
continue continue
i = 1 i = 1
rec = [] rec = []
while i < len(line) - 1: while i < len(line) - 1:
rec.append(int(line[i:i+2], 16)) rec.append(int(line[i : i + 2], 16))
i += 2 i += 2
tp = rec[3] tp = rec[3]
if tp == 4: if tp == 4:
upper = ((rec[4] << 8) | rec[5]) << 16 upper = ((rec[4] << 8) | rec[5]) << 16
elif tp == 2: elif tp == 2:
upper = ((rec[4] << 8) | rec[5]) << 4 upper = ((rec[4] << 8) | rec[5]) << 4
assert (upper & 0xffff) == 0 assert (upper & 0xFFFF) == 0
elif tp == 1: elif tp == 1:
break break
elif tp == 0: elif tp == 0:
@@ -170,10 +191,10 @@ def convert_from_hex_to_uf2(buf):
appstartaddr = addr appstartaddr = addr
i = 4 i = 4
while i < len(rec) - 1: while i < len(rec) - 1:
if not currblock or currblock.addr & ~0xff != addr & ~0xff: if not currblock or currblock.addr & ~0xFF != addr & ~0xFF:
currblock = Block(addr & ~0xff) currblock = Block(addr & ~0xFF)
blocks.append(currblock) blocks.append(currblock)
currblock.bytes[addr & 0xff] = rec[i] currblock.bytes[addr & 0xFF] = rec[i]
addr += 1 addr += 1
i += 1 i += 1
numblocks = len(blocks) numblocks = len(blocks)
@@ -182,17 +203,28 @@ def convert_from_hex_to_uf2(buf):
resfile += blocks[i].encode(i, numblocks) resfile += blocks[i].encode(i, numblocks)
return resfile return resfile
def to_str(b): def to_str(b):
return b.decode("utf-8") return b.decode("utf-8")
def get_drives(): def get_drives():
drives = [] drives = []
if sys.platform == "win32": if sys.platform == "win32":
r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk", r = subprocess.check_output(
"get", "DeviceID,", "VolumeName,", [
"FileSystem,", "DriveType"]) "wmic",
for line in to_str(r).split('\n'): "PATH",
words = re.split('\s+', line) "Win32_LogicalDisk",
"get",
"DeviceID,",
"VolumeName,",
"FileSystem,",
"DriveType",
]
)
for line in to_str(r).split("\n"):
words = re.split("\\s+", line)
if len(words) >= 3 and words[1] == "2" and words[2] == "FAT": if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
drives.append(words[0]) drives.append(words[0])
else: else:
@@ -206,7 +238,6 @@ def get_drives():
for d in os.listdir(rootpath): for d in os.listdir(rootpath):
drives.append(os.path.join(rootpath, d)) drives.append(os.path.join(rootpath, d))
def has_info(d): def has_info(d):
try: try:
return os.path.isfile(d + INFO_FILE) return os.path.isfile(d + INFO_FILE)
@@ -217,7 +248,7 @@ def get_drives():
def board_id(path): def board_id(path):
with open(path + INFO_FILE, mode='r') as file: with open(path + INFO_FILE, mode="r") as file:
file_content = file.read() file_content = file.read()
return re.search("Board-ID: ([^\r\n]*)", file_content).group(1) return re.search("Board-ID: ([^\r\n]*)", file_content).group(1)
@@ -235,30 +266,61 @@ def write_file(name, buf):
def main(): def main():
global appstartaddr, familyid global appstartaddr, familyid
def error(msg): def error(msg):
print(msg) print(msg)
sys.exit(1) sys.exit(1)
parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
parser.add_argument('input', metavar='INPUT', type=str, nargs='?', parser = argparse.ArgumentParser(description="Convert to UF2 or flash directly.")
help='input file (HEX, BIN or UF2)') parser.add_argument(
parser.add_argument('-b' , '--base', dest='base', type=str, "input",
default="0x2000", metavar="INPUT",
help='set base address of application for BIN format (default: 0x2000)') type=str,
parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str, nargs="?",
help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible') help="input file (HEX, BIN or UF2)",
parser.add_argument('-d' , '--device', dest="device_path", )
help='select a device path to flash') parser.add_argument(
parser.add_argument('-l' , '--list', action='store_true', "-b",
help='list connected devices') "--base",
parser.add_argument('-c' , '--convert', action='store_true', dest="base",
help='do not flash, just convert') type=str,
parser.add_argument('-D' , '--deploy', action='store_true', default="0x2000",
help='just flash, do not convert') help="set base address of application for BIN format (default: 0x2000)",
parser.add_argument('-f' , '--family', dest='family', type=str, )
default="0x0", parser.add_argument(
help='specify familyID - number or name (default: 0x0)') "-o",
parser.add_argument('-C' , '--carray', action='store_true', "--output",
help='convert binary file to a C array, not UF2') metavar="FILE",
dest="output",
type=str,
help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible',
)
parser.add_argument(
"-d", "--device", dest="device_path", help="select a device path to flash"
)
parser.add_argument(
"-l", "--list", action="store_true", help="list connected devices"
)
parser.add_argument(
"-c", "--convert", action="store_true", help="do not flash, just convert"
)
parser.add_argument(
"-D", "--deploy", action="store_true", help="just flash, do not convert"
)
parser.add_argument(
"-f",
"--family",
dest="family",
type=str,
default="0x0",
help="specify familyID - number or name (default: 0x0)",
)
parser.add_argument(
"-C",
"--carray",
action="store_true",
help="convert binary file to a C array, not UF2",
)
args = parser.parse_args() args = parser.parse_args()
appstartaddr = int(args.base, 0) appstartaddr = int(args.base, 0)
@@ -268,14 +330,17 @@ def main():
try: try:
familyid = int(args.family, 0) familyid = int(args.family, 0)
except ValueError: except ValueError:
error("Family ID needs to be a number or one of: " + ", ".join(families.keys())) error(
"Family ID needs to be a number or one of: "
+ ", ".join(families.keys())
)
if args.list: if args.list:
list_drives() list_drives()
else: else:
if not args.input: if not args.input:
error("Need input file") error("Need input file")
with open(args.input, mode='rb') as f: with open(args.input, mode="rb") as f:
inpbuf = f.read() inpbuf = f.read()
from_uf2 = is_uf2(inpbuf) from_uf2 = is_uf2(inpbuf)
ext = "uf2" ext = "uf2"
@@ -291,8 +356,10 @@ def main():
ext = "h" ext = "h"
else: else:
outbuf = convert_to_uf2(inpbuf) outbuf = convert_to_uf2(inpbuf)
print("Converting to %s, output size: %d, start address: 0x%x" % print(
(ext, len(outbuf), appstartaddr)) "Converting to %s, output size: %d, start address: 0x%x"
% (ext, len(outbuf), appstartaddr)
)
if args.convert or ext != "uf2": if args.convert or ext != "uf2":
drives = [] drives = []
if args.output == None: if args.output == None:

View File

@@ -0,0 +1,30 @@
# 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

@@ -19,7 +19,7 @@
"mcu": "esp32s3", "mcu": "esp32s3",
"variant": "CDEBYTE_EoRa-S3" "variant": "CDEBYTE_EoRa-S3"
}, },
"connectivity": ["wifi"], "connectivity": ["wifi", "bluetooth"],
"debug": { "debug": {
"openocd_target": "esp32s3.cfg" "openocd_target": "esp32s3.cfg"
}, },

View File

@@ -19,7 +19,7 @@
"mcu": "esp32s3", "mcu": "esp32s3",
"variant": "ESP32-S3-WROOM-1-N4" "variant": "ESP32-S3-WROOM-1-N4"
}, },
"connectivity": ["wifi"], "connectivity": ["wifi", "bluetooth"],
"debug": { "debug": {
"default_tool": "esp-builtin", "default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"], "onboard_tools": ["esp-builtin"],

View File

@@ -0,0 +1,53 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x4405"],
["0x239A", "0x0029"],
["0x239A", "0x002A"]
],
"usb_product": "HT-n5262",
"mcu": "nrf52840",
"variant": "heltec_mesh_node_t114",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Heltec nrf (Adafruit BSP)",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "FIXME",
"vendor": "Heltec"
}

View File

@@ -0,0 +1,42 @@
{
"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_e213"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "Heltec Vision Master E213",
"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-e213/",
"vendor": "Heltec"
}

View File

@@ -0,0 +1,42 @@
{
"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

@@ -0,0 +1,42 @@
{
"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

@@ -0,0 +1,58 @@
{
"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"
}

58
boards/ms24sf1.json Normal file
View File

@@ -0,0 +1,58 @@
{
"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": "MS24SF1-BOOT",
"mcu": "nrf52840",
"variant": "MINEWSEMI_MS24SF1_SX1262",
"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": "MINEWSEMI_MS24SF1_SX1262",
"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/nrf52840-sx1262-ms24sf1",
"vendor": "Minesemi"
}

View File

@@ -19,7 +19,7 @@
"mcu": "esp32s3", "mcu": "esp32s3",
"variant": "tlora-t3s3-v1" "variant": "tlora-t3s3-v1"
}, },
"connectivity": ["wifi"], "connectivity": ["wifi", "bluetooth"],
"debug": { "debug": {
"openocd_target": "esp32s3.cfg" "openocd_target": "esp32s3.cfg"
}, },

View File

@@ -0,0 +1,58 @@
{
"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

@@ -1,19 +1,12 @@
{ {
"build": { "build": {
"arduino": { "arduino": {
"ldscript": "nrf52840_s140_v6.ld" "ldscript": "nrf52840_s140_v7.ld"
}, },
"core": "nRF5", "core": "nRF5",
"cpu": "cortex-m4", "cpu": "cortex-m4",
"extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
"f_cpu": "64000000L", "f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "WIO-BOOT",
"mcu": "nrf52840", "mcu": "nrf52840",
"variant": "Seeed_WIO_WM1110", "variant": "Seeed_WIO_WM1110",
"bsp": { "bsp": {
@@ -22,8 +15,8 @@
"softdevice": { "softdevice": {
"sd_flags": "-DS140", "sd_flags": "-DS140",
"sd_name": "s140", "sd_name": "s140",
"sd_version": "6.1.1", "sd_version": "7.3.0",
"sd_fwid": "0x00B6" "sd_fwid": "0x0123"
}, },
"bootloader": { "bootloader": {
"settings_addr": "0xFF000" "settings_addr": "0xFF000"
@@ -34,7 +27,7 @@
"jlink_device": "nRF52840_xxAA", "jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd" "svd_path": "nrf52840.svd"
}, },
"frameworks": ["arduino"], "frameworks": ["arduino", "freertos"],
"name": "Seeed WIO WM1110", "name": "Seeed WIO WM1110",
"upload": { "upload": {
"maximum_ram_size": 248832, "maximum_ram_size": 248832,

58
boards/wio-t1000-s.json Normal file
View File

@@ -0,0 +1,58 @@
{
"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": "WIO-BOOT",
"mcu": "nrf52840",
"variant": "Seeed_WIO_WM1110",
"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 WIO WM1110",
"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/LoRaWAN-Tracker-c-1938.html",
"vendor": "Seeed Studio"
}

View File

@@ -1,7 +1,7 @@
{ {
"build": { "build": {
"arduino": { "arduino": {
"ldscript": "nrf52840_s140_v6.ld" "ldscript": "nrf52840_s140_v7.ld"
}, },
"core": "nRF5", "core": "nRF5",
"cpu": "cortex-m4", "cpu": "cortex-m4",
@@ -22,8 +22,8 @@
"softdevice": { "softdevice": {
"sd_flags": "-DS140", "sd_flags": "-DS140",
"sd_name": "s140", "sd_name": "s140",
"sd_version": "6.1.1", "sd_version": "7.3.0",
"sd_fwid": "0x00B6" "sd_fwid": "0x0123"
}, },
"bootloader": { "bootloader": {
"settings_addr": "0xFF000" "settings_addr": "0xFF000"
@@ -53,6 +53,6 @@
"require_upload_port": true, "require_upload_port": true,
"wait_for_upload_port": true "wait_for_upload_port": true
}, },
"url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html", "url": "https://www.seeedstudio.com/Wio-Tracker-1110-Dev-Board-p-5799.html",
"vendor": "Seeed Studio" "vendor": "Seeed Studio"
} }

View File

@@ -1,5 +1,8 @@
{ {
"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

@@ -35,7 +35,7 @@
"svd_path": "nrf52840.svd", "svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs" "openocd_target": "nrf52840-mdk-rs"
}, },
"frameworks": ["arduino"], "frameworks": ["arduino", "freertos"],
"name": "WisCore RAK4631 Board", "name": "WisCore RAK4631 Board",
"upload": { "upload": {
"maximum_ram_size": 248832, "maximum_ram_size": 248832,

3
extra_scripts/README.md Normal file
View File

@@ -0,0 +1,3 @@
# extra_scripts
This directory contains special [scripts](https://docs.platformio.org/en/latest/scripting/index.html) that are used to modify the platformio environment in rare cases.

View File

@@ -0,0 +1,28 @@
# trunk-ignore-all(flake8/F821)
# trunk-ignore-all(ruff/F821)
Import("env")
# 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 Build targets", BUILD_TARGETS)
# 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.
# 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
lib_builders = env.get("__PIO_LIB_BUILDERS", None)
if lib_builders is not None:
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
# env.AddPreAction("buildprog", callback...)

1
meshtestic Submodule

Submodule meshtestic added at 31ee3d90c8

View File

@@ -18,10 +18,7 @@ import subprocess
import sys import sys
from platformio.project.exception import PlatformioException from platformio.project.exception import PlatformioException
from platformio.public import ( from platformio.public import DeviceMonitorFilterBase, load_build_metadata
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
@@ -32,7 +29,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 = ""
@@ -117,7 +114,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
@@ -125,14 +122,10 @@ 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, u"-fipC", u"-e", self.firmware_path] args = [self.addr2line_path, "-fipC", "-e", self.firmware_path]
try: try:
addr = match.group() addr = match.group()
output = ( output = subprocess.check_output(args + [addr]).decode(enc).strip()
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,13 +34,19 @@ 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_e213
;default_envs = heltec_vision_master_e290
;default_envs = heltec_mesh_node_t114
extra_configs = extra_configs =
arch/*/*.ini arch/*/*.ini
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
@@ -75,19 +81,22 @@ build_flags = -Wno-missing-field-initializers
-DRADIOLIB_EXCLUDE_APRS -DRADIOLIB_EXCLUDE_APRS
-DRADIOLIB_EXCLUDE_LORAWAN -DRADIOLIB_EXCLUDE_LORAWAN
-DMESHTASTIC_EXCLUDE_DROPZONE=1 -DMESHTASTIC_EXCLUDE_DROPZONE=1
;-D OLED_PL
monitor_speed = 115200 monitor_speed = 115200
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#2b40affbe7f7dc63b6c00fa88e7e12ed1f8e1719 ; ESP8266_SSD1306 https://github.com/jgromes/RadioLib.git#3115fc2d6700a9aee05888791ac930a910f2628f
mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306
robtillaart/I2CKeyPad@^0.4.0 ; port extender with keymatrix https://github.com/mathertel/OneButton@~2.6.1 ; 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
nanopb/Nanopb@^0.4.8 nanopb/Nanopb@^0.4.8
erriez/ErriezCRC32@^1.0.1 erriez/ErriezCRC32@^1.0.1
robtillaart/I2CKeyPad@^0.4.0 ; port extender with keymatrix
; Used for the code analysis in PIO Home / Inspect ; Used for the code analysis in PIO Home / Inspect
check_tool = cppcheck check_tool = cppcheck
@@ -124,6 +133,7 @@ lib_deps =
adafruit/Adafruit BMP280 Library@^2.6.8 adafruit/Adafruit BMP280 Library@^2.6.8
adafruit/Adafruit BMP085 Library@^1.2.4 adafruit/Adafruit BMP085 Library@^1.2.4
adafruit/Adafruit BME280 Library@^2.2.2 adafruit/Adafruit BME280 Library@^2.2.2
adafruit/Adafruit BMP3XX Library@^2.1.5
adafruit/Adafruit MCP9808 Library@^2.0.0 adafruit/Adafruit MCP9808 Library@^2.0.0
adafruit/Adafruit INA260 Library@^1.5.0 adafruit/Adafruit INA260 Library@^1.5.0
adafruit/Adafruit INA219@^1.2.0 adafruit/Adafruit INA219@^1.2.0
@@ -152,4 +162,3 @@ lib_deps =
https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee

7
pyocd.yaml Normal file
View File

@@ -0,0 +1,7 @@
# This is a config file to control pyocd ICE debugger probe options (only used for NRF52 targets with hardware debugging connections)
# for more info see FIXMEURL
# console or telnet
semihost_console_type: telnet
enable_semihosting: True
telnet_port: 4444

View File

@@ -16,6 +16,8 @@
#include <Wire.h> #include <Wire.h>
#ifdef RAK_4631 #ifdef RAK_4631
#include "Fusion/Fusion.h" #include "Fusion/Fusion.h"
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
#include <Rak_BMX160.h> #include <Rak_BMX160.h>
#endif #endif
@@ -101,7 +103,11 @@ class AccelerometerThread : public concurrency::OSThread
bmx160.getAllData(&magAccel, NULL, &gAccel); bmx160.getAllData(&magAccel, NULL, &gAccel);
// expirimental calibrate routine. Limited to between 10 and 30 seconds after boot // expirimental calibrate routine. Limited to between 10 and 30 seconds after boot
if (millis() > 10 * 1000 && millis() < 30 * 1000) { if (millis() > 12 * 1000 && millis() < 30 * 1000) {
if (!showingScreen) {
showingScreen = true;
screen->startAlert((FrameCallback)drawFrameCalibration);
}
if (magAccel.x > highestX) if (magAccel.x > highestX)
highestX = magAccel.x; highestX = magAccel.x;
if (magAccel.x < lowestX) if (magAccel.x < lowestX)
@@ -114,6 +120,9 @@ class AccelerometerThread : public concurrency::OSThread
highestZ = magAccel.z; highestZ = magAccel.z;
if (magAccel.z < lowestZ) if (magAccel.z < lowestZ)
lowestZ = magAccel.z; lowestZ = magAccel.z;
} else if (showingScreen && millis() >= 30 * 1000) {
showingScreen = false;
screen->endAlert();
} }
int highestRealX = highestX - (highestX + lowestX) / 2; int highestRealX = highestX - (highestX + lowestX) / 2;
@@ -255,11 +264,34 @@ class AccelerometerThread : public concurrency::OSThread
Adafruit_LIS3DH lis; Adafruit_LIS3DH lis;
Adafruit_LSM6DS3TRC lsm; Adafruit_LSM6DS3TRC lsm;
SensorBMA423 bmaSensor; SensorBMA423 bmaSensor;
bool BMA_IRQ = false;
#ifdef RAK_4631 #ifdef RAK_4631
bool showingScreen = false;
RAK_BMX160 bmx160; RAK_BMX160 bmx160;
float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
static void drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
int x_offset = display->width() / 2;
int y_offset = display->height() <= 80 ? 0 : 32;
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_MEDIUM);
display->drawString(x, y, "Calibrating\nCompass");
int16_t compassX = 0, compassY = 0;
uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight());
// coordinates for the center of the compass/circle
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
compassX = x + display->getWidth() - compassDiam / 2 - 5;
compassY = y + display->getHeight() / 2;
} else {
compassX = x + display->getWidth() - compassDiam / 2 - 5;
compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2;
}
display->drawCircle(compassX, compassY, compassDiam / 2);
screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180);
}
#endif #endif
bool BMA_IRQ = false;
}; };
#endif #endif

View File

@@ -1,3 +1,4 @@
#include "Observer.h"
#include "configuration.h" #include "configuration.h"
#ifdef HAS_NCP5623 #ifdef HAS_NCP5623
@@ -22,10 +23,18 @@ 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;
@@ -82,9 +91,46 @@ 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
@@ -100,6 +146,17 @@ 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

@@ -11,3 +11,7 @@ const uint8_t FROMRADIO_UUID_16[16u] = {0x02, 0x00, 0x12, 0xac, 0x42, 0x02, 0x78
0xed, 0x11, 0x93, 0x49, 0x9e, 0xe6, 0x55, 0x2c}; 0xed, 0x11, 0x93, 0x49, 0x9e, 0xe6, 0x55, 0x2c};
const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6, const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6,
0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed}; 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed};
const uint8_t LEGACY_LOGRADIO_UUID_16[16u] = {0xe2, 0xf2, 0x1e, 0xbe, 0xc5, 0x15, 0xcf, 0xaa,
0x6b, 0x43, 0xfa, 0x78, 0x38, 0xd2, 0x6f, 0x6c};
const uint8_t LOGRADIO_UUID_16[16u] = {0x47, 0x95, 0xDF, 0x8C, 0xDE, 0xE9, 0x44, 0x99,
0x23, 0x44, 0xE6, 0x06, 0x49, 0x6E, 0x3D, 0x5A};

View File

@@ -11,10 +11,12 @@
#define TORADIO_UUID "f75c76d2-129e-4dad-a1dd-7866124401e7" #define TORADIO_UUID "f75c76d2-129e-4dad-a1dd-7866124401e7"
#define FROMRADIO_UUID "2c55e69e-4993-11ed-b878-0242ac120002" #define FROMRADIO_UUID "2c55e69e-4993-11ed-b878-0242ac120002"
#define FROMNUM_UUID "ed9da18c-a800-4f66-a670-aa7547e34453" #define FROMNUM_UUID "ed9da18c-a800-4f66-a670-aa7547e34453"
#define LEGACY_LOGRADIO_UUID "6c6fd238-78fa-436b-aacf-15c5be1ef2e2"
#define LOGRADIO_UUID "5a3d6e49-06e6-4423-9944-e9de8cdf9547"
// NRF52 wants these constants as byte arrays // NRF52 wants these constants as byte arrays
// Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER // Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER
extern const uint8_t MESH_SERVICE_UUID_16[], TORADIO_UUID_16[16u], FROMRADIO_UUID_16[], FROMNUM_UUID_16[]; extern const uint8_t MESH_SERVICE_UUID_16[], TORADIO_UUID_16[16u], FROMRADIO_UUID_16[], FROMNUM_UUID_16[], LOGRADIO_UUID_16[];
/// Given a level between 0-100, update the BLE attribute /// Given a level between 0-100, update the BLE attribute
void updateBatteryLevel(uint8_t level); void updateBatteryLevel(uint8_t level);

View File

@@ -29,7 +29,6 @@ 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)
@@ -43,6 +42,8 @@ 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)
this->userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP);
#else #else
this->userButton = OneButton(pin, true, true); this->userButton = OneButton(pin, true, true);
#endif #endif
@@ -51,8 +52,12 @@ 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
pinMode(pin, BUTTON_SENSE_TYPE);
#else
pinMode(pin, INPUT_PULLUP_SENSE); pinMode(pin, INPUT_PULLUP_SENSE);
#endif #endif
#endif
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
userButton.attachClick(userButtonPressed); userButton.attachClick(userButtonPressed);
@@ -139,8 +144,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");
@@ -181,8 +186,9 @@ int32_t ButtonThread::runOnce()
case BUTTON_EVENT_LONG_PRESSED: { case BUTTON_EVENT_LONG_PRESSED: {
LOG_BUTTON("Long press!\n"); LOG_BUTTON("Long press!\n");
powerFSM.trigger(EVENT_PRESS); powerFSM.trigger(EVENT_PRESS);
if (screen) if (screen) {
screen->startShutdownScreen(); screen->startAlert("Shutting down...");
}
playBeep(); playBeep();
break; break;
} }
@@ -218,7 +224,6 @@ 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_TOCH_MS 400 #define BUTTON_TOUCH_MS 400
#endif #endif
class ButtonThread : public concurrency::OSThread class ButtonThread : public concurrency::OSThread

View File

@@ -26,7 +26,21 @@ SOFTWARE.*/
#include "DebugConfiguration.h" #include "DebugConfiguration.h"
#if HAS_WIFI || HAS_ETHERNET #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
Syslog::Syslog(UDP &client) Syslog::Syslog(UDP &client)
{ {
@@ -129,6 +143,11 @@ 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;
@@ -159,7 +178,12 @@ 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(" - - - \xEF\xBB\xBF")); this->_client->print(F(" - - - "));
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("]: "));

View File

@@ -1,9 +1,10 @@
#ifndef SYSLOG_H #pragma once
#define SYSLOG_H
#include "configuration.h"
// DEBUG LED // DEBUG LED
#ifndef LED_INVERTED #ifndef LED_STATE_ON
#define LED_INVERTED 0 // define as 1 if LED is active low (on) #define LED_STATE_ON 1
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -25,6 +26,14 @@
#include "SerialConsole.h" #include "SerialConsole.h"
// If defined we will include support for ARM ICE "semihosting" for a virtual
// console over the JTAG port (to replace the normal serial port)
// Note: Normally this flag is passed into the gcc commandline by platformio.ini.
// for an example see env:rak4631_dap.
// #ifndef USE_SEMIHOSTING
// #define USE_SEMIHOSTING
// #endif
#define DEBUG_PORT (*console) // Serial debug port #define DEBUG_PORT (*console) // Serial debug port
#ifdef USE_SEGGER #ifdef USE_SEGGER
@@ -36,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) #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) && !defined(PIO_UNIT_TESTING)
#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__)
@@ -53,6 +62,9 @@
#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 */
@@ -117,7 +129,7 @@
#include <WiFi.h> #include <WiFi.h>
#endif // HAS_WIFI #endif // HAS_WIFI
#if HAS_WIFI || HAS_ETHERNET #if HAS_NETWORKING
class Syslog class Syslog
{ {
@@ -153,5 +165,3 @@ class Syslog
}; };
#endif // HAS_ETHERNET || HAS_WIFI #endif // HAS_ETHERNET || HAS_WIFI
#endif // SYSLOG_H

View File

@@ -3,6 +3,9 @@
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,6 +24,39 @@ 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.
* *
@@ -33,7 +66,33 @@ SPIClass SPI1(HSPI);
*/ */
bool copyFile(const char *from, const char *to) bool copyFile(const char *from, const char *to)
{ {
#ifdef FSCom #ifdef ARCH_STM32WL
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);
@@ -70,7 +129,13 @@ 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 FSCom #ifdef ARCH_STM32WL
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);
@@ -84,6 +149,58 @@ bool renameFile(const char *pathFrom, const char *pathTo)
#endif #endif
} }
#include <vector>
/**
* @brief Get the list of files in a directory.
*
* This function returns a list of files in a directory. The list includes the full path of each file.
*
* @param dirname The name of the directory.
* @param levels The number of levels of subdirectories to list.
* @return A vector of strings containing the full path of each file in the directory.
*/
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels)
{
std::vector<meshtastic_FileInfo> filenames = {};
#ifdef FSCom
File root = FSCom.open(dirname, FILE_O_READ);
if (!root)
return filenames;
if (!root.isDirectory())
return filenames;
File file = root.openNextFile();
while (file) {
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
if (levels) {
#ifdef ARCH_ESP32
std::vector<meshtastic_FileInfo> subDirFilenames = getFiles(file.path(), levels - 1);
#else
std::vector<meshtastic_FileInfo> subDirFilenames = getFiles(file.name(), levels - 1);
#endif
filenames.insert(filenames.end(), subDirFilenames.begin(), subDirFilenames.end());
file.close();
}
} else {
meshtastic_FileInfo fileInfo = {"", file.size()};
#ifdef ARCH_ESP32
strcpy(fileInfo.file_name, file.path());
#else
strcpy(fileInfo.file_name, file.name());
#endif
if (!String(fileInfo.file_name).endsWith(".")) {
filenames.push_back(fileInfo);
}
file.close();
}
file = root.openNextFile();
}
root.close();
#endif
return filenames;
}
/** /**
* Lists the contents of a directory. * Lists the contents of a directory.
* *
@@ -91,7 +208,7 @@ bool renameFile(const char *pathFrom, const char *pathTo)
* @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 = false) void listDir(const char *dirname, uint8_t levels, bool del)
{ {
#ifdef FSCom #ifdef FSCom
#if (defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)) #if (defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO))
@@ -106,7 +223,9 @@ void listDir(const char *dirname, uint8_t levels, bool del = false)
} }
File file = root.openNextFile(); File file = root.openNextFile();
while (file) { while (
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
@@ -130,6 +249,7 @@ void listDir(const char *dirname, uint8_t levels, bool del = false)
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
@@ -156,7 +276,7 @@ void listDir(const char *dirname, uint8_t levels, bool del = false)
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
} }
@@ -205,62 +325,6 @@ 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
@@ -270,35 +334,6 @@ 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

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "configuration.h" #include "configuration.h"
#include <vector>
// Cross platform filesystem API // Cross platform filesystem API
@@ -14,10 +15,13 @@
#endif #endif
#if defined(ARCH_STM32WL) #if defined(ARCH_STM32WL)
#include "platform/stm32wl/InternalFileSystem.h" // STM32WL version // STM32WL series 2 Kbytes (8 rows of 256 bytes)
#define FSCom InternalFS #include <EEPROM.h>
#define FSBegin() FSCom.begin() #include <OSFS.h>
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)
@@ -47,8 +51,13 @@ 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);
void listDir(const char *dirname, uint8_t levels, bool del); std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
void listDir(const char *dirname, uint8_t levels, bool del = false);
void rmDir(const char *dirname); void 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

@@ -124,7 +124,7 @@ class GPSStatus : public Status
if (isDirty) { if (isDirty) {
if (hasLock) { if (hasLock) {
// In debug logs, identify position by @timestamp:stage (stage 3 = notify) // In debug logs, identify position by @timestamp:stage (stage 3 = notify)
LOG_DEBUG("New GPS pos@%x:3 lat=%f, lon=%f, alt=%d, pdop=%.2f, track=%.2f, speed=%.2f, sats=%d\n", p.timestamp, LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d\n", p.timestamp,
p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5, p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5,
p.ground_speed * 1e-2, p.sats_in_view); p.ground_speed * 1e-2, p.sats_in_view);
} else { } else {

104
src/GpioLogic.cpp Normal file
View File

@@ -0,0 +1,104 @@
#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();
}
}
void GpioHwPin::set(bool value)
{
// if (num == 3) LOG_DEBUG("Setting pin %d to %d\n", num, value);
pinMode(num, OUTPUT);
digitalWrite(num, value);
}
GpioTransformer::GpioTransformer(GpioPin *outPin) : outPin(outPin) {}
void GpioTransformer::set(bool value)
{
outPin->set(value);
}
GpioUnaryTransformer::GpioUnaryTransformer(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 GpioUnaryTransformer::update()
{
auto p = inPin->get();
if (p == GpioVirtPin::PinState::Unset)
return; // Not yet fully initialized
set(p);
}
/**
* 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:
// LOG_DEBUG("Doing GPIO OR\n");
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) {}

160
src/GpioLogic.h Normal file
View File

@@ -0,0 +1,160 @@
#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);
};
class GpioTransformer;
class GpioNotTransformer;
class GpioBinaryTransformer;
/**
* A virtual GPIO pin.
*/
class GpioVirtPin : public GpioPin
{
friend class GpioBinaryTransformer;
friend class GpioUnaryTransformer;
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 just drives a hw pin based on a virtual pin.
*/
class GpioUnaryTransformer : public GpioTransformer
{
public:
GpioUnaryTransformer(GpioVirtPin *inPin, GpioPin *outPin);
protected:
friend class GpioVirtPin;
/**
* Update the output pin based on the current state of the input pin.
*/
virtual void update();
GpioVirtPin *inPin;
};
/**
* A transformer that performs a unary NOT operation from an input.
*/
class GpioNotTransformer : public GpioUnaryTransformer
{
public:
GpioNotTransformer(GpioVirtPin *inPin, GpioPin *outPin) : GpioUnaryTransformer(inPin, outPin) {}
protected:
friend class GpioVirtPin;
/**
* Update the output pin based on the current state of the input pin.
*/
void update();
};
/**
* 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;
};

66
src/Led.cpp Normal file
View File

@@ -0,0 +1,66 @@
#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);

7
src/Led.h Normal file
View File

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

View File

@@ -1,60 +0,0 @@
#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

View File

@@ -1,8 +0,0 @@
#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

@@ -27,7 +27,7 @@
#if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT #if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h" #include "mqtt/MQTT.h"
#include "target_specific.h" #include "target_specific.h"
#if !MESTASTIC_EXCLUDE_WIFI #if HAS_WIFI
#include <WiFi.h> #include <WiFi.h>
#endif #endif
#endif #endif
@@ -80,9 +80,6 @@ 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
@@ -139,6 +136,30 @@ using namespace meshtastic;
*/ */
static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level sensor static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level sensor
static void adcEnable()
{
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
#ifdef ADC_USE_PULLUP
pinMode(ADC_CTRL, INPUT_PULLUP);
#else
pinMode(ADC_CTRL, OUTPUT);
digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED);
#endif
delay(10);
#endif
}
static void adcDisable()
{
#ifdef ADC_CTRL // disable adc voltage divider when we need to read
#ifdef ADC_USE_PULLUP
pinMode(ADC_CTRL, INPUT_PULLDOWN);
#else
digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED);
#endif
#endif
}
/** /**
* A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input * A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input
*/ */
@@ -200,7 +221,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
} }
#endif #endif
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(HAS_PMU) && \
!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();
@@ -228,6 +250,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
uint32_t raw = 0; uint32_t raw = 0;
float scaled = 0; float scaled = 0;
adcEnable();
#ifdef ARCH_ESP32 // ADC block for espressif platforms #ifdef ARCH_ESP32 // ADC block for espressif platforms
raw = espAdcRead(); raw = espAdcRead();
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs); scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
@@ -239,6 +262,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
raw = raw / BATTERY_SENSE_SAMPLES; raw = raw / BATTERY_SENSE_SAMPLES;
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw; scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
#endif #endif
adcDisable();
if (!initial_read_done) { if (!initial_read_done) {
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
@@ -269,11 +293,6 @@ class AnalogBatteryLevel : public HasBatteryLevel
uint8_t raw_c = 0; // raw reading counter uint8_t raw_c = 0; // raw reading counter
#ifndef BAT_MEASURE_ADC_UNIT // ADC1 #ifndef BAT_MEASURE_ADC_UNIT // ADC1
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
pinMode(ADC_CTRL, OUTPUT);
digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED);
delay(10);
#endif
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) { for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
int val_ = adc1_get_raw(adc_channel); int val_ = adc1_get_raw(adc_channel);
if (val_ >= 0) { // save only valid readings if (val_ >= 0) { // save only valid readings
@@ -282,18 +301,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
} }
// delayMicroseconds(100); // delayMicroseconds(100);
} }
#ifdef ADC_CTRL // disable adc voltage divider when we need to read #else // ADC2
digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED);
#endif
#else // ADC2
#ifdef ADC_CTRL
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
pinMode(ADC_CTRL, OUTPUT);
digitalWrite(ADC_CTRL, LOW); // ACTIVE LOW
delay(10);
#endif
#endif // End ADC_CTRL
#ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3 #ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3
// ADC2 wifi bug workaround not required, breaks compile // ADC2 wifi bug workaround not required, breaks compile
// On ESP32S3, ADC2 can take turns with Wifi (?) // On ESP32S3, ADC2 can take turns with Wifi (?)
@@ -328,12 +336,6 @@ class AnalogBatteryLevel : public HasBatteryLevel
} }
#endif // BAT_MEASURE_ADC_UNIT #endif // BAT_MEASURE_ADC_UNIT
#ifdef ADC_CTRL
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
digitalWrite(ADC_CTRL, HIGH);
#endif
#endif // End ADC_CTRL
#endif // End BAT_MEASURE_ADC_UNIT #endif // End BAT_MEASURE_ADC_UNIT
return (raw / (raw_c < 1 ? 1 : raw_c)); return (raw / (raw_c < 1 ? 1 : raw_c));
} }
@@ -412,7 +414,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
} }
#endif #endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
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) {
@@ -441,13 +443,18 @@ class AnalogBatteryLevel : public HasBatteryLevel
if (!ina260Sensor.isInitialized()) if (!ina260Sensor.isInitialized())
return ina260Sensor.runOnce() > 0; return ina260Sensor.runOnce() > 0;
return ina260Sensor.isRunning(); return ina260Sensor.isRunning();
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA3221].first ==
config.power.device_battery_ina_address) {
if (!ina3221Sensor.isInitialized())
return ina3221Sensor.runOnce() > 0;
return ina3221Sensor.isRunning();
} }
return false; return false;
} }
#endif #endif
}; };
AnalogBatteryLevel analogLevel; static AnalogBatteryLevel analogLevel;
Power::Power() : OSThread("Power") Power::Power() : OSThread("Power")
{ {
@@ -546,6 +553,10 @@ 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;
@@ -575,10 +586,16 @@ 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) {
bool hasBattery = batteryLevel->isBatteryConnect(); hasBattery = batteryLevel->isBatteryConnect() ? OptTrue : OptFalse;
uint32_t batteryVoltageMv = 0; usbPowered = batteryLevel->isVbusIn() ? OptTrue : OptFalse;
int8_t batteryChargePercent = 0; isCharging = batteryLevel->isCharging() ? OptTrue : OptFalse;
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
@@ -593,102 +610,90 @@ void Power::readPowerStatus()
0, 100); 0, 100);
} }
} }
}
OptionalBool NRF_USB = OptFalse; // FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass
// (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.
static nrfx_power_usb_state_t prev_nrf_usb_state = (nrfx_power_usb_state_t)-1; // -1 so that state detected at boot nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get(); // LOG_DEBUG("NRF Power %d\n", nrf_usb_state);
// If state changed // If changed to DISCONNECTED
if (nrf_usb_state != prev_nrf_usb_state) { if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED)
// If changed to DISCONNECTED isCharging = usbPowered = OptFalse;
if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) { // If changed to CONNECTED / READY
powerFSM.trigger(EVENT_POWER_DISCONNECTED); else
NRF_USB = OptFalse; isCharging = usbPowered = OptTrue;
}
// 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
const PowerStatus powerStatus2 = PowerStatus( // Notify any status instances that are observing us
hasBattery ? OptTrue : OptFalse, batteryLevel->isVbusIn() || NRF_USB == OptTrue ? OptTrue : OptFalse, const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isCharging, batteryVoltageMv, batteryChargePercent);
batteryLevel->isCharging() || NRF_USB == OptTrue ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent); LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus2.getHasUSB(),
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); newStatus.notifyObservers(&powerStatus2);
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
#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 // 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. // 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 && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
if (batteryLevel->getBattVoltage() < OCV[NUM_OCV_POINTS - 1]) { if (batteryLevel->getBattVoltage() < OCV[NUM_OCV_POINTS - 1]) {
low_voltage_counter++; low_voltage_counter++;
LOG_DEBUG("Low voltage counter: %d/10\n", low_voltage_counter); LOG_DEBUG("Low voltage counter: %d/10\n", low_voltage_counter);
if (low_voltage_counter > 10) { if (low_voltage_counter > 10) {
#ifdef ARCH_NRF52 #ifdef ARCH_NRF52
// We can't trigger deep sleep on NRF52, it's freezing the board // We can't trigger deep sleep on NRF52, it's freezing the board
LOG_DEBUG("Low voltage detected, but not triggering deep sleep\n"); LOG_DEBUG("Low voltage detected, but not triggering deep sleep\n");
#else #else
LOG_INFO("Low voltage detected, triggering deep sleep\n"); LOG_INFO("Low voltage detected, triggering deep sleep\n");
powerFSM.trigger(EVENT_LOW_BATTERY); powerFSM.trigger(EVENT_LOW_BATTERY);
#endif #endif
}
} else {
low_voltage_counter = 0;
} }
} 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);
} }
} }

View File

@@ -9,8 +9,10 @@
*/ */
#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 "configuration.h" #include "configuration.h"
#include "graphics/Screen.h" #include "graphics/Screen.h"
#include "main.h" #include "main.h"
@@ -20,12 +22,15 @@
#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) #if !defined(BATTERY_PIN) && !defined(HAS_AXP192) && !defined(HAS_AXP2101) && !defined(NRF_APM)
return true; return true;
#endif #endif
@@ -87,14 +92,16 @@ static void lsIdle()
// Briefly come out of sleep long enough to blink the led once every few seconds // Briefly come out of sleep long enough to blink the led once every few seconds
uint32_t sleepTime = SLEEP_TIME; uint32_t sleepTime = SLEEP_TIME;
setLed(false); // Never leave led on while in light sleep powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep);
ledBlink.set(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);
switch (wakeCause2) { switch (wakeCause2) {
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
setLed(true); // briefly turn on led ledBlink.set(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;
@@ -129,7 +136,7 @@ static void lsIdle()
} }
} else { } else {
// Time to stop sleeping! // Time to stop sleeping!
setLed(false); ledBlink.set(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);
} }
@@ -392,3 +399,4 @@ void PowerFSM_setup()
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 <Fsm.h> #include "configuration.h"
// See sw-design.md for documentation // See sw-design.md for documentation
@@ -22,7 +22,30 @@
#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,6 +18,7 @@ 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
@@ -35,6 +36,9 @@ class PowerFSMThread : public OSThread
} }
return 100; return 100;
#else
return INT32_MAX;
#endif
} }
}; };

47
src/PowerMon.cpp Normal file
View File

@@ -0,0 +1,47 @@
#include "PowerMon.h"
#include "NodeDB.h"
// Use the 'live' config flag to figure out if we should be showing this message
bool PowerMon::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
// 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)
{
#ifdef USE_POWERMON
auto oldstates = states;
states |= state;
if (oldstates != states && is_power_enabled(state)) {
emitLog(reason);
}
#endif
}
void PowerMon::clearState(_meshtastic_PowerMon_State state, const char *reason)
{
#ifdef USE_POWERMON
auto oldstates = states;
states &= ~state;
if (oldstates != states && is_power_enabled(state)) {
emitLog(reason);
}
#endif
}
void PowerMon::emitLog(const char *reason)
{
#ifdef USE_POWERMON
// The nrf52 printf doesn't understand 64 bit ints, so if we ever reach that point this function will need to change.
LOG_INFO("S:PM:0x%08lx,%s\n", (uint32_t)states, reason);
#endif
}
PowerMon *powerMon;
void powerMonInit()
{
powerMon = new PowerMon();
}

44
src/PowerMon.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include "configuration.h"
#include "meshtastic/powermon.pb.h"
#ifndef MESHTASTIC_EXCLUDE_POWERMON
#define USE_POWERMON // FIXME turn this only for certain builds
#endif
/**
* The singleton class for monitoring power consumption of device
* subsystems/modes.
*
* For more information see the PowerMon docs.
*/
class PowerMon
{
uint64_t states = 0UL;
friend class PowerStressModule;
/**
* If stress testing we always want all events logged
*/
bool force_enabled = false;
public:
PowerMon() {}
// Mark entry/exit of a power consuming state
void setState(_meshtastic_PowerMon_State state, const char *reason = "");
void clearState(_meshtastic_PowerMon_State state, const char *reason = "");
private:
// Emit the coded log message
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;
void powerMonInit();

View File

@@ -3,5 +3,4 @@
#define RF95_RESET LORA_RESET #define RF95_RESET LORA_RESET
#define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0 #define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0
#define RF95_DIO1 LORA_DIO1 // Note: not really used for RF95, but used for pure SX127x #define RF95_DIO1 LORA_DIO1 // Note: not really used for RF95, but used for pure SX127x
#define RF95_DIO2 LORA_DIO2 // Note: not really used for RF95
#endif #endif

View File

@@ -3,6 +3,8 @@
#include "RTC.h" #include "RTC.h"
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#include "configuration.h" #include "configuration.h"
#include "main.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include <assert.h> #include <assert.h>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
@@ -14,12 +16,7 @@
#include "platform/portduino/PortduinoGlue.h" #include "platform/portduino/PortduinoGlue.h"
#endif #endif
/** #if HAS_NETWORKING
* A printer that doesn't go anywhere
*/
NoopPrint noopPrint;
#if HAS_WIFI || HAS_ETHERNET
extern Syslog syslog; extern Syslog syslog;
#endif #endif
void RedirectablePrint::rpInit() void RedirectablePrint::rpInit()
@@ -38,21 +35,32 @@ void RedirectablePrint::setDestination(Print *_dest)
size_t RedirectablePrint::write(uint8_t c) size_t RedirectablePrint::write(uint8_t c)
{ {
// Always send the characters to our segger JTAG debugger // Always send the characters to our segger JTAG debugger
#ifdef SEGGER_STDOUT_CH #ifdef USE_SEGGER
SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c); SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c);
#endif #endif
// Account for legacy config transition
if (!config.has_lora || config.device.serial_enabled) bool serialEnabled = config.has_security ? config.security.serial_enabled : 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
// serial port said (which could be zero) // serial port said (which could be zero)
} }
size_t RedirectablePrint::vprintf(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);
@@ -65,25 +73,242 @@ size_t RedirectablePrint::vprintf(const char *format, va_list arg)
len = sizeof(printBuf) - 1; len = sizeof(printBuf) - 1;
printBuf[sizeof(printBuf) - 2] = '\n'; printBuf[sizeof(printBuf) - 2] = '\n';
} }
for (size_t f = 0; f < len; f++) {
if (!std::isprint(static_cast<unsigned char>(printBuf[f])) && printBuf[f] != '\n')
printBuf[f] = '#';
}
if (color && logLevel != nullptr) {
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
Print::write("\u001b[34m", 6);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
Print::write("\u001b[32m", 6);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
Print::write("\u001b[33m", 6);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
Print::write("\u001b[31m", 6);
}
len = Print::write(printBuf, len); len = Print::write(printBuf, len);
if (color && logLevel != nullptr) {
Print::write("\u001b[0m", 5);
}
return len; return len;
} }
size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, va_list arg)
{ {
size_t r = 0;
// Cope with 0 len format strings, but look for new line terminator
bool hasNewline = *format && format[strlen(format) - 1] == '\n';
#ifdef ARCH_PORTDUINO #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 (!isContinuationMessage) {
if (color) {
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
Print::write("\u001b[34m", 6);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
Print::write("\u001b[32m", 6);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
Print::write("\u001b[33m", 6);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
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
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
#ifdef ARCH_PORTDUINO
::printf("%s ", logLevel);
if (color) {
::printf("\u001b[0m");
}
::printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
#else
printf("%s ", logLevel);
if (color) {
printf("\u001b[0m");
}
printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
#endif
} else {
#ifdef ARCH_PORTDUINO
::printf("%s ", logLevel);
if (color) {
::printf("\u001b[0m");
}
::printf("| ??:??:?? %u ", millis() / 1000);
#else
printf("%s ", logLevel);
if (color) {
printf("\u001b[0m");
}
printf("| ??:??:?? %u ", millis() / 1000);
#endif
}
auto thread = concurrency::OSThread::currentThread;
if (thread) {
print("[");
// printf("%p ", thread);
// assert(thread->ThreadName.length());
print(thread->ThreadName);
print("] ");
}
}
r += vprintf(logLevel, format, arg);
isContinuationMessage = !hasNewline;
}
void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, va_list arg)
{
#if HAS_NETWORKING && !defined(ARCH_PORTDUINO)
// if syslog is in use, collect the log messages and send them to syslog
if (syslog.isEnabled()) {
int ll = 0;
switch (logLevel[0]) {
case 'D':
ll = SYSLOG_DEBUG;
break;
case 'I':
ll = SYSLOG_INFO;
break;
case 'W':
ll = SYSLOG_WARN;
break;
case 'E':
ll = SYSLOG_ERR;
break;
case 'C':
ll = SYSLOG_CRIT;
break;
default:
ll = 0;
}
auto thread = concurrency::OSThread::currentThread;
if (thread) {
syslog.vlogf(ll, thread->ThreadName.c_str(), format, arg);
} else {
syslog.vlogf(ll, format, arg);
}
}
#endif
}
void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg)
{
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
if (config.security.debug_log_api_enabled && !pauseBluetoothLogging) {
bool isBleConnected = false;
#ifdef ARCH_ESP32
isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected();
#elif defined(ARCH_NRF52)
isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected();
#endif
if (isBleConnected) {
char *message;
size_t initialLen;
size_t len;
initialLen = strlen(format);
message = new char[initialLen + 1];
len = vsnprintf(message, initialLen + 1, format, arg);
if (len > initialLen) {
delete[] message;
message = new char[len + 1];
vsnprintf(message, len + 1, format, arg);
}
auto thread = concurrency::OSThread::currentThread;
meshtastic_LogRecord logRecord = meshtastic_LogRecord_init_zero;
logRecord.level = getLogLevel(logLevel);
strcpy(logRecord.message, message);
if (thread)
strcpy(logRecord.source, thread->ThreadName.c_str());
logRecord.time = getValidTime(RTCQuality::RTCQualityDevice, true);
uint8_t *buffer = new uint8_t[meshtastic_LogRecord_size];
size_t size = pb_encode_to_bytes(buffer, meshtastic_LogRecord_size, meshtastic_LogRecord_fields, &logRecord);
#ifdef ARCH_ESP32
nimbleBluetooth->sendLog(buffer, size);
#elif defined(ARCH_NRF52)
nrf52Bluetooth->sendLog(buffer, size);
#endif
delete[] message;
delete[] buffer;
}
}
#else
(void)logLevel;
(void)format;
(void)arg;
#endif
}
meshtastic_LogRecord_Level RedirectablePrint::getLogLevel(const char *logLevel)
{
meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset
switch (logLevel[0]) {
case 'D':
ll = meshtastic_LogRecord_Level_DEBUG;
break;
case 'I':
ll = meshtastic_LogRecord_Level_INFO;
break;
case 'W':
ll = meshtastic_LogRecord_Level_WARNING;
break;
case 'E':
ll = meshtastic_LogRecord_Level_ERROR;
break;
case 'C':
ll = meshtastic_LogRecord_Level_CRITICAL;
break;
}
return ll;
}
void RedirectablePrint::log(const char *logLevel, const char *format, ...)
{
#if 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 0; 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)
return 0; return;
else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
return 0; return;
#endif #endif
if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
return 0; return;
} }
size_t r = 0;
#ifdef HAS_FREE_RTOS #ifdef HAS_FREE_RTOS
if (inDebugPrint != nullptr && xSemaphoreTake(inDebugPrint, portMAX_DELAY) == pdTRUE) { if (inDebugPrint != nullptr && xSemaphoreTake(inDebugPrint, portMAX_DELAY) == pdTRUE) {
#else #else
@@ -94,81 +319,11 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
va_list arg; va_list arg;
va_start(arg, format); va_start(arg, format);
// Cope with 0 len format strings, but look for new line terminator log_to_serial(logLevel, format, arg);
bool hasNewline = *format && format[strlen(format) - 1] == '\n'; log_to_syslog(logLevel, format, arg);
log_to_ble(logLevel, format, arg);
// If we are the first message on a report, include the header
if (!isContinuationMessage) {
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
#ifdef ARCH_PORTDUINO
r += ::printf("%s | %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000);
#else
r += printf("%s | %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000);
#endif
} else
#ifdef ARCH_PORTDUINO
r += ::printf("%s | ??:??:?? %u ", logLevel, millis() / 1000);
#else
r += printf("%s | ??:??:?? %u ", logLevel, millis() / 1000);
#endif
auto thread = concurrency::OSThread::currentThread;
if (thread) {
print("[");
// printf("%p ", thread);
// assert(thread->ThreadName.length());
print(thread->ThreadName);
print("] ");
}
}
r += vprintf(format, arg);
#if (HAS_WIFI || HAS_ETHERNET) && !defined(ARCH_PORTDUINO)
// if syslog is in use, collect the log messages and send them to syslog
if (syslog.isEnabled()) {
int ll = 0;
switch (logLevel[0]) {
case 'D':
ll = SYSLOG_DEBUG;
break;
case 'I':
ll = SYSLOG_INFO;
break;
case 'W':
ll = SYSLOG_WARN;
break;
case 'E':
ll = SYSLOG_ERR;
break;
case 'C':
ll = SYSLOG_CRIT;
break;
default:
ll = 0;
}
auto thread = concurrency::OSThread::currentThread;
if (thread) {
syslog.vlogf(ll, thread->ThreadName.c_str(), format, arg);
} else {
syslog.vlogf(ll, format, arg);
}
}
#endif
va_end(arg); va_end(arg);
isContinuationMessage = !hasNewline;
#ifdef HAS_FREE_RTOS #ifdef HAS_FREE_RTOS
xSemaphoreGive(inDebugPrint); xSemaphoreGive(inDebugPrint);
#else #else
@@ -176,7 +331,7 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
#endif #endif
} }
return r; return;
} }
void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len) void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len)

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "../freertosinc.h" #include "../freertosinc.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include <Print.h> #include <Print.h>
#include <stdarg.h> #include <stdarg.h>
#include <string> #include <string>
@@ -41,23 +42,21 @@ class RedirectablePrint : public Print
* log message. Otherwise we assume more prints will come before the log message ends. This * log message. Otherwise we assume more prints will come before the log message ends. This
* allows you to call logDebug a few times to build up a single log message line if you wish. * allows you to call logDebug a few times to build up a single log message line if you wish.
*/ */
size_t log(const char *logLevel, const char *format, ...) __attribute__((format(printf, 3, 4))); void log(const char *logLevel, const char *format, ...) __attribute__((format(printf, 3, 4)));
/** like printf but va_list based */ /** like printf but va_list based */
size_t vprintf(const char *format, va_list arg); size_t vprintf(const char *logLevel, const char *format, va_list arg);
void hexDump(const char *logLevel, unsigned char *buf, uint16_t len); void hexDump(const char *logLevel, unsigned char *buf, uint16_t len);
std::string mt_sprintf(const std::string fmt_str, ...); std::string mt_sprintf(const std::string fmt_str, ...);
};
class NoopPrint : public Print protected:
{ /// Subclasses can override if they need to change how we format over the serial port
public: virtual void log_to_serial(const char *logLevel, const char *format, va_list arg);
virtual size_t write(uint8_t c) { return 1; }
};
/** private:
* A printer that doesn't go anywhere void log_to_syslog(const char *logLevel, const char *format, va_list arg);
*/ void log_to_ble(const char *logLevel, const char *format, va_list arg);
extern NoopPrint noopPrint; meshtastic_LogRecord_Level getLogLevel(const char *logLevel);
};

105
src/SafeFile.cpp Normal file
View File

@@ -0,0 +1,105 @@
#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

49
src/SafeFile.h Normal file
View File

@@ -0,0 +1,49 @@
#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

@@ -7,8 +7,12 @@
#ifdef RP2040_SLOW_CLOCK #ifdef RP2040_SLOW_CLOCK
#define Port Serial2 #define Port Serial2
#else #else
#ifdef USER_DEBUG_PORT // change by WayenWeng
#define Port USER_DEBUG_PORT
#else
#define Port Serial #define Port Serial
#endif #endif
#endif
// Defaulting to the formerly removed phone_timeout_secs value of 15 minutes // Defaulting to the formerly removed phone_timeout_secs value of 15 minutes
#define SERIAL_CONNECTION_TIMEOUT (15 * 60) * 1000UL #define SERIAL_CONNECTION_TIMEOUT (15 * 60) * 1000UL
@@ -24,7 +28,7 @@ void consolePrintf(const char *format, ...)
{ {
va_list arg; va_list arg;
va_start(arg, format); va_start(arg, format);
console->vprintf(format, arg); console->vprintf(nullptr, format, arg);
va_end(arg); va_end(arg);
console->flush(); console->flush();
} }
@@ -34,7 +38,6 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
assert(!console); assert(!console);
console = this; console = this;
canWrite = false; // We don't send packets to our port until it has talked to us first canWrite = false; // We don't send packets to our port until it has talked to us first
// setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks
#ifdef RP2040_SLOW_CLOCK #ifdef RP2040_SLOW_CLOCK
Port.setTX(SERIAL2_TX); Port.setTX(SERIAL2_TX);
@@ -80,10 +83,9 @@ 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.device.serial_enabled) { if (config.has_lora && config.security.serial_enabled) {
// Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets // Switch to protobufs for log messages
if (!config.device.debug_log_enabled) usingProtobufs = true;
setDestination(&noopPrint);
canWrite = true; canWrite = true;
return StreamAPI::handleToRadio(buf, len); return StreamAPI::handleToRadio(buf, len);
@@ -91,3 +93,31 @@ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
return false; return false;
} }
} }
void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg)
{
if (usingProtobufs && config.security.debug_log_api_enabled) {
meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset
switch (logLevel[0]) {
case 'D':
ll = meshtastic_LogRecord_Level_DEBUG;
break;
case 'I':
ll = meshtastic_LogRecord_Level_INFO;
break;
case 'W':
ll = meshtastic_LogRecord_Level_WARNING;
break;
case 'E':
ll = meshtastic_LogRecord_Level_ERROR;
break;
case 'C':
ll = meshtastic_LogRecord_Level_CRITICAL;
break;
}
auto thread = concurrency::OSThread::currentThread;
emitLogRecord(ll, thread ? thread->ThreadName.c_str() : "", format, arg);
} else
RedirectablePrint::log_to_serial(logLevel, format, arg);
}

View File

@@ -8,6 +8,11 @@
*/ */
class SerialConsole : public StreamAPI, public RedirectablePrint, private concurrency::OSThread class SerialConsole : public StreamAPI, public RedirectablePrint, private concurrency::OSThread
{ {
/**
* If true we are talking to a smart host and all messages (including log messages) must be framed as protobufs.
*/
bool usingProtobufs = false;
public: public:
SerialConsole(); SerialConsole();
@@ -31,6 +36,9 @@ class SerialConsole : public StreamAPI, public RedirectablePrint, private concur
protected: protected:
/// Check the current underlying physical link to see if the client is currently connected /// Check the current underlying physical link to see if the client is currently connected
virtual bool checkIsConnected() override; virtual bool checkIsConnected() override;
/// Possibly switch to protobufs if we see a valid protobuf message
virtual void log_to_serial(const char *logLevel, const char *format, va_list arg);
}; };
// A simple wrapper to allow non class aware code write to the console // A simple wrapper to allow non class aware code write to the console

View File

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