Compare commits

...

41 Commits

Author SHA1 Message Date
Tom Fifield
deb7c274c4 Cleanup NRF s140 Softdevice variants (#4252)
Note: This idea is originally from @caveman99 and should be
credited as such. Submitting as a separate PR so the work in
meshtastic/firmware#4148 can be a bit cleaner and Seeed boards
can build while that work is ongoing.

The nrf52 boards that depend on the v7 softdevice all use the same
code and linker files. Rather than duplicate the code, keep it
all together with the platform.
2024-07-08 06:02:05 -05:00
Mark Trevor Birss
e1bf4c32f3 Update to SoftDevice 7.3.0 for wio-sdk-wm1110 and wio-tracker-wm1110 (#4248)
* Update variant.h

* Update wio-tracker-wm1110.json

* Update wio-sdk-wm1110.json

* Update platformio.ini

* Update platformio.ini

* Add files via upload

* Add files via upload

* Update variant.h
2024-07-07 12:14:18 -05:00
GUVWAF
86ca81b555 If toPhoneQueue is full, still increment fromNum to avoid client never getting packets (#4246) 2024-07-07 09:06:42 -05:00
geeksville
f59d98482f Fix build when HAS_NETWORKING is false on nrf52 (#4237)
(tested on a rak4631 by setting HAS_ETHERNET false when shrinking
image)
2024-07-07 07:08:49 -05:00
geeksville
27dfe10689 Fix BLE logging on nrf52 (#4244)
* allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types

This allows 'lossless' log reading.  If client has requested INDICATE
(rather than NOTIFY) each log record emitted via log() will have to fetched
by the client device before the meshtastic node can continue.

* Fix serious problem with nrf52 BLE logging.
When doing notifies of LogRecords it is important to use the
binary write routines - writing using the 'string' write won't work.
Because protobufs can contain \0 nuls inside of them which if being
parsed as a string will cause only a portion of the protobuf to be sent.
I noticed this because some log messages were not getting through.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-07-07 06:50:47 -05:00
Agent Blu, 006
7b838d388d Updated raspbian CI to update apt repository ahead of libbluetooth. (#4243) 2024-07-06 19:45:58 -05:00
todd-herbert
c3d3dfa8c8 Tidy Wireless Paper variant files (#4238)
* Quick tidy of pins_arduino.h
Matches requests made at https://github.com/meshtastic/firmware/pull/4226#discussion_r1664183480)

* Tidy variant.h

* Change deprecated ADC attenuation parameter
From 11dB to 12dB. Resolves compiler warning. Allegly, no impact on function: `This is deprecated, it behaves the same as `ADC_ATTEN_DB_12`
2024-07-06 12:41:29 -05:00
Tom Fifield
8be378c227 fix typo in build-nrf52.sh (#4231)
chmod is the command, '+x' is the argument.
2024-07-05 09:03:45 -05:00
Manuel
ae420dcd21 Fix exclude macros (#4233)
* fix MESHTASTIC_EXCLUDE_BLUETOOTH

* fix HAS_SCREEN=0

* fix MESHTASTIC_EXCLUDE_GPS
2024-07-05 08:58:16 -05:00
Ben Meadors
c1df621711 Sudo 2024-07-04 08:32:59 -05:00
Ben Meadors
2ba88d305f Only sdk 2024-07-04 08:29:49 -05:00
Ben Meadors
fc63d956e7 Merge hex for wm1110 target(s) 2024-07-04 08:10:40 -05:00
Ben Meadors
4b82634d1a Cleanup buffer 2024-07-03 22:19:01 -05:00
geeksville
8bca3e168d Add PowerMon support (#4155)
* Turn off vscode cmake prompt - we don't use cmake on meshtastic

* Add rak4631_dap variant for debugging with NanoDAP debug probe device.

* The rak device can also run freertos (which is underneath nrf52 arduino)

* Add semihosting support for nrf52840 devices
Initial platformio.ini file only supports rak4630
Default to non TCP for the semihosting log output for now...
Fixes https://github.com/meshtastic/firmware/issues/4135

* powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 )

* oops - mean't to mark the _dbg variant as an 'extra' board.

* powermon wip

* Make serial port on wio-sdk-wm1110 board work
By disabling the (inaccessible) adafruit USB

* Instrument (radiolib only for now) lora for powermon
per https://github.com/meshtastic/firmware/issues/4136

* powermon gps support
https://github.com/meshtastic/firmware/issues/4136

* Add CPU deep and light sleep powermon states
https://github.com/meshtastic/firmware/issues/4136

* Change the board/swversion bootstring so it is a new "structured" log msg.

* powermon wip

* add example script for getting esp S3 debugging working
Not yet used but I didn't want these nasty tricks to get lost yet.

* Add PowerMon reporting for screen and bluetooth pwr.

* make power.powermon_enables config setting work.

* update to latest protobufs

* fix bogus shellcheck warning

* make powermon optional (but default enabled because tiny and no runtime impact)

* tell vscode, if formatting, use whatever our trunk formatter wants
without this flag if the user has set some other formatter (clang)
in their user level settings, it will be looking in the wrong directory
for the clang options (we want the options in .trunk/clang)

Note: formatOnSave is true in master, which means a bunch of our older
files are non compliant and if you edit them it will generate lots of
formatting related diffs.  I guess I'll start letting that happen with
my future commits ;-).

* add PowerStress module

* nrf52 arduino is built upon freertos, so let platformio debug it

* don't accidentally try to Segger ICE if we are using another ICE

* clean up RedirectablePrint::log so it doesn't have three very different implementations inline.

* remove NoopPrint - it is no longer needed

* when talking to API clients via serial, don't turn off log msgs instead encapsuate them

* fix the build - would loop forever if there were no files to send

* don't use Segger code if not talking to a Segger debugger

* when encapsulating logs, make sure the strings always has nul terminators

* nrf52 soft device will watchdog if you use ICE while BT on...
so have debugger disable bluetooth.

* Important to not print debug messages while writing to the toPhone scratch buffer

* don't include newlines if encapsulating log records as protobufs

* update to latest protobufs (needed for powermon goo)

* PowerStress WIP

* fix linter warning
2024-07-03 18:02:20 -05:00
geeksville
8785adf6e4 minor cleanup proposal (#4169)
* MESHTASTIC_EXCLUDE_WIFI and HAS_WIFI cleanup...
Our code was checking HAS_WIFI and the new MESHTASTIC_EXCLUDE_WIFI
flags in various places (even though EXCLUDE_WIFI forces HAS_WIFI
to 0).  Instead just check HAS_WIFI, only use EXCLUDE_WIFI inside
configuration.h

* cleanup: use HAS_NETWORKING instead of HAS_WIFI || HAS_ETHERNET
We already had HAS_NETWORKING as flag in MQTT to mean 'we have
tcpip'.  Generallize that and move it into configuration.h so that
we can use it elsewhere.

* Use #pragma once, because supported by gcc and all modern compilers
instead of #ifdef DOTHFILE_H etc...

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2024-07-03 17:39:09 -05:00
Ben Meadors
9c46bdad1a New new BLE logging characteristic with LogRecord protos (#4220)
* New UUID

* New log radio characteristic with LogRecord protobuf

* LogRecord

* Merge derp

* How did you get there

* Trunk

* Fix length

* Remove assert
2024-07-03 16:29:07 -05:00
Tom Fifield
10b157a38d Typo fix in logs - mhz - MHz (#4225)
As reported by karamo, a few different places in our logs had
incorrect capitalization of MHz.

fixes meshtastic/firmware#4126
2024-07-03 09:04:39 -05:00
Tom Fifield
e65c309af6 Fix SHT41 support (#4222)
* Add SHT41 Serial to I2c Detection Code

On the Seeed Wio-WM1110 Dev Kit board, the SHT41 chip was being
incorrectly detected as SHT31.

This patch adds the necessary serial number for the SHT41 chip to
be correctly detected.

fixes meshtastic/firmware#4221

* Add missing sensor read for SHT41
2024-07-02 07:03:51 -05:00
github-actions[bot]
9701f35a83 [create-pull-request] automated change (#4218)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2024-07-01 06:29:44 -05:00
geeksville
3219d65387 When talking via serial, encapsulate log messages in protobufs if necessary (#4187)
* clean up RedirectablePrint::log so it doesn't have three very different implementations inline.

* remove NoopPrint - it is no longer needed

* when talking to API clients via serial, don't turn off log msgs instead encapsuate them

* fix the build - would loop forever if there were no files to send

* don't use Segger code if not talking to a Segger debugger

* when encapsulating logs, make sure the strings always has nul terminators

* nrf52 soft device will watchdog if you use ICE while BT on...
so have debugger disable bluetooth.

* Important to not print debug messages while writing to the toPhone scratch buffer

* don't include newlines if encapsulating log records as protobufs

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-06-30 18:41:27 -05:00
Manuel
8177329eac enable colors in platformio serial monitor (#4217) 2024-06-30 16:01:28 -05:00
Ben Meadors
469ae0ff84 Fix flakey phone api transition from file manifest to complete (#4209)
* Try fix flakey phone api transition from file manifest to complete

* Skip
2024-06-30 08:22:24 -05:00
Jonathan Bennett
b5d7718319 Move waypoint (#4202)
* Move waypoint screen draw into the waypoint module

* Get the observer set up for the waypoint screen draw

* Static squashing: screen dimensions
Macros moved back to Screen.cpp, as a band-aid until we eventually move all those static functions into the Screen class.

* Move getCompassDiam into Screen class
(supress compiler warnings)
At this stage, the method is still static, because it's used by drawNodeInfo, which has no tidy reference to our screen instance.
This is probably just another band-aid until these static functions all move.

* Use new getCompassDiam function in AccelerometerThread

* Properly gate display code in WaypointModule

---------

Co-authored-by: Todd Herbert <herbert.todd@gmail.com>
2024-06-29 21:16:07 -05:00
github-actions[bot]
47a94d7a07 [create-pull-request] automated change (#4205)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2024-06-29 19:04:08 -05:00
Ben Meadors
20c1d71214 Deprecate Router Client role (and make it Client) (#4201) 2024-06-29 19:03:00 -05:00
Jonathan Bennett
6f3d7ca4d2 Trim extra vprintf and filter for unprintable characters 2024-06-28 23:30:39 -05:00
Jonathan Bennett
ca969e26a5 Squash needlessly static functions (#4183) 2024-06-28 21:28:18 -05:00
Jonathan Bennett
5263c738f3 Make the logs Colorful! (#4199) 2024-06-28 20:10:41 -05:00
github-actions[bot]
9c232da00f [create-pull-request] automated change (#4200)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2024-06-28 18:46:44 -05:00
Ben Meadors
0016e747e9 Clear vector after complete config state (#4194)
* Clear after complete config

* Don't collect . entries

* Log file name and size
2024-06-28 09:50:22 -05:00
geeksville
ce58a23f9b Force a compile time failur if FromRadio or ToRadio get larger than (#4190)
a BLE packet size. We are actually very close to this threshold so
important to make sure we don't accidentally pass it.
2024-06-28 06:51:04 -05:00
quimnut
c95b2c2d3c correct xiao_ble build preventing sx1262 init (#4191) 2024-06-28 06:49:38 -05:00
geeksville
f86a0e5228 nrf52 soft device will watchdog if you use ICE while BT on... (#4189)
so have debugger disable bluetooth.
2024-06-28 06:48:55 -05:00
Alexander
51f3ce5e60 Show owner.short_name on boot (and E-Ink sleep screen) (#4134)
* Show owner.short_name on boot and sleep screen (on e-ink)

* Update Screen.cpp - new line for short_name

Boot screen short_name now below the region setting.
Looks better on small screens.

* Draw short_name on right

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: todd-herbert <herbert.todd@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-06-28 18:55:54 +12:00
geeksville
41d633bfd8 fix the build - would loop forever if there were no files to send (#4188) 2024-06-27 20:43:08 -05:00
geeksville
2cb6e7bd37 tell vscode, if formatting, use whatever our trunk formatter wants (#4186)
without this flag if the user has set some other formatter (clang)
in their user level settings, it will be looking in the wrong directory
for the clang options (we want the options in .trunk/clang)

Note: formatOnSave is true in master, which means a bunch of our older
files are non compliant and if you edit them it will generate lots of
formatting related diffs.  I guess I'll start letting that happen with
my future commits ;-).
2024-06-27 13:14:16 -05:00
Ben Meadors
a966d84e3d Send file system manifest up on want_config (#4176)
* Send file system manifest up on want_config

* Platform specific methods

* Helps to actually make the change

* Clear
2024-06-27 07:07:27 -05:00
Jonathan Bennett
0425551341 Display alerts (#4170)
* Move static functions into Screen.h, show compass during calibration

* Move to _fontHeight macro to avoid collision

* Move some alert functions to new alert handler

* Catch missed reboot code

* ESP32 fixes

* Bump esp8266-oled-ssd1306

* Fixes for when a device has no screen

* Use new startAlert(char*) helper class

* Add EINK bits back to alert handling

* Add noop class for no-display devices

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-06-25 11:26:02 -05:00
github-actions[bot]
626aa762df [create-pull-request] automated change (#4174)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2024-06-24 20:27:00 -05:00
geeksville
aa12e28568 Add semihosting support for nrf52 devices (#4137)
* Turn off vscode cmake prompt - we don't use cmake on meshtastic

* Add rak4631_dap variant for debugging with NanoDAP debug probe device.

* The rak device can also run freertos (which is underneath nrf52 arduino)

* Add semihosting support for nrf52840 devices
Initial platformio.ini file only supports rak4630
Default to non TCP for the semihosting log output for now...
Fixes https://github.com/meshtastic/firmware/issues/4135

* fix my botched merge - keep board_level = extra flag for rak3631_dbg

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-06-24 10:27:37 -05:00
github-actions[bot]
58c00d0447 [create-pull-request] automated change (#4171)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2024-06-24 08:01:40 -05:00
146 changed files with 29359 additions and 828 deletions

View File

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

View File

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

View File

@@ -2,8 +2,8 @@
set -e
VERSION=`bin/buildinfo.py long`
SHORT_VERSION=`bin/buildinfo.py short`
VERSION=$(bin/buildinfo.py long)
SHORT_VERSION=$(bin/buildinfo.py short)
OUTDIR=release/
@@ -11,7 +11,7 @@ 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
platformio pkg update
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*
@@ -29,6 +29,15 @@ cp $DFUPKG $OUTDIR/$basename-ota.zip
echo "Generating NRF52 uf2 file"
SRCHEX=.pio/build/$1/firmware.hex
# if WM1110 target, merge hex with softdevice 7.3.0
if (echo $1 | grep -q "wio-sdk-wm1110"); then
echo "Merging with softdevice"
sudo chmod +x ./bin/mergehex
bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/merged_fimware.hex
SRCHEX=.pio/build/$1/merged_fimware.hex
fi
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840
cp bin/device-install.* $OUTDIR

BIN
bin/mergehex Normal file

Binary file not shown.

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,7 +1,7 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
"ldscript": "nrf52840_s140_v7.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
@@ -15,8 +15,8 @@
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
"sd_version": "7.3.0",
"sd_fwid": "0x0123"
},
"bootloader": {
"settings_addr": "0xFF000"
@@ -27,7 +27,7 @@
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"frameworks": ["arduino", "freertos"],
"name": "Seeed WIO WM1110",
"upload": {
"maximum_ram_size": 248832,

View File

@@ -1,7 +1,7 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
"ldscript": "nrf52840_s140_v7.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
@@ -22,8 +22,8 @@
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
"sd_version": "7.3.0",
"sd_fwid": "0x0123"
},
"bootloader": {
"settings_addr": "0xFF000"
@@ -53,6 +53,6 @@
"require_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"
}

View File

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

View File

@@ -77,10 +77,11 @@ build_flags = -Wno-missing-field-initializers
-DMESHTASTIC_EXCLUDE_DROPZONE=1
monitor_speed = 115200
monitor_filters = direct
lib_deps =
jgromes/RadioLib@~6.6.0
https://github.com/meshtastic/esp8266-oled-ssd1306.git#2b40affbe7f7dc63b6c00fa88e7e12ed1f8e1719 ; ESP8266_SSD1306
https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306
mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4
@@ -150,5 +151,4 @@ lib_deps =
mprograms/QMC5883LCompass@^1.2.0
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>
#ifdef RAK_4631
#include "Fusion/Fusion.h"
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
#include <Rak_BMX160.h>
#endif
@@ -101,7 +103,11 @@ class AccelerometerThread : public concurrency::OSThread
bmx160.getAllData(&magAccel, NULL, &gAccel);
// 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)
highestX = magAccel.x;
if (magAccel.x < lowestX)
@@ -114,6 +120,9 @@ class AccelerometerThread : public concurrency::OSThread
highestZ = magAccel.z;
if (magAccel.z < lowestZ)
lowestZ = magAccel.z;
} else if (showingScreen && millis() >= 30 * 1000) {
showingScreen = false;
screen->endAlert();
}
int highestRealX = highestX - (highestX + lowestX) / 2;
@@ -255,11 +264,34 @@ class AccelerometerThread : public concurrency::OSThread
Adafruit_LIS3DH lis;
Adafruit_LSM6DS3TRC lsm;
SensorBMA423 bmaSensor;
bool BMA_IRQ = false;
#ifdef RAK_4631
bool showingScreen = false;
RAK_BMX160 bmx160;
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
bool BMA_IRQ = false;
};
#endif

View File

@@ -11,5 +11,7 @@ const uint8_t FROMRADIO_UUID_16[16u] = {0x02, 0x00, 0x12, 0xac, 0x42, 0x02, 0x78
0xed, 0x11, 0x93, 0x49, 0x9e, 0xe6, 0x55, 0x2c};
const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6,
0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed};
const uint8_t LOGRADIO_UUID_16[16u] = {0xe2, 0xf2, 0x1e, 0xbe, 0xc5, 0x15, 0xcf, 0xaa,
0x6b, 0x43, 0xfa, 0x78, 0x38, 0xd2, 0x6f, 0x6c};
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,7 +11,8 @@
#define TORADIO_UUID "f75c76d2-129e-4dad-a1dd-7866124401e7"
#define FROMRADIO_UUID "2c55e69e-4993-11ed-b878-0242ac120002"
#define FROMNUM_UUID "ed9da18c-a800-4f66-a670-aa7547e34453"
#define LOGRADIO_UUID "6c6fd238-78fa-436b-aacf-15c5be1ef2e2"
#define LEGACY_LOGRADIO_UUID "6c6fd238-78fa-436b-aacf-15c5be1ef2e2"
#define LOGRADIO_UUID "5a3d6e49-06e6-4423-9944-e9de8cdf9547"
// NRF52 wants these constants as byte arrays
// Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER
@@ -28,5 +29,4 @@ class BluetoothApi
virtual void clearBonds();
virtual bool isConnected();
virtual int getRssi() = 0;
virtual void sendLog(const char *logMessage);
};

View File

@@ -181,8 +181,9 @@ int32_t ButtonThread::runOnce()
case BUTTON_EVENT_LONG_PRESSED: {
LOG_BUTTON("Long press!\n");
powerFSM.trigger(EVENT_PRESS);
if (screen)
screen->startShutdownScreen();
if (screen) {
screen->startAlert("Shutting down...");
}
playBeep();
break;
}
@@ -322,4 +323,4 @@ void ButtonThread::userButtonPressedLongStop()
if (millis() > c_holdOffTime) {
btnEvent = BUTTON_EVENT_LONG_RELEASED;
}
}
}

View File

@@ -26,7 +26,7 @@ SOFTWARE.*/
#include "DebugConfiguration.h"
#if HAS_WIFI || HAS_ETHERNET
#if HAS_NETWORKING
Syslog::Syslog(UDP &client)
{

View File

@@ -1,5 +1,6 @@
#ifndef SYSLOG_H
#define SYSLOG_H
#pragma once
#include "configuration.h"
// DEBUG LED
#ifndef LED_INVERTED
@@ -25,6 +26,14 @@
#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
#ifdef USE_SEGGER
@@ -117,7 +126,7 @@
#include <WiFi.h>
#endif // HAS_WIFI
#if HAS_WIFI || HAS_ETHERNET
#if HAS_NETWORKING
class Syslog
{
@@ -152,6 +161,4 @@ class Syslog
bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0)));
};
#endif // HAS_ETHERNET || HAS_WIFI
#endif // SYSLOG_H
#endif // HAS_ETHERNET || HAS_WIFI

View File

@@ -84,6 +84,58 @@ bool renameFile(const char *pathFrom, const char *pathTo)
#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.
*

View File

@@ -1,6 +1,7 @@
#pragma once
#include "configuration.h"
#include <vector>
// Cross platform filesystem API
@@ -49,6 +50,7 @@ using namespace Adafruit_LittleFS_Namespace;
void fsInit();
bool copyFile(const char *from, const char *to);
bool renameFile(const char *pathFrom, const char *pathTo);
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
void listDir(const char *dirname, uint8_t levels, bool del);
void rmDir(const char *dirname);
void setupSDCard();

View File

@@ -27,7 +27,7 @@
#if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#include "target_specific.h"
#if !MESTASTIC_EXCLUDE_WIFI
#if HAS_WIFI
#include <WiFi.h>
#endif
#endif

View File

@@ -11,6 +11,7 @@
#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerMon.h"
#include "configuration.h"
#include "graphics/Screen.h"
#include "main.h"
@@ -49,6 +50,7 @@ static bool isPowered()
static void sdsEnter()
{
LOG_DEBUG("Enter state: SDS\n");
powerMon->setState(meshtastic_PowerMon_State_CPU_DeepSleep);
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false);
}
@@ -68,6 +70,7 @@ static uint32_t secsSlept;
static void lsEnter()
{
LOG_INFO("lsEnter begin, ls_secs=%u\n", config.power.ls_secs);
powerMon->clearState(meshtastic_PowerMon_State_Screen_On);
screen->setOn(false);
secsSlept = 0; // How long have we been sleeping this time
@@ -87,8 +90,10 @@ static void lsIdle()
// Briefly come out of sleep long enough to blink the led once every few seconds
uint32_t sleepTime = SLEEP_TIME;
powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep);
setLed(false); // Never leave led on while in light sleep
esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL);
powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep);
switch (wakeCause2) {
case ESP_SLEEP_WAKEUP_TIMER:
@@ -144,6 +149,7 @@ static void lsExit()
static void nbEnter()
{
LOG_DEBUG("Enter state: NB\n");
powerMon->clearState(meshtastic_PowerMon_State_BT_On);
screen->setOn(false);
#ifdef ARCH_ESP32
// Only ESP32 should turn off bluetooth
@@ -155,6 +161,8 @@ static void nbEnter()
static void darkEnter()
{
powerMon->clearState(meshtastic_PowerMon_State_BT_On);
powerMon->clearState(meshtastic_PowerMon_State_Screen_On);
setBluetoothEnable(true);
screen->setOn(false);
}
@@ -162,6 +170,8 @@ static void darkEnter()
static void serialEnter()
{
LOG_DEBUG("Enter state: SERIAL\n");
powerMon->clearState(meshtastic_PowerMon_State_BT_On);
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
setBluetoothEnable(false);
screen->setOn(true);
screen->print("Serial connected\n");
@@ -170,6 +180,7 @@ static void serialEnter()
static void serialExit()
{
// Turn bluetooth back on when we leave serial stream API
powerMon->setState(meshtastic_PowerMon_State_BT_On);
setBluetoothEnable(true);
screen->print("Serial disconnected\n");
}
@@ -182,6 +193,8 @@ static void powerEnter()
LOG_INFO("Loss of power in Powered\n");
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
} else {
powerMon->setState(meshtastic_PowerMon_State_BT_On);
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
screen->setOn(true);
setBluetoothEnable(true);
// within enter() the function getState() returns the state we came from
@@ -205,6 +218,8 @@ static void powerIdle()
static void powerExit()
{
powerMon->setState(meshtastic_PowerMon_State_BT_On);
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
screen->setOn(true);
setBluetoothEnable(true);
@@ -216,6 +231,8 @@ static void powerExit()
static void onEnter()
{
LOG_DEBUG("Enter state: ON\n");
powerMon->setState(meshtastic_PowerMon_State_BT_On);
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
screen->setOn(true);
setBluetoothEnable(true);
}

45
src/PowerMon.cpp Normal file
View File

@@ -0,0 +1,45 @@
#include "PowerMon.h"
#include "NodeDB.h"
// Use the 'live' config flag to figure out if we should be showing this message
static bool is_power_enabled(uint64_t m)
{
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();
}

34
src/PowerMon.h Normal file
View File

@@ -0,0 +1,34 @@
#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;
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);
};
extern PowerMon *powerMon;
void powerMonInit();

View File

@@ -4,6 +4,7 @@
#include "concurrency/OSThread.h"
#include "configuration.h"
#include "main.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include <assert.h>
#include <cstring>
#include <memory>
@@ -15,12 +16,7 @@
#include "platform/portduino/PortduinoGlue.h"
#endif
/**
* A printer that doesn't go anywhere
*/
NoopPrint noopPrint;
#if HAS_WIFI || HAS_ETHERNET
#if HAS_NETWORKING
extern Syslog syslog;
#endif
void RedirectablePrint::rpInit()
@@ -39,7 +35,7 @@ void RedirectablePrint::setDestination(Print *_dest)
size_t RedirectablePrint::write(uint8_t c)
{
// Always send the characters to our segger JTAG debugger
#ifdef SEGGER_STDOUT_CH
#ifdef USE_SEGGER
SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c);
#endif
@@ -50,7 +46,7 @@ size_t RedirectablePrint::write(uint8_t c)
// 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;
static char printBuf[160];
@@ -66,25 +62,200 @@ size_t RedirectablePrint::vprintf(const char *format, va_list arg)
len = sizeof(printBuf) - 1;
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 (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);
Print::write("\u001b[0m", 5);
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';
// If we are the first message on a report, include the header
if (!isContinuationMessage) {
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);
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 \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000);
#else
printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000);
#endif
} else
#ifdef ARCH_PORTDUINO
::printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000);
#else
printf("%s \u001b[0m| ??:??:?? %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(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.bluetooth.device_logging_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, ...)
{
#ifdef ARCH_PORTDUINO
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)
return 0;
return;
else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
return 0;
return;
#endif
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
if (inDebugPrint != nullptr && xSemaphoreTake(inDebugPrint, portMAX_DELAY) == pdTRUE) {
#else
@@ -95,114 +266,10 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
va_list arg;
va_start(arg, format);
// Cope with 0 len format strings, but look for new line terminator
bool hasNewline = *format && format[strlen(format) - 1] == '\n';
log_to_serial(logLevel, format, arg);
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
isContinuationMessage = !hasNewline;
if (config.bluetooth.device_logging_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;
#ifdef ARCH_ESP32
if (thread)
nimbleBluetooth->sendLog(mt_sprintf("%s | [%s] %s", logLevel, thread->ThreadName.c_str(), message).c_str());
else
nimbleBluetooth->sendLog(mt_sprintf("%s | %s", logLevel, message).c_str());
#elif defined(ARCH_NRF52)
if (thread)
nrf52Bluetooth->sendLog(mt_sprintf("%s | [%s] %s", logLevel, thread->ThreadName.c_str(), message).c_str());
else
nrf52Bluetooth->sendLog(mt_sprintf("%s | %s", logLevel, message).c_str());
#endif
delete[] message;
}
}
va_end(arg);
#ifdef HAS_FREE_RTOS
xSemaphoreGive(inDebugPrint);
@@ -211,7 +278,7 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
#endif
}
return r;
return;
}
void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len)

View File

@@ -1,6 +1,7 @@
#pragma once
#include "../freertosinc.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include <Print.h>
#include <stdarg.h>
#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
* 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 */
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);
std::string mt_sprintf(const std::string fmt_str, ...);
};
class NoopPrint : public Print
{
public:
virtual size_t write(uint8_t c) { return 1; }
};
protected:
/// Subclasses can override if they need to change how we format over the serial port
virtual void log_to_serial(const char *logLevel, const char *format, va_list arg);
/**
* A printer that doesn't go anywhere
*/
extern NoopPrint noopPrint;
private:
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);
meshtastic_LogRecord_Level getLogLevel(const char *logLevel);
};

View File

@@ -24,7 +24,7 @@ void consolePrintf(const char *format, ...)
{
va_list arg;
va_start(arg, format);
console->vprintf(format, arg);
console->vprintf(nullptr, format, arg);
va_end(arg);
console->flush();
}
@@ -34,7 +34,6 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
assert(!console);
console = this;
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
Port.setTX(SERIAL2_TX);
@@ -81,13 +80,40 @@ 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.
if (config.has_lora && config.device.serial_enabled) {
// Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets
if (!config.device.debug_log_enabled)
setDestination(&noopPrint);
// Switch to protobufs for log messages
usingProtobufs = true;
canWrite = true;
return StreamAPI::handleToRadio(buf, len);
} else {
return false;
}
}
void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg)
{
if (usingProtobufs) {
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
{
/**
* If true we are talking to a smart host and all messages (including log messages) must be framed as protobufs.
*/
bool usingProtobufs = false;
public:
SerialConsole();
@@ -31,10 +36,13 @@ class SerialConsole : public StreamAPI, public RedirectablePrint, private concur
protected:
/// Check the current underlying physical link to see if the client is currently connected
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
void consolePrintf(const char *format, ...);
void consoleInit();
extern SerialConsole *console;
extern SerialConsole *console;

View File

@@ -8,13 +8,11 @@ enum class Cmd {
SET_ON,
SET_OFF,
ON_PRESS,
START_BLUETOOTH_PIN_SCREEN,
START_ALERT_FRAME,
STOP_ALERT_FRAME,
START_FIRMWARE_UPDATE_SCREEN,
STOP_BLUETOOTH_PIN_SCREEN,
STOP_BOOT_SCREEN,
PRINT,
START_SHUTDOWN_SCREEN,
START_REBOOT_SCREEN,
SHOW_PREV_FRAME,
SHOW_NEXT_FRAME
};

View File

@@ -242,9 +242,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define HAS_BLUETOOTH 0
#endif
#include "DebugConfiguration.h"
#include "RF95Configuration.h"
#ifndef HW_VENDOR
#error HW_VENDOR must be defined
#endif
@@ -261,6 +258,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_GPS 1
#define MESHTASTIC_EXCLUDE_SCREEN 1
#define MESHTASTIC_EXCLUDE_MQTT 1
#define MESHTASTIC_EXCLUDE_POWERMON 1
#endif
// Turn off all optional modules
@@ -281,6 +279,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_WAYPOINT 1
#define MESHTASTIC_EXCLUDE_INPUTBROKER 1
#define MESHTASTIC_EXCLUDE_SERIAL 1
#define MESHTASTIC_EXCLUDE_POWERSTRESS 1
#endif
// // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled)
@@ -290,6 +289,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define HAS_WIFI 0
#endif
// Allow code that needs internet to just check HAS_NETWORKING rather than HAS_WIFI || HAS_ETHERNET
#define HAS_NETWORKING (HAS_WIFI || HAS_ETHERNET)
// // Turn off Bluetooth
#ifdef MESHTASTIC_EXCLUDE_BLUETOOTH
#undef HAS_BLUETOOTH
@@ -308,4 +310,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifdef MESHTASTIC_EXCLUDE_SCREEN
#undef HAS_SCREEN
#define HAS_SCREEN 0
#endif
#endif
#include "DebugConfiguration.h"
#include "RF95Configuration.h"

View File

@@ -314,7 +314,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case SHT31_4x_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
if (registerValue == 0x11a2) {
if (registerValue == 0x11a2 || registerValue == 0x11da) {
type = SHT4X;
LOG_INFO("SHT4X sensor found\n");
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
@@ -402,4 +402,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const
size_t ScanI2CTwoWire::countDevices() const
{
return foundDevices.size();
}
}

View File

@@ -3,6 +3,7 @@
#include "Default.h"
#include "GPS.h"
#include "NodeDB.h"
#include "PowerMon.h"
#include "RTC.h"
#include "main.h" // pmu_found
@@ -815,9 +816,12 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
return;
if (on) {
powerMon->setState(meshtastic_PowerMon_State_GPS_Active);
clearBuffer(); // drop any old data waiting in the buffer before re-enabling
if (en_gpio)
digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE); // turn this on if defined, every time
} else {
powerMon->clearState(meshtastic_PowerMon_State_GPS_Active);
}
isInPowersave = !on;
if (!standbyOnly && en_gpio != 0 &&

View File

@@ -43,7 +43,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "meshUtils.h"
#include "modules/ExternalNotificationModule.h"
#include "modules/TextMessageModule.h"
#include "modules/WaypointModule.h"
#include "sleep.h"
#include "target_specific.h"
@@ -60,9 +59,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "platform/portduino/PortduinoGlue.h"
#endif
/// Convert an integer GPS coords to a floating point
#define DegD(i) (i * 1e-7)
using namespace meshtastic; /** @todo remove */
namespace graphics
@@ -79,7 +75,6 @@ namespace graphics
// A text message frame + debug frame + all the node infos
FrameCallback *normalFrames;
static uint32_t targetFramerate = IDLE_FRAMERATE;
static char btPIN[16] = "888888";
uint32_t logo_timeout = 5000; // 4 seconds for EACH logo
@@ -112,15 +107,39 @@ GeoCoord geoCoord;
static bool heartbeat = false;
#endif
static uint16_t displayWidth, displayHeight;
#define SCREEN_WIDTH displayWidth
#define SCREEN_HEIGHT displayHeight
// Quick access to screen dimensions from static drawing functions
// DEPRECATED. To-do: move static functions inside Screen class
#define SCREEN_WIDTH display->getWidth()
#define SCREEN_HEIGHT display->getHeight()
#include "graphics/ScreenFonts.h"
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
/// Check if the display can render a string (detect special chars; emoji)
static bool haveGlyphs(const char *str)
{
#if defined(OLED_UA) || defined(OLED_RU)
// Don't want to make any assumptions about custom language support
return true;
#endif
// Check each character with the lookup function for the OLED library
// We're not really meant to use this directly..
bool have = true;
for (uint16_t i = 0; i < strlen(str); i++) {
uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]);
// If font doesn't support a character, it is substituted for ¿
if (result == 191 && (uint8_t)str[i] != 191) {
have = false;
break;
}
}
LOG_DEBUG("haveGlyphs=%d\n", have);
return have;
}
/**
* Draw the icon with extra info printed around the corners
*/
@@ -144,13 +163,15 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg);
// Draw version in upper right
char buf[16];
snprintf(buf, sizeof(buf), "%s",
xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf);
// Draw version and short name in upper right
char buf[25];
snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : "");
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(x + SCREEN_WIDTH, y + 0, buf);
screen->forceDisplay();
// FIXME - draw serial # somewhere?
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
}
static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@@ -185,14 +206,15 @@ static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDi
if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg);
// Draw version in upper right
char buf[16];
snprintf(buf, sizeof(buf), "%s",
xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf);
// Draw version and shortname in upper right
char buf[25];
snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : "");
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(x + SCREEN_WIDTH, y + 0, buf);
screen->forceDisplay();
// FIXME - draw serial # somewhere?
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
}
static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@@ -202,7 +224,7 @@ static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
drawOEMIconScreen(region, display, state, x, y);
}
static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message)
void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message)
{
uint16_t x_offset = display->width() / 2;
display->setTextAlignment(TEXT_ALIGN_CENTER);
@@ -210,20 +232,6 @@ static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16
display->drawString(x_offset + x, 26 + y, message);
}
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
#ifdef ARCH_ESP32
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) {
drawFrameText(display, state, x, y, "Resuming...");
} else
#endif
{
// Draw region in upper left
const char *region = myRegion ? myRegion->name : NULL;
drawIconScreen(region, display, state, x, y);
}
}
// Used on boot when a certificate is being created
static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@@ -281,40 +289,19 @@ static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
}
}
/// Check if the display can render a string (detect special chars; emoji)
static bool haveGlyphs(const char *str)
{
#if defined(OLED_UA) || defined(OLED_RU)
// Don't want to make any assumptions about custom language support
return true;
#endif
// Check each character with the lookup function for the OLED library
// We're not really meant to use this directly..
bool have = true;
for (uint16_t i = 0; i < strlen(str); i++) {
uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]);
// If font doesn't support a character, it is substituted for ¿
if (result == 191 && (uint8_t)str[i] != 191) {
have = false;
break;
}
}
LOG_DEBUG("haveGlyphs=%d\n", have);
return have;
}
#ifdef USE_EINK
/// Used on eink displays while in deep sleep
static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// Next frame should use full-refresh, and block while running, else device will sleep before async callback
EINK_ADD_FRAMEFLAG(display, COSMETIC);
EINK_ADD_FRAMEFLAG(display, BLOCKING);
LOG_DEBUG("Drawing deep sleep screen\n");
drawIconScreen("Sleeping...", display, state, x, y);
// Display displayStr on the screen
drawIconScreen("Sleeping", display, state, x, y);
}
/// Used on eink displays when screen updates are paused
@@ -379,7 +366,7 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
// in the array of "drawScreen" functions; however,
// the passed-state doesn't quite reflect the "current"
// screen, so we have to detect it.
if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == INCOMING) {
if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == TransitionRelationship_INCOMING) {
// if we're transitioning from the end of the frame list back around to the first
// frame, then we want this to be `0`
module_frame = state->transitionFrameTarget;
@@ -393,31 +380,6 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
pi.drawFrame(display, state, x, y);
}
static void drawFrameBluetooth(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_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(x_offset + x, y_offset + y, "Bluetooth");
display->setFont(FONT_SMALL);
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5;
display->drawString(x_offset + x, y_offset + y, "Enter this code");
display->setFont(FONT_LARGE);
String displayPin(btPIN);
String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6);
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5;
display->drawString(x_offset + x, y_offset + y, pin);
display->setFont(FONT_SMALL);
String deviceName = "Name: ";
deviceName.concat(getDeviceName());
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5;
display->drawString(x_offset + x, y_offset + y, deviceName);
}
static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_CENTER);
@@ -450,37 +412,6 @@ static bool shouldDrawMessage(const meshtastic_MeshPacket *packet)
return packet->from != 0 && !moduleConfig.store_forward.enabled;
}
// Determine whether the waypoint frame should be drawn (waypoint deleted? expired?)
static bool shouldDrawWaypoint(const meshtastic_MeshPacket *packet)
{
#if !MESHTASTIC_EXCLUDE_WAYPOINT
// If no waypoint to show
if (!devicestate.has_rx_waypoint)
return false;
// Decode the message, to find the expiration time (is waypoint still valid)
// This handles "deletion" as well as expiration
meshtastic_Waypoint wp;
memset(&wp, 0, sizeof(wp));
if (pb_decode_from_bytes(packet->decoded.payload.bytes, packet->decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) {
// Valid waypoint
if (wp.expire > getTime())
return devicestate.has_rx_waypoint = true;
// Expired, or deleted
else
return devicestate.has_rx_waypoint = false;
}
// If decoding failed
LOG_ERROR("Failed to decode waypoint\n");
devicestate.has_rx_waypoint = false;
return false;
#else
return false;
#endif
}
// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage.
static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus)
{
@@ -1127,7 +1058,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
}
/// Draw a series of fields in a column, wrapping to multiple columns if needed
static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
void Screen::drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
{
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
@@ -1307,56 +1238,13 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const
}
}
#endif
namespace
{
/// A basic 2D point class for drawing
class Point
{
public:
float x, y;
Point(float _x, float _y) : x(_x), y(_y) {}
/// Apply a rotation around zero (standard rotation matrix math)
void rotate(float radian)
{
float cos = cosf(radian), sin = sinf(radian);
float rx = x * cos + y * sin, ry = -x * sin + y * cos;
x = rx;
y = ry;
}
void translate(int16_t dx, int dy)
{
x += dx;
y += dy;
}
void scale(float f)
{
// We use -f here to counter the flip that happens
// on the y axis when drawing and rotating on screen
x *= f;
y *= -f;
}
};
} // namespace
static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2)
{
d->drawLine(p1.x, p1.y, p2.x, p2.y);
}
/**
* Given a recent lat/lon return a guess of the heading the user is walking on.
*
* We keep a series of "after you've gone 10 meters, what is your heading since
* the last reference point?"
*/
static float estimatedHeading(double lat, double lon)
float Screen::estimatedHeading(double lat, double lon)
{
static double oldLat, oldLon;
static float b;
@@ -1380,38 +1268,13 @@ static float estimatedHeading(double lat, double lon)
return b;
}
static uint16_t getCompassDiam(OLEDDisplay *display)
{
uint16_t diam = 0;
uint16_t offset = 0;
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT)
offset = FONT_HEIGHT_SMALL;
// get the smaller of the 2 dimensions and subtract 20
if (display->getWidth() > (display->getHeight() - offset)) {
diam = display->getHeight() - offset;
// if 2/3 of the other size would be smaller, use that
if (diam > (display->getWidth() * 2 / 3)) {
diam = display->getWidth() * 2 / 3;
}
} else {
diam = display->getWidth();
if (diam > ((display->getHeight() - offset) * 2 / 3)) {
diam = (display->getHeight() - offset) * 2 / 3;
}
}
return diam - 20;
};
/// We will skip one node - the one for us, so we just blindly loop over all
/// nodes
static size_t nodeIndex;
static int8_t prevFrame = -1;
// Draw the arrow pointing to a node's location
static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian)
void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian)
{
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
@@ -1421,38 +1284,16 @@ static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t comp
for (int i = 0; i < 4; i++) {
arrowPoints[i]->rotate(headingRadian);
arrowPoints[i]->scale(getCompassDiam(display) * 0.6);
arrowPoints[i]->scale(compassDiam * 0.6);
arrowPoints[i]->translate(compassX, compassY);
}
drawLine(display, tip, tail);
drawLine(display, leftArrow, tip);
drawLine(display, rightArrow, tip);
}
// Draw north
static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading)
{
// If north is supposed to be at the top of the compass we want rotation to be +0
if (config.display.compass_north_top)
myHeading = -0;
Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f);
Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f);
Point *rosePoints[] = {&N1, &N2, &N3, &N4};
for (int i = 0; i < 4; i++) {
// North on compass will be negative of heading
rosePoints[i]->rotate(-myHeading);
rosePoints[i]->scale(getCompassDiam(display));
rosePoints[i]->translate(compassX, compassY);
}
drawLine(display, N1, N3);
drawLine(display, N2, N4);
drawLine(display, N1, N4);
display->drawLine(tip.x, tip.y, tail.x, tail.y);
display->drawLine(leftArrow.x, leftArrow.y, tip.x, tip.y);
display->drawLine(rightArrow.x, rightArrow.y, tip.x, tip.y);
}
// Get a string representation of the time passed since something happened
static void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
void Screen::getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
{
// Use an absolute timestamp in some cases.
// Particularly useful with E-Ink displays. Static UI, fewer refreshes.
@@ -1481,6 +1322,54 @@ static void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
snprintf(timeStr, maxLength, "unknown age");
}
void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading)
{
// If north is supposed to be at the top of the compass we want rotation to be +0
if (config.display.compass_north_top)
myHeading = -0;
Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f);
Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f);
Point *rosePoints[] = {&N1, &N2, &N3, &N4};
uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT);
for (int i = 0; i < 4; i++) {
// North on compass will be negative of heading
rosePoints[i]->rotate(-myHeading);
rosePoints[i]->scale(compassDiam);
rosePoints[i]->translate(compassX, compassY);
}
display->drawLine(N1.x, N1.y, N3.x, N3.y);
display->drawLine(N2.x, N2.y, N4.x, N4.y);
display->drawLine(N1.x, N1.y, N4.x, N4.y);
}
uint16_t Screen::getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
{
uint16_t diam = 0;
uint16_t offset = 0;
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT)
offset = FONT_HEIGHT_SMALL;
// get the smaller of the 2 dimensions and subtract 20
if (displayWidth > (displayHeight - offset)) {
diam = displayHeight - offset;
// if 2/3 of the other size would be smaller, use that
if (diam > (displayWidth * 2 / 3)) {
diam = displayWidth * 2 / 3;
}
} else {
diam = displayWidth;
if (diam > ((displayHeight - offset) * 2 / 3)) {
diam = (displayHeight - offset) * 2 / 3;
}
}
return diam - 20;
};
static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// We only advance our nodeIndex if the frame # has changed - because
@@ -1520,7 +1409,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
}
static char lastStr[20];
getTimeAgoStr(sinceLastSeen(node), lastStr, sizeof(lastStr));
screen->getTimeAgoStr(sinceLastSeen(node), lastStr, sizeof(lastStr));
static char distStr[20];
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
@@ -1531,13 +1420,14 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
const char *fields[] = {username, lastStr, signalStr, distStr, NULL};
int16_t compassX = 0, compassY = 0;
uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT);
// coordinates for the center of the compass/circle
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5;
compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5;
compassY = y + SCREEN_HEIGHT / 2;
} else {
compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5;
compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5;
compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2;
}
bool hasNodeHeading = false;
@@ -1548,8 +1438,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
if (screen->hasHeading())
myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
else
myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
drawCompassNorth(display, compassX, compassY, myHeading);
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
screen->drawCompassNorth(display, compassX, compassY, myHeading);
if (hasValidPosition(node)) {
// display direction toward node
@@ -1576,7 +1466,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
// If the top of the compass is not a static north we need adjust bearingToOther based on heading
if (!config.display.compass_north_top)
bearingToOther -= myHeading;
drawNodeHeading(display, compassX, compassY, bearingToOther);
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
}
}
if (!hasNodeHeading) {
@@ -1586,119 +1476,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
// hasValidPosition(node));
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
}
display->drawCircle(compassX, compassY, getCompassDiam(display) / 2);
display->drawCircle(compassX, compassY, compassDiam / 2);
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
display->setColor(BLACK);
}
// Must be after distStr is populated
drawColumns(display, x, y, fields);
}
/// Draw the last waypoint we received
static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// Prepare to draw
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
// Handle inverted display
// Unsure of expected behavior: for now, copy drawNodeInfo
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED)
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
// Decode the waypoint
meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
meshtastic_Waypoint wp;
memset(&wp, 0, sizeof(wp));
if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) {
// This *should* be caught by shouldDrawWaypoint, but we'll short-circuit here just in case
display->drawStringMaxWidth(0 + x, 0 + y, x + display->getWidth(), "Couldn't decode waypoint");
devicestate.has_rx_waypoint = false;
return;
}
// Get timestamp info. Will pass as a field to drawColumns
static char lastStr[20];
getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr));
// Will contain distance information, passed as a field to drawColumns
static char distStr[20];
// Get our node, to use our own position
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
// Text fields to draw (left of compass)
// Last element must be NULL. This signals the end of the char*[] to drawColumns
const char *fields[] = {"Waypoint", lastStr, wp.name, distStr, NULL};
// Co-ordinates for the center of the compass/circle
int16_t compassX = 0, compassY = 0;
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5;
compassY = y + SCREEN_HEIGHT / 2;
} else {
compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5;
compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2;
}
// If our node has a position:
if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) {
const meshtastic_PositionLite &op = ourNode->position;
float myHeading;
if (screen->hasHeading())
myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
else
myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
drawCompassNorth(display, compassX, compassY, myHeading);
// Distance to Waypoint
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
if (d < (2 * MILES_TO_FEET))
snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET);
else
snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET);
} else {
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0f m", d);
else
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
}
// Compass bearing to waypoint
float bearingToOther =
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
// If the top of the compass is not a static north we need adjust bearingToOther based on heading
if (!config.display.compass_north_top)
bearingToOther -= myHeading;
drawNodeHeading(display, compassX, compassY, bearingToOther);
}
// If our node doesn't have position
else {
// ? in the compass
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
// ? in the distance field
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
strncpy(distStr, "? mi", sizeof(distStr));
else
strncpy(distStr, "? km", sizeof(distStr));
}
// Undo color-inversion, if set prior to drawing header
// Unsure of expected behavior? For now: copy drawNodeInfo
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
display->setColor(BLACK);
}
// Draw compass circle
display->drawCircle(compassX, compassY, getCompassDiam(display) / 2);
// Must be after distStr is populated
drawColumns(display, x, y, fields);
screen->drawColumns(display, x, y, fields);
}
Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry)
@@ -1846,9 +1630,19 @@ void Screen::setup()
// Add frames.
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
static FrameCallback bootFrames[] = {drawBootScreen};
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
ui->setFrames(bootFrames, bootFrameCount);
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
#ifdef ARCH_ESP32
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) {
drawFrameText(display, state, x, y, "Resuming...");
} else
#endif
{
// Draw region in upper left
const char *region = myRegion ? myRegion->name : NULL;
drawIconScreen(region, display, state, x, y);
}
};
ui->setFrames(alertFrames, 1);
// No overlays.
ui->setOverlays(nullptr, 0);
@@ -1911,8 +1705,6 @@ void Screen::setup()
textMessageObserver.observe(textMessageModule);
if (inputBroker)
inputObserver.observe(inputBroker);
if (waypointModule)
waypointObserver.observe(waypointModule);
// Modules can notify screen about refresh
MeshModule::observeUIEvents(&uiFrameEventObserver);
@@ -2023,13 +1815,22 @@ int32_t Screen::runOnce()
case Cmd::SHOW_NEXT_FRAME:
handleShowNextFrame();
break;
case Cmd::START_BLUETOOTH_PIN_SCREEN:
handleStartBluetoothPinScreen(cmd.bluetooth_pin);
case Cmd::START_ALERT_FRAME: {
showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away
showingNormalScreen = false;
alertFrames[0] = alertFrame;
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?)
#endif
setFrameImmediateDraw(alertFrames);
break;
}
case Cmd::START_FIRMWARE_UPDATE_SCREEN:
handleStartFirmwareUpdateScreen();
break;
case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
case Cmd::STOP_ALERT_FRAME:
case Cmd::STOP_BOOT_SCREEN:
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
setFrames();
@@ -2038,12 +1839,6 @@ int32_t Screen::runOnce()
handlePrint(cmd.print_text);
free(cmd.print_text);
break;
case Cmd::START_SHUTDOWN_SCREEN:
handleShutdownScreen();
break;
case Cmd::START_REBOOT_SCREEN:
handleRebootScreen();
break;
default:
LOG_ERROR("Invalid screen cmd\n");
}
@@ -2241,11 +2036,6 @@ void Screen::setFrames()
normalFrames[numframes++] = drawTextMessageFrame;
}
// If we have a waypoint (not expired, not deleted)
if (devicestate.has_rx_waypoint && shouldDrawWaypoint(&devicestate.rx_waypoint)) {
normalFrames[numframes++] = drawWaypointFrame;
}
// then all the nodes
// We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens
size_t numToShow = min(numMeshNodes, 4U);
@@ -2284,17 +2074,6 @@ void Screen::setFrames()
setFastFramerate(); // Draw ASAP
}
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
{
LOG_DEBUG("showing bluetooth screen\n");
showingNormalScreen = false;
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
static FrameCallback frames[] = {drawFrameBluetooth};
snprintf(btPIN, sizeof(btPIN), "%06u", pin);
setFrameImmediateDraw(frames);
}
void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
{
ui->disableAllIndicators();
@@ -2302,41 +2081,6 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
setFastFramerate();
}
void Screen::handleShutdownScreen()
{
LOG_DEBUG("showing shutdown screen\n");
showingNormalScreen = false;
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?)
#endif
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
drawFrameText(display, state, x, y, "Shutting down...");
};
static FrameCallback frames[] = {frame};
setFrameImmediateDraw(frames);
}
void Screen::handleRebootScreen()
{
LOG_DEBUG("showing reboot screen\n");
showingNormalScreen = false;
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
handleSetOn(true); // Power-on to show rebooting screen (PowerFSM should handle?)
#endif
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
drawFrameText(display, state, x, y, "Rebooting...");
};
static FrameCallback frames[] = {frame};
setFrameImmediateDraw(frames);
}
void Screen::handleStartFirmwareUpdateScreen()
{
LOG_DEBUG("showing firmware screen\n");
@@ -2353,7 +2097,7 @@ void Screen::blink()
uint8_t count = 10;
dispdev->setBrightness(254);
while (count > 0) {
dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight());
dispdev->display();
delay(50);
dispdev->clear();
@@ -2844,13 +2588,6 @@ int Screen::handleInputEvent(const InputEvent *event)
return 0;
}
int Screen::handleWaypoint(const meshtastic_MeshPacket *arg)
{
// TODO: move to appropriate frame when redrawing
setFrames();
return 0;
}
} // namespace graphics
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}

View File

@@ -21,11 +21,13 @@ class Screen
void print(const char *) {}
void doDeepSleep() {}
void forceDisplay(bool forceUiUpdate = false) {}
void startBluetoothPinScreen(uint32_t pin) {}
void stopBluetoothPinScreen() {}
void startRebootScreen() {}
void startShutdownScreen() {}
void startFirmwareUpdateScreen() {}
void increaseBrightness() {}
void decreaseBrightness() {}
void setFunctionSymbal(std::string) {}
void removeFunctionSymbal(std::string) {}
void startAlert(const char *) {}
void endAlert() {}
};
} // namespace graphics
#else
@@ -34,6 +36,8 @@ class Screen
#include <OLEDDisplayUi.h>
#include "../configuration.h"
#include "gps/GeoCoord.h"
#include "graphics/ScreenFonts.h"
#ifdef USE_ST7567
#include <ST7567Wire.h>
@@ -82,6 +86,46 @@ class Screen
#define SEGMENT_WIDTH 16
#define SEGMENT_HEIGHT 4
/// Convert an integer GPS coords to a floating point
#define DegD(i) (i * 1e-7)
namespace
{
/// A basic 2D point class for drawing
class Point
{
public:
float x, y;
Point(float _x, float _y) : x(_x), y(_y) {}
/// Apply a rotation around zero (standard rotation matrix math)
void rotate(float radian)
{
float cos = cosf(radian), sin = sinf(radian);
float rx = x * cos + y * sin, ry = -x * sin + y * cos;
x = rx;
y = ry;
}
void translate(int16_t dx, int dy)
{
x += dx;
y += dy;
}
void scale(float f)
{
// We use -f here to counter the flip that happens
// on the y axis when drawing and rotating on screen
x *= f;
y *= -f;
}
};
} // namespace
namespace graphics
{
@@ -126,8 +170,6 @@ class Screen : public concurrency::OSThread
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
CallbackObserver<Screen, const meshtastic_MeshPacket *> textMessageObserver =
CallbackObserver<Screen, const meshtastic_MeshPacket *>(this, &Screen::handleTextMessage);
CallbackObserver<Screen, const meshtastic_MeshPacket *> waypointObserver =
CallbackObserver<Screen, const meshtastic_MeshPacket *>(this, &Screen::handleWaypoint);
CallbackObserver<Screen, const UIFrameEvent *> uiFrameEventObserver =
CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent);
CallbackObserver<Screen, const InputEvent *> inputObserver =
@@ -168,20 +210,49 @@ class Screen : public concurrency::OSThread
void blink();
void drawFrameText(OLEDDisplay *, OLEDDisplayUiState *, int16_t, int16_t, const char *);
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
// Draw north
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading);
static uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight);
float estimatedHeading(double lat, double lon);
void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian);
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields);
/// Handle button press, trackball or swipe action)
void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); }
void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); }
void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); }
/// Starts showing the Bluetooth PIN screen.
//
// Switches over to a static frame showing the Bluetooth pairing screen
// with the PIN.
void startBluetoothPinScreen(uint32_t pin)
// generic alert start
void startAlert(FrameCallback _alertFrame)
{
alertFrame = _alertFrame;
ScreenCmd cmd;
cmd.cmd = Cmd::START_ALERT_FRAME;
enqueueCmd(cmd);
}
void startAlert(const char *_alertMessage)
{
startAlert([_alertMessage](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
uint16_t x_offset = display->width() / 2;
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(x_offset + x, 26 + y, _alertMessage);
});
}
void endAlert()
{
ScreenCmd cmd;
cmd.cmd = Cmd::START_BLUETOOTH_PIN_SCREEN;
cmd.bluetooth_pin = pin;
cmd.cmd = Cmd::STOP_ALERT_FRAME;
enqueueCmd(cmd);
}
@@ -192,20 +263,6 @@ class Screen : public concurrency::OSThread
enqueueCmd(cmd);
}
void startShutdownScreen()
{
ScreenCmd cmd;
cmd.cmd = Cmd::START_SHUTDOWN_SCREEN;
enqueueCmd(cmd);
}
void startRebootScreen()
{
ScreenCmd cmd;
cmd.cmd = Cmd::START_REBOOT_SCREEN;
enqueueCmd(cmd);
}
// Function to allow the AccelerometerThread to set the heading if a sensor provides it
// Mutex needed?
void setHeading(long _heading)
@@ -224,9 +281,6 @@ class Screen : public concurrency::OSThread
void setFunctionSymbal(std::string sym);
void removeFunctionSymbal(std::string sym);
/// Stops showing the bluetooth PIN screen.
void stopBluetoothPinScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); }
/// Stops showing the boot screen.
void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); }
@@ -338,7 +392,6 @@ class Screen : public concurrency::OSThread
int handleTextMessage(const meshtastic_MeshPacket *arg);
int handleUIFrameEvent(const UIFrameEvent *arg);
int handleInputEvent(const InputEvent *arg);
int handleWaypoint(const meshtastic_MeshPacket *arg);
/// Used to force (super slow) eink displays to draw critical frames
void forceDisplay(bool forceUiUpdate = false);
@@ -361,7 +414,13 @@ class Screen : public concurrency::OSThread
bool isAUTOOled = false;
// Screen dimensions (for convenience)
// Defined during Screen::setup
uint16_t displayWidth = 0;
uint16_t displayHeight = 0;
private:
FrameCallback alertFrames[1];
struct ScreenCmd {
Cmd cmd;
union {
@@ -387,11 +446,8 @@ class Screen : public concurrency::OSThread
void handleOnPress();
void handleShowNextFrame();
void handleShowPrevFrame();
void handleStartBluetoothPinScreen(uint32_t pin);
void handlePrint(const char *text);
void handleStartFirmwareUpdateScreen();
void handleShutdownScreen();
void handleRebootScreen();
/// Rebuilds our list of frames (screens) to default ones.
void setFrames();
@@ -429,6 +485,9 @@ class Screen : public concurrency::OSThread
bool digitalWatchFace = true;
#endif
/// callback for current alert frame
FrameCallback alertFrame;
/// Queue of commands to execute in doTask.
TypedQueue<ScreenCmd> cmdQueue;
/// Whether we are using a display
@@ -455,4 +514,5 @@ class Screen : public concurrency::OSThread
};
} // namespace graphics
#endif

View File

@@ -28,8 +28,8 @@
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
#endif
#define fontHeight(font) ((font)[1] + 1) // height is position 1
#define _fontHeight(font) ((font)[1] + 1) // height is position 1
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
#define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL)
#define FONT_HEIGHT_MEDIUM _fontHeight(FONT_MEDIUM)
#define FONT_HEIGHT_LARGE _fontHeight(FONT_LARGE)

View File

@@ -6,6 +6,7 @@
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "PowerMon.h"
#include "ReliableRouter.h"
#include "airtime.h"
#include "buzz.h"
@@ -214,6 +215,14 @@ __attribute__((weak, noinline)) bool loopCanSleep()
return true;
}
/**
* Print info as a structured log message (for automated log processing)
*/
void printInfo()
{
LOG_INFO("S:B:%d,%s\n", HW_VENDOR, optstr(APP_VERSION));
}
void setup()
{
concurrency::hasBeenSetup = true;
@@ -221,7 +230,7 @@ void setup()
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64;
#ifdef SEGGER_STDOUT_CH
#ifdef USE_SEGGER
auto mode = false ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM;
#ifdef NRF52840_XXAA
auto buflen = 4096; // this board has a fair amount of ram
@@ -234,6 +243,7 @@ void setup()
#ifdef DEBUG_PORT
consoleInit(); // Set serial baud rate and init our mesh console
#endif
powerMonInit();
serialSinceMsec = millis();
@@ -553,7 +563,7 @@ void setup()
#endif
// Hello
LOG_INFO("Meshtastic hwvendor=%d, swver=%s\n", HW_VENDOR, optstr(APP_VERSION));
printInfo();
#ifdef ARCH_ESP32
esp32Setup();
@@ -930,7 +940,7 @@ void setup()
nodeDB->saveToDisk(SEGMENT_CONFIG);
if (!rIf->reconfigure()) {
LOG_WARN("Reconfigure failed, rebooting\n");
screen->startRebootScreen();
screen->startAlert("Rebooting...");
rebootAtMsec = millis() + 5000;
}
}

View File

@@ -22,7 +22,6 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
printPacket("Ignoring incoming msg we've already seen", p);
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
Router::cancelSending(p->from, p->id);

View File

@@ -184,6 +184,7 @@ template <typename T> void LR11x0Interface<T>::setStandby()
activeReceiveStart = 0;
disableInterrupt();
completeSending(); // If we were sending, not anymore
RadioLibInterface::setStandby();
}
/**
@@ -223,7 +224,7 @@ template <typename T> void LR11x0Interface<T>::startReceive()
0); // only RX_DONE IRQ is needed, we'll check for PREAMBLE_DETECTED and HEADER_VALID in isActivelyReceiving
assert(err == RADIOLIB_ERR_NONE);
isReceiving = true;
RadioLibInterface::startReceive();
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
enableInterrupt(isrRxLevel0);

View File

@@ -269,7 +269,7 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
assert(node);
if (hasValidPosition(node)) {
#if HAS_GPS
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
if (positionModule) {
LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel);
positionModule->sendOurPosition(dest, wantReplies, node->channel);
@@ -299,6 +299,7 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p)
} else {
LOG_WARN("ToPhone queue is full, dropping packet.\n");
releaseToPool(p);
fromNum++; // Make sure to notify observers in case they are reconnected so they can get the packets
return;
}
}

View File

@@ -26,7 +26,7 @@
#include <vector>
#ifdef ARCH_ESP32
#if !MESHTASTIC_EXCLUDE_WIFI
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#endif
#include "modules/esp32/StoreForwardModule.h"
@@ -180,7 +180,7 @@ bool NodeDB::resetRadioConfig(bool factory_reset)
if (didFactoryReset) {
LOG_INFO("Rebooting due to factory reset");
screen->startRebootScreen();
screen->startAlert("Rebooting...");
rebootAtMsec = millis() + (5 * 1000);
}

View File

@@ -5,6 +5,7 @@
#include "Channels.h"
#include "Default.h"
#include "FSCommon.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PhoneAPI.h"
@@ -47,6 +48,8 @@ void PhoneAPI::handleStartConfig()
// even if we were already connected - restart our state machine
state = STATE_SEND_MY_INFO;
pauseBluetoothLogging = true;
filesManifest = getFiles("/", 10);
LOG_DEBUG("Got %d files in manifest\n", filesManifest.size());
LOG_INFO("Starting API client config\n");
nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos
@@ -149,6 +152,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
STATE_SEND_CONFIG,
STATE_SEND_MODULE_CONFIG,
STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to the client
STATE_SEND_FILEMANIFEST,
STATE_SEND_COMPLETE_ID,
STATE_SEND_PACKETS // send packets or debug strings
*/
@@ -324,7 +328,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Advance when we have sent all of our ModuleConfig objects
if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) {
// Clients sending special nonce don't want to see other nodeinfos
state = config_nonce == SPECIAL_NONCE ? STATE_SEND_COMPLETE_ID : STATE_SEND_OTHER_NODEINFOS;
state = config_nonce == SPECIAL_NONCE ? STATE_SEND_FILEMANIFEST : STATE_SEND_OTHER_NODEINFOS;
config_state = 0;
}
break;
@@ -340,20 +344,32 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time
} else {
LOG_INFO("Done sending nodeinfos\n");
state = STATE_SEND_COMPLETE_ID;
state = STATE_SEND_FILEMANIFEST;
// Go ahead and send that ID right now
return getFromRadio(buf);
}
break;
}
case STATE_SEND_FILEMANIFEST: {
LOG_INFO("getFromRadio=STATE_SEND_FILEMANIFEST\n");
// last element
if (config_state == filesManifest.size()) { // also handles an empty filesManifest
config_state = 0;
filesManifest.clear();
// Skip to complete packet
sendConfigComplete();
} else {
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_fileInfo_tag;
fromRadioScratch.fileInfo = filesManifest.at(config_state);
LOG_DEBUG("File: %s (%d) bytes\n", fromRadioScratch.fileInfo.file_name, fromRadioScratch.fileInfo.size_bytes);
config_state++;
}
break;
}
case STATE_SEND_COMPLETE_ID:
LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag;
fromRadioScratch.config_complete_id = config_nonce;
config_nonce = 0;
state = STATE_SEND_PACKETS;
pauseBluetoothLogging = false;
sendConfigComplete();
break;
case STATE_SEND_PACKETS:
@@ -391,7 +407,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Encapsulate as a FromRadio packet
size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch);
LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes);
// VERY IMPORTANT to not print debug messages while writing to fromRadioScratch - because we use that same buffer
// for logging (when we are encapsulating with protobufs)
// LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes);
return numbytes;
}
@@ -399,8 +417,19 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
return 0;
}
void PhoneAPI::sendConfigComplete()
{
LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag;
fromRadioScratch.config_complete_id = config_nonce;
config_nonce = 0;
state = STATE_SEND_PACKETS;
pauseBluetoothLogging = false;
}
void PhoneAPI::handleDisconnect()
{
filesManifest.clear();
pauseBluetoothLogging = false;
LOG_INFO("PhoneAPI disconnect\n");
}
@@ -443,6 +472,7 @@ bool PhoneAPI::available()
case STATE_SEND_MODULECONFIG:
case STATE_SEND_METADATA:
case STATE_SEND_OWN_NODEINFO:
case STATE_SEND_FILEMANIFEST:
case STATE_SEND_COMPLETE_ID:
return true;
@@ -457,7 +487,6 @@ bool PhoneAPI::available()
}
}
return true; // Always say we have something, because we might need to advance our state machine
case STATE_SEND_PACKETS: {
if (!queueStatusPacketForPhone)
queueStatusPacketForPhone = service.getQueueStatusForPhone();

View File

@@ -2,10 +2,20 @@
#include "Observer.h"
#include "mesh-pb-constants.h"
#include <iterator>
#include <string>
#include <vector>
// Make sure that we never let our packets grow too large for one BLE packet
#define MAX_TO_FROM_RADIO_SIZE 512
#if meshtastic_FromRadio_size > MAX_TO_FROM_RADIO_SIZE
#error "meshtastic_FromRadio_size is too large for our BLE packets"
#endif
#if meshtastic_ToRadio_size > MAX_TO_FROM_RADIO_SIZE
#error "meshtastic_ToRadio_size is too large for our BLE packets"
#endif
#define SPECIAL_NONCE 69420
/**
@@ -29,6 +39,7 @@ class PhoneAPI
STATE_SEND_CONFIG, // Replacement for the old Radioconfig
STATE_SEND_MODULECONFIG, // Send Module specific config
STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to to the client
STATE_SEND_FILEMANIFEST, // Send file manifest
STATE_SEND_COMPLETE_ID,
STATE_SEND_PACKETS // send packets or debug strings
};
@@ -65,6 +76,8 @@ class PhoneAPI
uint32_t config_nonce = 0;
uint32_t readIndex = 0;
std::vector<meshtastic_FileInfo> filesManifest = {};
void resetReadIndex() { readIndex = 0; }
public:
@@ -91,6 +104,8 @@ class PhoneAPI
*/
size_t getFromRadio(uint8_t *buf);
void sendConfigComplete();
/**
* Return true if we have data available to send to the phone
*/

View File

@@ -25,7 +25,8 @@ typedef struct {
} DACDB;
// Interpolation function
DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val2) {
DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val2)
{
DACDB result;
double fraction = (double)(dbm - dbm1) / (dbm2 - dbm1);
result.dac = (uint8_t)(val1.dac + fraction * (val2.dac - val1.dac));
@@ -34,16 +35,17 @@ DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val
}
// Function to find the correct DAC and DB values based on dBm using interpolation
DACDB getDACandDB(uint8_t dbm) {
DACDB getDACandDB(uint8_t dbm)
{
// Predefined values
static const struct {
uint8_t dbm;
DACDB values;
} dbmToDACDB[] = {
{20, {168, 2}}, // 100mW
{24, {148, 6}}, // 250mW
{27, {128, 9}}, // 500mW
{30, {90, 12}} // 1000mW
{20, {168, 2}}, // 100mW
{24, {148, 6}}, // 250mW
{27, {128, 9}}, // 500mW
{30, {90, 12}} // 1000mW
};
const int numValues = sizeof(dbmToDACDB) / sizeof(dbmToDACDB[0]);
@@ -103,7 +105,7 @@ bool RF95Interface::init()
if (power > RF95_MAX_POWER) // This chip has lower power limits than some
power = RF95_MAX_POWER;
limitPower();
iface = lora = new RadioLibRF95(&module);
@@ -116,13 +118,13 @@ bool RF95Interface::init()
// enable PA
#ifdef RF95_PA_EN
#if defined(RF95_PA_DAC_EN)
#ifdef RADIOMASTER_900_BANDIT_NANO
// Use calculated DAC value
dacWrite(RF95_PA_EN, powerDAC);
#else
// Use Value set in /*/variant.h
dacWrite(RF95_PA_EN, RF95_PA_LEVEL);
#endif
#ifdef RADIOMASTER_900_BANDIT_NANO
// Use calculated DAC value
dacWrite(RF95_PA_EN, powerDAC);
#else
// Use Value set in /*/variant.h
dacWrite(RF95_PA_EN, RF95_PA_LEVEL);
#endif
#endif
#endif
@@ -254,6 +256,7 @@ void RF95Interface::setStandby()
isReceiving = false; // If we were receiving, not any more
disableInterrupt();
completeSending(); // If we were sending, not anymore
RadioLibInterface::setStandby();
}
/** We override to turn on transmitter power as needed.

View File

@@ -261,7 +261,6 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
uint8_t CWsize = map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
// LOG_DEBUG("rx_snr of %f so setting CWsize to:%d\n", snr, CWsize);
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
delay = random(0, 2 * CWsize) * slotTimeMsec;
LOG_DEBUG("rx_snr found in packet. As a router, setting tx delay:%d\n", delay);
@@ -522,7 +521,7 @@ void RadioInterface::applyModemConfig()
LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f\n", freq, loraConfig.frequency_offset);
LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d\n", myRegion->name, channelName, loraConfig.modem_preset,
channel_num, power);
LOG_INFO("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f mhz)\n", myRegion->freqStart, myRegion->freqEnd,
LOG_INFO("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)\n", myRegion->freqStart, myRegion->freqEnd,
myRegion->freqEnd - myRegion->freqStart);
LOG_INFO("Radio myRegion->numChannels: %d x %.3fkHz\n", numChannels, bw);
LOG_INFO("Radio channel_num: %d\n", channel_num + 1);

View File

@@ -1,6 +1,7 @@
#include "RadioLibInterface.h"
#include "MeshTypes.h"
#include "NodeDB.h"
#include "PowerMon.h"
#include "SPILock.h"
#include "configuration.h"
#include "error.h"
@@ -317,6 +318,7 @@ void RadioLibInterface::handleTransmitInterrupt()
// ignore the transmit interrupt
if (sendingPacket)
completeSending();
powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); // But our transmitter is deffinitely off now
}
void RadioLibInterface::completeSending()
@@ -412,6 +414,24 @@ void RadioLibInterface::handleReceiveInterrupt()
}
}
void RadioLibInterface::startReceive()
{
isReceiving = true;
powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn);
}
void RadioLibInterface::configHardwareForSend()
{
powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn);
}
void RadioLibInterface::setStandby()
{
// neither sending nor receiving
powerMon->clearState(meshtastic_PowerMon_State_Lora_RXOn);
powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn);
}
/** start an immediate transmit */
void RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
{
@@ -431,6 +451,7 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
// This send failed, but make sure to 'complete' it properly
completeSending();
powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); // Transmitter off now
startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode)
}

View File

@@ -126,8 +126,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
* Start waiting to receive a message
*
* External functions can call this method to wake the device from sleep.
* Subclasses must override and call this base method
*/
virtual void startReceive() = 0;
virtual void startReceive();
/** can we detect a LoRa preamble on the current channel? */
virtual bool isChannelActive() = 0;
@@ -166,8 +167,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
meshtastic_QueueStatus getQueueStatus();
protected:
/** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */
virtual void configHardwareForSend() {}
/** Do any hardware setup needed on entry into send configuration for the radio.
* Subclasses can customize, but must also call this base method */
virtual void configHardwareForSend();
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
virtual bool canSendImmediately();
@@ -186,5 +188,8 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
*/
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) = 0;
virtual void setStandby() = 0;
/**
* Subclasses must override, implement and then call into this base class implementation
*/
virtual void setStandby();
};

View File

@@ -244,8 +244,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
// If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it)
assert(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag ||
p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now
if (!(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag ||
p->which_payload_variant == meshtastic_MeshPacket_decoded_tag)) {
return meshtastic_Routing_Error_BAD_REQUEST;
}
// If the packet is not yet encrypted, do so now
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {

View File

@@ -231,6 +231,7 @@ template <typename T> void SX126xInterface<T>::setStandby()
activeReceiveStart = 0;
disableInterrupt();
completeSending(); // If we were sending, not anymore
RadioLibInterface::setStandby();
}
/**
@@ -270,7 +271,7 @@ template <typename T> void SX126xInterface<T>::startReceive()
LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err);
assert(err == RADIOLIB_ERR_NONE);
isReceiving = true;
RadioLibInterface::startReceive();
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
enableInterrupt(isrRxLevel0);

View File

@@ -190,6 +190,7 @@ template <typename T> void SX128xInterface<T>::setStandby()
activeReceiveStart = 0;
disableInterrupt();
completeSending(); // If we were sending, not anymore
RadioLibInterface::setStandby();
}
/**
@@ -263,7 +264,7 @@ template <typename T> void SX128xInterface<T>::startReceive()
LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err);
assert(err == RADIOLIB_ERR_NONE);
isReceiving = true;
RadioLibInterface::startReceive();
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
enableInterrupt(isrRxLevel0);

View File

@@ -1,5 +1,6 @@
#include "StreamAPI.h"
#include "PowerFSM.h"
#include "RTC.h"
#include "configuration.h"
#define START1 0x94
@@ -96,7 +97,6 @@ void StreamAPI::writeStream()
void StreamAPI::emitTxBuffer(size_t len)
{
if (len != 0) {
// LOG_DEBUG("emit tx %d\n", len);
txBuf[0] = START1;
txBuf[1] = START2;
txBuf[2] = (len >> 8) & 0xff;
@@ -119,6 +119,25 @@ void StreamAPI::emitRebooted()
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch));
}
void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg)
{
// In case we send a FromRadio packet
memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_log_record_tag;
fromRadioScratch.log_record.level = level;
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
fromRadioScratch.log_record.time = rtc_sec;
strncpy(fromRadioScratch.log_record.source, src, sizeof(fromRadioScratch.log_record.source) - 1);
auto num_printed =
vsnprintf(fromRadioScratch.log_record.message, sizeof(fromRadioScratch.log_record.message) - 1, format, arg);
if (num_printed > 0 && fromRadioScratch.log_record.message[num_printed - 1] ==
'\n') // Strip any ending newline, because we have records for framing instead.
fromRadioScratch.log_record.message[num_printed - 1] = '\0';
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch));
}
/// Hookable to find out when connection changes
void StreamAPI::onConnectionChanged(bool connected)
{
@@ -131,4 +150,4 @@ void StreamAPI::onConnectionChanged(bool connected)
// received a packet in a while
powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
}
}
}

View File

@@ -82,4 +82,7 @@ class StreamAPI : public PhoneAPI
/// Subclasses can use this scratch buffer if they wish
uint8_t txBuf[MAX_STREAM_BUF_SIZE] = {0};
};
/// Low level function to emit a protobuf encapsulated log record
void emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg);
};

View File

@@ -12,6 +12,8 @@
#include <RAK13800_W5100S.h>
#include <SPI.h>
#if HAS_NETWORKING
#ifndef DISABLE_NTP
#include <NTPClient.h>
@@ -183,3 +185,5 @@ bool isEthernetAvailable()
return true;
}
}
#endif

View File

@@ -22,7 +22,6 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
The wifi radio and the oled screen will be put to sleep.
This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh. */
meshtastic_Config_DeviceConfig_Role_ROUTER = 2,
/* Description: Combination of both ROUTER and CLIENT. Not for mobile devices. */
meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT = 3,
/* Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry

View File

@@ -8,7 +8,6 @@
#include "meshtastic/channel.pb.h"
#include "meshtastic/localonly.pb.h"
#include "meshtastic/mesh.pb.h"
#include "meshtastic/module_config.pb.h"
#include "meshtastic/telemetry.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@@ -36,7 +36,7 @@ PB_BIND(meshtastic_NodeInfo, meshtastic_NodeInfo, AUTO)
PB_BIND(meshtastic_MyNodeInfo, meshtastic_MyNodeInfo, AUTO)
PB_BIND(meshtastic_LogRecord, meshtastic_LogRecord, AUTO)
PB_BIND(meshtastic_LogRecord, meshtastic_LogRecord, 2)
PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO)
@@ -45,6 +45,9 @@ PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO)
PB_BIND(meshtastic_FromRadio, meshtastic_FromRadio, 2)
PB_BIND(meshtastic_FileInfo, meshtastic_FileInfo, AUTO)
PB_BIND(meshtastic_ToRadio, meshtastic_ToRadio, 2)

View File

@@ -167,6 +167,15 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO = 64,
/* Heltec Capsule Sensor V3 with ESP32-S3 CPU, Portable LoRa device that can replace GNSS modules or sensors */
meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 = 65,
/* Heltec Vision Master T190 with ESP32-S3 CPU, and a 1.90 inch TFT display */
meshtastic_HardwareModel_HELTEC_VISION_MASTER_T190 = 66,
/* Heltec Vision Master E213 with ESP32-S3 CPU, and a 2.13 inch E-Ink display */
meshtastic_HardwareModel_HELTEC_VISION_MASTER_E213 = 67,
/* Heltec Vision Master E290 with ESP32-S3 CPU, and a 2.9 inch E-Ink display */
meshtastic_HardwareModel_HELTEC_VISION_MASTER_E290 = 68,
/* Heltec Mesh Node T114 board with nRF52840 CPU, and a 1.14 inch TFT display, Ultimate low-power design,
specifically adapted for the Meshtatic project */
meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 = 69,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -691,11 +700,11 @@ typedef struct _meshtastic_MyNodeInfo {
and then extend as needed by emitting multiple records. */
typedef struct _meshtastic_LogRecord {
/* Log levels, chosen to match python logging conventions. */
char message[64];
char message[384];
/* Seconds since 1970 - or 0 for unknown/unset */
uint32_t time;
/* Usually based on thread name - if known */
char source[8];
char source[32];
/* Not yet set */
meshtastic_LogRecord_Level level;
} meshtastic_LogRecord;
@@ -711,6 +720,14 @@ typedef struct _meshtastic_QueueStatus {
uint32_t mesh_packet_id;
} meshtastic_QueueStatus;
/* Individual File info for the device */
typedef struct _meshtastic_FileInfo {
/* The fully qualified path of the file */
char file_name[228];
/* The size of the file in bytes */
uint32_t size_bytes;
} meshtastic_FileInfo;
typedef PB_BYTES_ARRAY_T(237) meshtastic_Compressed_data_t;
/* Compressed message payload */
typedef struct _meshtastic_Compressed {
@@ -815,6 +832,8 @@ typedef struct _meshtastic_FromRadio {
meshtastic_DeviceMetadata metadata;
/* MQTT Client Proxy Message (device sending to client / phone for publishing to MQTT) */
meshtastic_MqttClientProxyMessage mqttClientProxyMessage;
/* File system manifest messages */
meshtastic_FileInfo fileInfo;
};
} meshtastic_FromRadio;
@@ -958,6 +977,7 @@ extern "C" {
#define meshtastic_Compressed_portnum_ENUMTYPE meshtastic_PortNum
@@ -985,6 +1005,7 @@ extern "C" {
#define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_default {0, 0, 0, 0}
#define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}}
#define meshtastic_FileInfo_init_default {"", 0}
#define meshtastic_ToRadio_init_default {0, {meshtastic_MeshPacket_init_default}}
#define meshtastic_Compressed_init_default {_meshtastic_PortNum_MIN, {0, {0}}}
#define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}}
@@ -1008,6 +1029,7 @@ extern "C" {
#define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_zero {0, 0, 0, 0}
#define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}}
#define meshtastic_FileInfo_init_zero {"", 0}
#define meshtastic_ToRadio_init_zero {0, {meshtastic_MeshPacket_init_zero}}
#define meshtastic_Compressed_init_zero {_meshtastic_PortNum_MIN, {0, {0}}}
#define meshtastic_NeighborInfo_init_zero {0, 0, 0, 0, {meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero}}
@@ -1110,6 +1132,8 @@ extern "C" {
#define meshtastic_QueueStatus_free_tag 2
#define meshtastic_QueueStatus_maxlen_tag 3
#define meshtastic_QueueStatus_mesh_packet_id_tag 4
#define meshtastic_FileInfo_file_name_tag 1
#define meshtastic_FileInfo_size_bytes_tag 2
#define meshtastic_Compressed_portnum_tag 1
#define meshtastic_Compressed_data_tag 2
#define meshtastic_Neighbor_node_id_tag 1
@@ -1144,6 +1168,7 @@ extern "C" {
#define meshtastic_FromRadio_xmodemPacket_tag 12
#define meshtastic_FromRadio_metadata_tag 13
#define meshtastic_FromRadio_mqttClientProxyMessage_tag 14
#define meshtastic_FromRadio_fileInfo_tag 15
#define meshtastic_ToRadio_packet_tag 1
#define meshtastic_ToRadio_want_config_id_tag 3
#define meshtastic_ToRadio_disconnect_tag 4
@@ -1321,7 +1346,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,channel,channel), 10) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,queueStatus,queueStatus), 11) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,xmodemPacket,xmodemPacket), 12) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,metadata,metadata), 13) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14)
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15)
#define meshtastic_FromRadio_CALLBACK NULL
#define meshtastic_FromRadio_DEFAULT NULL
#define meshtastic_FromRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket
@@ -1335,6 +1361,13 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttC
#define meshtastic_FromRadio_payload_variant_xmodemPacket_MSGTYPE meshtastic_XModem
#define meshtastic_FromRadio_payload_variant_metadata_MSGTYPE meshtastic_DeviceMetadata
#define meshtastic_FromRadio_payload_variant_mqttClientProxyMessage_MSGTYPE meshtastic_MqttClientProxyMessage
#define meshtastic_FromRadio_payload_variant_fileInfo_MSGTYPE meshtastic_FileInfo
#define meshtastic_FileInfo_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, file_name, 1) \
X(a, STATIC, SINGULAR, UINT32, size_bytes, 2)
#define meshtastic_FileInfo_CALLBACK NULL
#define meshtastic_FileInfo_DEFAULT NULL
#define meshtastic_ToRadio_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,packet,packet), 1) \
@@ -1434,6 +1467,7 @@ extern const pb_msgdesc_t meshtastic_MyNodeInfo_msg;
extern const pb_msgdesc_t meshtastic_LogRecord_msg;
extern const pb_msgdesc_t meshtastic_QueueStatus_msg;
extern const pb_msgdesc_t meshtastic_FromRadio_msg;
extern const pb_msgdesc_t meshtastic_FileInfo_msg;
extern const pb_msgdesc_t meshtastic_ToRadio_msg;
extern const pb_msgdesc_t meshtastic_Compressed_msg;
extern const pb_msgdesc_t meshtastic_NeighborInfo_msg;
@@ -1459,6 +1493,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_LogRecord_fields &meshtastic_LogRecord_msg
#define meshtastic_QueueStatus_fields &meshtastic_QueueStatus_msg
#define meshtastic_FromRadio_fields &meshtastic_FromRadio_msg
#define meshtastic_FileInfo_fields &meshtastic_FileInfo_msg
#define meshtastic_ToRadio_fields &meshtastic_ToRadio_msg
#define meshtastic_Compressed_fields &meshtastic_Compressed_msg
#define meshtastic_NeighborInfo_fields &meshtastic_NeighborInfo_msg
@@ -1478,9 +1513,10 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_Compressed_size 243
#define meshtastic_Data_size 270
#define meshtastic_DeviceMetadata_size 46
#define meshtastic_FileInfo_size 236
#define meshtastic_FromRadio_size 510
#define meshtastic_Heartbeat_size 0
#define meshtastic_LogRecord_size 81
#define meshtastic_LogRecord_size 426
#define meshtastic_MeshPacket_size 326
#define meshtastic_MqttClientProxyMessage_size 501
#define meshtastic_MyNodeInfo_size 18

View File

@@ -124,6 +124,8 @@ typedef enum _meshtastic_PortNum {
meshtastic_PortNum_ATAK_PLUGIN = 72,
/* Provides unencrypted information about a node for consumption by a map via MQTT */
meshtastic_PortNum_MAP_REPORT_APP = 73,
/* PowerStress based monitoring support (for automated power consumption testing) */
meshtastic_PortNum_POWERSTRESS_APP = 74,
/* Private applications should use portnums >= 256.
To simplify initial development and testing you can use "PRIVATE_APP"
in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */

View File

@@ -9,5 +9,9 @@
PB_BIND(meshtastic_PowerMon, meshtastic_PowerMon, AUTO)
PB_BIND(meshtastic_PowerStressMessage, meshtastic_PowerStressMessage, AUTO)

View File

@@ -38,6 +38,33 @@ See GPSPowerState for more details */
meshtastic_PowerMon_State_GPS_Active = 2048
} meshtastic_PowerMon_State;
/* What operation would we like the UUT to perform.
note: senders should probably set want_response in their request packets, so that they can know when the state
machine has started processing their request */
typedef enum _meshtastic_PowerStressMessage_Opcode {
/* Unset/unused */
meshtastic_PowerStressMessage_Opcode_UNSET = 0,
meshtastic_PowerStressMessage_Opcode_PRINT_INFO = 1, /* Print board version slog and send an ack that we are alive and ready to process commands */
meshtastic_PowerStressMessage_Opcode_FORCE_QUIET = 2, /* Try to turn off all automatic processing of packets, screen, sleeping, etc (to make it easier to measure in isolation) */
meshtastic_PowerStressMessage_Opcode_END_QUIET = 3, /* Stop powerstress processing - probably by just rebooting the board */
meshtastic_PowerStressMessage_Opcode_SCREEN_ON = 16, /* Turn the screen on */
meshtastic_PowerStressMessage_Opcode_SCREEN_OFF = 17, /* Turn the screen off */
meshtastic_PowerStressMessage_Opcode_CPU_IDLE = 32, /* Let the CPU run but we assume mostly idling for num_seconds */
meshtastic_PowerStressMessage_Opcode_CPU_DEEPSLEEP = 33, /* Force deep sleep for FIXME seconds */
meshtastic_PowerStressMessage_Opcode_CPU_FULLON = 34, /* Spin the CPU as fast as possible for num_seconds */
meshtastic_PowerStressMessage_Opcode_LED_ON = 48, /* Turn the LED on for num_seconds (and leave it on - for baseline power measurement purposes) */
meshtastic_PowerStressMessage_Opcode_LED_OFF = 49, /* Force the LED off for num_seconds */
meshtastic_PowerStressMessage_Opcode_LORA_OFF = 64, /* Completely turn off the LORA radio for num_seconds */
meshtastic_PowerStressMessage_Opcode_LORA_TX = 65, /* Send Lora packets for num_seconds */
meshtastic_PowerStressMessage_Opcode_LORA_RX = 66, /* Receive Lora packets for num_seconds (node will be mostly just listening, unless an external agent is helping stress this by sending packets on the current channel) */
meshtastic_PowerStressMessage_Opcode_BT_OFF = 80, /* Turn off the BT radio for num_seconds */
meshtastic_PowerStressMessage_Opcode_BT_ON = 81, /* Turn on the BT radio for num_seconds */
meshtastic_PowerStressMessage_Opcode_WIFI_OFF = 96, /* Turn off the WIFI radio for num_seconds */
meshtastic_PowerStressMessage_Opcode_WIFI_ON = 97, /* Turn on the WIFI radio for num_seconds */
meshtastic_PowerStressMessage_Opcode_GPS_OFF = 112, /* Turn off the GPS radio for num_seconds */
meshtastic_PowerStressMessage_Opcode_GPS_ON = 113 /* Turn on the GPS radio for num_seconds */
} meshtastic_PowerStressMessage_Opcode;
/* Struct definitions */
/* Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs).
But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) */
@@ -45,6 +72,13 @@ typedef struct _meshtastic_PowerMon {
char dummy_field;
} meshtastic_PowerMon;
/* PowerStress testing support via the C++ PowerStress module */
typedef struct _meshtastic_PowerStressMessage {
/* What type of HardwareMessage is this? */
meshtastic_PowerStressMessage_Opcode cmd;
float num_seconds;
} meshtastic_PowerStressMessage;
#ifdef __cplusplus
extern "C" {
@@ -55,13 +89,23 @@ extern "C" {
#define _meshtastic_PowerMon_State_MAX meshtastic_PowerMon_State_GPS_Active
#define _meshtastic_PowerMon_State_ARRAYSIZE ((meshtastic_PowerMon_State)(meshtastic_PowerMon_State_GPS_Active+1))
#define _meshtastic_PowerStressMessage_Opcode_MIN meshtastic_PowerStressMessage_Opcode_UNSET
#define _meshtastic_PowerStressMessage_Opcode_MAX meshtastic_PowerStressMessage_Opcode_GPS_ON
#define _meshtastic_PowerStressMessage_Opcode_ARRAYSIZE ((meshtastic_PowerStressMessage_Opcode)(meshtastic_PowerStressMessage_Opcode_GPS_ON+1))
#define meshtastic_PowerStressMessage_cmd_ENUMTYPE meshtastic_PowerStressMessage_Opcode
/* Initializer values for message structs */
#define meshtastic_PowerMon_init_default {0}
#define meshtastic_PowerStressMessage_init_default {_meshtastic_PowerStressMessage_Opcode_MIN, 0}
#define meshtastic_PowerMon_init_zero {0}
#define meshtastic_PowerStressMessage_init_zero {_meshtastic_PowerStressMessage_Opcode_MIN, 0}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_PowerStressMessage_cmd_tag 1
#define meshtastic_PowerStressMessage_num_seconds_tag 2
/* Struct field encoding specification for nanopb */
#define meshtastic_PowerMon_FIELDLIST(X, a) \
@@ -69,14 +113,23 @@ extern "C" {
#define meshtastic_PowerMon_CALLBACK NULL
#define meshtastic_PowerMon_DEFAULT NULL
#define meshtastic_PowerStressMessage_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, cmd, 1) \
X(a, STATIC, SINGULAR, FLOAT, num_seconds, 2)
#define meshtastic_PowerStressMessage_CALLBACK NULL
#define meshtastic_PowerStressMessage_DEFAULT NULL
extern const pb_msgdesc_t meshtastic_PowerMon_msg;
extern const pb_msgdesc_t meshtastic_PowerStressMessage_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_PowerMon_fields &meshtastic_PowerMon_msg
#define meshtastic_PowerStressMessage_fields &meshtastic_PowerStressMessage_msg
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_POWERMON_PB_H_MAX_SIZE meshtastic_PowerMon_size
#define MESHTASTIC_MESHTASTIC_POWERMON_PB_H_MAX_SIZE meshtastic_PowerStressMessage_size
#define meshtastic_PowerMon_size 0
#define meshtastic_PowerStressMessage_size 7
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -6,7 +6,7 @@
#include "main.h"
#include "mesh/http/ContentHelper.h"
#include "mesh/http/WebServer.h"
#if !MESHTASTIC_EXCLUDE_WIFI
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#endif
#include "mqtt/JSON.h"

View File

@@ -1,5 +1,5 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_WIFI
#if HAS_WIFI
#include "NodeDB.h"
#include "RTC.h"
#include "concurrency/Periodic.h"

View File

@@ -137,7 +137,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
if (BleOta::getOtaAppVersion().isEmpty()) {
LOG_INFO("No OTA firmware available, scheduling regular reboot in %d seconds\n", s);
screen->startRebootScreen();
screen->startAlert("Rebooting...");
} else {
screen->startFirmwareUpdateScreen();
BleOta::switchToOtaApp();
@@ -145,7 +145,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
}
#else
LOG_INFO("Not on ESP32, scheduling regular reboot in %d seconds\n", s);
screen->startRebootScreen();
screen->startAlert("Rebooting...");
#endif
rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000);
break;
@@ -232,9 +232,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
#if !MESHTASTIC_EXCLUDE_GPS
if (gps != nullptr)
gps->enable();
#endif
// Send our new fixed position to the mesh for good measure
positionModule->sendOurPosition();
#endif
}
break;
}
@@ -388,6 +388,10 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d\n", min_node_info_broadcast_secs);
config.device.node_info_broadcast_secs = min_node_info_broadcast_secs;
}
// Router Client is deprecated; Set it to client
if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
}
break;
case meshtastic_Config_position_tag:
LOG_INFO("Setting config: Position\n");
@@ -811,7 +815,7 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch
void AdminModule::reboot(int32_t seconds)
{
LOG_INFO("Rebooting in %d seconds\n", seconds);
screen->startRebootScreen();
screen->startAlert("Rebooting...");
rebootAtMsec = (seconds < 0) ? 0 : (millis() + seconds * 1000);
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include "ProtobufModule.h"
#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#endif

View File

@@ -597,14 +597,14 @@ int32_t CannedMessageModule::runOnce()
// handle fn+s for shutdown
case 0x9b:
if (screen)
screen->startShutdownScreen();
screen->startAlert("Shutting down...");
shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000;
runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
break;
// and fn+r for reboot
case 0x90:
if (screen)
screen->startRebootScreen();
screen->startAlert("Rebooting...");
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
break;

View File

@@ -27,6 +27,9 @@
#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
#include "modules/RemoteHardwareModule.h"
#endif
#if !MESHTASTIC_EXCLUDE_POWERSTRESS
#include "modules/PowerStressModule.h"
#endif
#include "modules/RoutingModule.h"
#include "modules/TextMessageModule.h"
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
@@ -115,6 +118,9 @@ void setupModules()
#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
new RemoteHardwareModule();
#endif
#if !MESHTASTIC_EXCLUDE_POWERSTRESS
new PowerStressModule();
#endif
// Example: Put your module here
// new ReplyModule();

View File

@@ -0,0 +1,77 @@
#include "PowerStressModule.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "Router.h"
#include "configuration.h"
#include "main.h"
extern void printInfo();
PowerStressModule::PowerStressModule()
: ProtobufModule("powerstress", meshtastic_PortNum_POWERSTRESS_APP, &meshtastic_PowerStressMessage_msg),
concurrency::OSThread("PowerStressModule")
{
}
bool PowerStressModule::handleReceivedProtobuf(const meshtastic_MeshPacket &req, meshtastic_PowerStressMessage *pptr)
{
// We only respond to messages if powermon debugging is already on
if (config.power.powermon_enables) {
auto p = *pptr;
LOG_INFO("Received PowerStress cmd=%d\n", p.cmd);
// Some commands we can handle immediately, anything else gets deferred to be handled by our thread
switch (p.cmd) {
case meshtastic_PowerStressMessage_Opcode_UNSET:
LOG_ERROR("PowerStress operation unset\n");
break;
case meshtastic_PowerStressMessage_Opcode_PRINT_INFO:
printInfo();
break;
default:
if (currentMessage.cmd != meshtastic_PowerStressMessage_Opcode_UNSET)
LOG_ERROR("PowerStress operation %d already in progress! Can't start new command\n", currentMessage.cmd);
else
currentMessage = p; // copy for use by thread (the message provided to us will be getting freed)
break;
}
}
return true;
}
int32_t PowerStressModule::runOnce()
{
if (!config.power.powermon_enables) {
// Powermon not enabled - stop using CPU/stop this thread
return disable();
}
int32_t sleep_msec = 10; // when not active check for new messages every 10ms
auto &p = currentMessage;
if (isRunningCommand) {
// Done with the previous command - our sleep must have finished
p.cmd = meshtastic_PowerStressMessage_Opcode_UNSET;
p.num_seconds = 0;
} else {
sleep_msec = (int32_t)(p.num_seconds * 1000);
isRunningCommand = !!sleep_msec; // if the command wants us to sleep, make sure to mark that we have something running
switch (p.cmd) {
case meshtastic_PowerStressMessage_Opcode_UNSET: // No need to start a new command
break;
case meshtastic_PowerStressMessage_Opcode_LED_ON:
break;
default:
LOG_ERROR("PowerStress operation %d not yet implemented!\n", p.cmd);
sleep_msec = 0; // Don't do whatever sleep was requested...
break;
}
}
return sleep_msec;
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include "ProtobufModule.h"
#include "concurrency/OSThread.h"
#include "mesh/generated/meshtastic/powermon.pb.h"
/**
* A module that provides easy low-level remote access to device hardware.
*/
class PowerStressModule : public ProtobufModule<meshtastic_PowerStressMessage>, private concurrency::OSThread
{
meshtastic_PowerStressMessage currentMessage = meshtastic_PowerStressMessage_init_default;
bool isRunningCommand = false;
public:
/** Constructor
* name is for debugging output
*/
PowerStressModule();
protected:
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_PowerStressMessage *p) override;
/**
* Periodically read the gpios we have been asked to WATCH, if they have changed,
* broadcast a message with the change information.
*
* The method that will be called each time our thread gets a chance to run
*
* Returns desired period for next invocation (or RUN_SAME for no change)
*/
virtual int32_t runOnce() override;
};
extern PowerStressModule powerStressModule;

View File

@@ -188,7 +188,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
if (lastMeasurementPacket == nullptr) {
// If there's no valid packet, display "Environment"
display->drawString(x, y, "Environment");
display->drawString(x, y += fontHeight(FONT_SMALL), "No measurement");
display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement");
return;
}
@@ -213,31 +213,31 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
}
// Continue with the remaining details
display->drawString(x, y += fontHeight(FONT_SMALL),
display->drawString(x, y += _fontHeight(FONT_SMALL),
"Temp/Hum: " + last_temp + " / " +
String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%");
if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) {
display->drawString(x, y += fontHeight(FONT_SMALL),
display->drawString(x, y += _fontHeight(FONT_SMALL),
"Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA");
}
if (lastMeasurement.variant.environment_metrics.voltage != 0) {
display->drawString(x, y += fontHeight(FONT_SMALL),
display->drawString(x, y += _fontHeight(FONT_SMALL),
"Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " +
String(lastMeasurement.variant.environment_metrics.current, 0) + "mA");
}
if (lastMeasurement.variant.environment_metrics.iaq != 0) {
display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq));
display->drawString(x, y += _fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq));
}
if (lastMeasurement.variant.environment_metrics.distance != 0)
display->drawString(x, y += fontHeight(FONT_SMALL),
display->drawString(x, y += _fontHeight(FONT_SMALL),
"Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm");
if (lastMeasurement.variant.environment_metrics.weight != 0)
display->drawString(x, y += fontHeight(FONT_SMALL),
display->drawString(x, y += _fontHeight(FONT_SMALL),
"Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg");
}
@@ -285,6 +285,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
valid = valid && sht31Sensor.getMetrics(m);
hasSensor = true;
}
if (sht4xSensor.hasSensor()) {
valid = valid && sht4xSensor.getMetrics(m);
hasSensor = true;
}
if (lps22hbSensor.hasSensor()) {
valid = valid && lps22hbSensor.getMetrics(m);
hasSensor = true;
@@ -533,4 +537,4 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
return result;
}
#endif
#endif

View File

@@ -108,7 +108,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
display->drawString(x, y, "Power Telemetry");
if (lastMeasurementPacket == nullptr) {
display->setFont(FONT_SMALL);
display->drawString(x, y += fontHeight(FONT_MEDIUM), "No measurement");
display->drawString(x, y += _fontHeight(FONT_MEDIUM), "No measurement");
return;
}
@@ -120,22 +120,22 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
auto &p = lastMeasurementPacket->decoded;
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) {
display->setFont(FONT_SMALL);
display->drawString(x, y += fontHeight(FONT_MEDIUM), "Measurement Error");
display->drawString(x, y += _fontHeight(FONT_MEDIUM), "Measurement Error");
LOG_ERROR("Unable to decode last packet");
return;
}
display->setFont(FONT_SMALL);
String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C";
display->drawString(x, y += fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
if (lastMeasurement.variant.power_metrics.ch1_voltage != 0) {
display->drawString(x, y += fontHeight(FONT_SMALL),
display->drawString(x, y += _fontHeight(FONT_SMALL),
"Ch 1 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 0) + "V / " +
String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA");
display->drawString(x, y += fontHeight(FONT_SMALL),
display->drawString(x, y += _fontHeight(FONT_SMALL),
"Ch 2 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 0) + "V / " +
String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA");
display->drawString(x, y += fontHeight(FONT_SMALL),
display->drawString(x, y += _fontHeight(FONT_SMALL),
"Ch 3 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 0) + "V / " +
String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA");
}

View File

@@ -2,6 +2,11 @@
#include "NodeDB.h"
#include "PowerFSM.h"
#include "configuration.h"
#if HAS_SCREEN
#include "gps/RTC.h"
#include "graphics/Screen.h"
#include "main.h"
#endif
WaypointModule *waypointModule;
@@ -11,14 +16,155 @@ ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp)
auto &p = mp.decoded;
LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes);
#endif
UIFrameEvent e = {true, true};
// We only store/display messages destined for us.
// Keep a copy of the most recent text message.
devicestate.rx_waypoint = mp;
devicestate.has_rx_waypoint = true;
powerFSM.trigger(EVENT_RECEIVED_MSG);
notifyObservers(&mp);
notifyObservers(&e);
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
}
#if HAS_SCREEN
bool WaypointModule::shouldDraw()
{
#if !MESHTASTIC_EXCLUDE_WAYPOINT
// If no waypoint to show
if (!devicestate.has_rx_waypoint)
return false;
// Decode the message, to find the expiration time (is waypoint still valid)
// This handles "deletion" as well as expiration
meshtastic_Waypoint wp;
memset(&wp, 0, sizeof(wp));
if (pb_decode_from_bytes(devicestate.rx_waypoint.decoded.payload.bytes, devicestate.rx_waypoint.decoded.payload.size,
&meshtastic_Waypoint_msg, &wp)) {
// Valid waypoint
if (wp.expire > getTime())
return devicestate.has_rx_waypoint = true;
// Expired, or deleted
else
return devicestate.has_rx_waypoint = false;
}
// If decoding failed
LOG_ERROR("Failed to decode waypoint\n");
devicestate.has_rx_waypoint = false;
return false;
#else
return false;
#endif
}
/// Draw the last waypoint we received
void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// Prepare to draw
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
// Handle inverted display
// Unsure of expected behavior: for now, copy drawNodeInfo
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED)
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
// Decode the waypoint
meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
meshtastic_Waypoint wp;
memset(&wp, 0, sizeof(wp));
if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) {
// This *should* be caught by shouldDrawWaypoint, but we'll short-circuit here just in case
display->drawStringMaxWidth(0 + x, 0 + y, x + display->getWidth(), "Couldn't decode waypoint");
devicestate.has_rx_waypoint = false;
return;
}
// Get timestamp info. Will pass as a field to drawColumns
static char lastStr[20];
screen->getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr));
// Will contain distance information, passed as a field to drawColumns
static char distStr[20];
// Get our node, to use our own position
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
// Text fields to draw (left of compass)
// Last element must be NULL. This signals the end of the char*[] to drawColumns
const char *fields[] = {"Waypoint", lastStr, wp.name, distStr, NULL};
// Dimensions / co-ordinates for the compass/circle
int16_t compassX = 0, compassY = 0;
uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight());
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;
}
// If our node has a position:
if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) {
const meshtastic_PositionLite &op = ourNode->position;
float myHeading;
if (screen->hasHeading())
myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
else
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
screen->drawCompassNorth(display, compassX, compassY, myHeading);
// Distance to Waypoint
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
if (d < (2 * MILES_TO_FEET))
snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET);
else
snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET);
} else {
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0f m", d);
else
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
}
// Compass bearing to waypoint
float bearingToOther =
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
// If the top of the compass is not a static north we need adjust bearingToOther based on heading
if (!config.display.compass_north_top)
bearingToOther -= myHeading;
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
}
// If our node doesn't have position
else {
// ? in the compass
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
// ? in the distance field
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
strncpy(distStr, "? mi", sizeof(distStr));
else
strncpy(distStr, "? km", sizeof(distStr));
}
// Undo color-inversion, if set prior to drawing header
// Unsure of expected behavior? For now: copy drawNodeInfo
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
display->setColor(BLACK);
}
// Draw compass circle
display->drawCircle(compassX, compassY, compassDiam / 2);
// Must be after distStr is populated
screen->drawColumns(display, x, y, fields);
}
#endif

View File

@@ -5,21 +5,29 @@
/**
* Waypoint message handling for meshtastic
*/
class WaypointModule : public SinglePortModule, public Observable<const meshtastic_MeshPacket *>
class WaypointModule : public SinglePortModule, public Observable<const UIFrameEvent *>
{
public:
/** Constructor
* name is for debugging output
*/
WaypointModule() : SinglePortModule("waypoint", meshtastic_PortNum_WAYPOINT_APP) {}
#if HAS_SCREEN
bool shouldDraw();
#endif
protected:
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for
it
*/
virtual Observable<const UIFrameEvent *> *getUIFrameObservable() override { return this; }
#if HAS_SCREEN
virtual bool wantUIFrame() override { return this->shouldDraw(); }
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
#endif
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
};
extern WaypointModule *waypointModule;
extern WaypointModule *waypointModule;

View File

@@ -319,8 +319,8 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m
#ifdef ARCH_ESP32
if (moduleConfig.store_forward.enabled) {
// The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT
if ((getFrom(&mp) != nodeDB->getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) {
// The router node should not be sending messages as a client
if ((getFrom(&mp) != nodeDB->getNodeNum())) {
if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) {
auto &p = mp.decoded;

View File

@@ -14,7 +14,7 @@
#endif
#include "mesh/generated/meshtastic/remote_hardware.pb.h"
#include "sleep.h"
#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#include <WiFi.h>
#endif
@@ -175,7 +175,7 @@ void mqttInit()
new MQTT();
}
#ifdef HAS_NETWORKING
#if HAS_NETWORKING
MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient), mqttQueue(MAX_MQTT_QUEUE)
#else
MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
@@ -206,7 +206,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs);
}
#ifdef HAS_NETWORKING
#if HAS_NETWORKING
if (!moduleConfig.mqtt.proxy_to_client_enabled)
pubSub.setCallback(mqttCallback);
#endif
@@ -226,7 +226,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
bool MQTT::isConnectedDirectly()
{
#ifdef HAS_NETWORKING
#if HAS_NETWORKING
return pubSub.connected();
#else
return false;
@@ -244,7 +244,7 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained)
service.sendMqttMessageToClientProxy(msg);
return true;
}
#ifdef HAS_NETWORKING
#if HAS_NETWORKING
else if (isConnectedDirectly()) {
return pubSub.publish(topic, payload, retained);
}
@@ -264,7 +264,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo
service.sendMqttMessageToClientProxy(msg);
return true;
}
#ifdef HAS_NETWORKING
#if HAS_NETWORKING
else if (isConnectedDirectly()) {
return pubSub.publish(topic, payload, length, retained);
}
@@ -284,7 +284,7 @@ void MQTT::reconnect()
publishStatus();
return; // Don't try to connect directly to the server
}
#ifdef HAS_NETWORKING
#if HAS_NETWORKING
// Defaults
int serverPort = 1883;
const char *serverAddr = default_mqtt_address;
@@ -357,7 +357,7 @@ void MQTT::reconnect()
void MQTT::sendSubscriptions()
{
#ifdef HAS_NETWORKING
#if HAS_NETWORKING
size_t numChan = channels.getNumChannels();
for (size_t i = 0; i < numChan; i++) {
const auto &ch = channels.getByIndex(i);
@@ -396,7 +396,7 @@ bool MQTT::wantsLink() const
int32_t MQTT::runOnce()
{
#ifdef HAS_NETWORKING
#if HAS_NETWORKING
if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()))
return disable();

View File

@@ -8,17 +8,15 @@
#include "mqtt/JSON.h"
#if HAS_WIFI
#include <WiFiClient.h>
#define HAS_NETWORKING 1
#if !defined(ARCH_PORTDUINO)
#include <WiFiClientSecure.h>
#endif
#endif
#if HAS_ETHERNET
#include <EthernetClient.h>
#define HAS_NETWORKING 1
#endif
#ifdef HAS_NETWORKING
#if HAS_NETWORKING
#include <PubSubClient.h>
#endif
@@ -43,7 +41,7 @@ class MQTT : private concurrency::OSThread
#endif
public:
#ifdef HAS_NETWORKING
#if HAS_NETWORKING
PubSubClient pubSub;
#endif
MQTT();

View File

@@ -82,7 +82,33 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
LOG_INFO("*** Enter passkey %d on the peer side ***\n", passkey);
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
screen->startBluetoothPinScreen(passkey);
#if HAS_SCREEN
screen->startAlert([passkey](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
char btPIN[16] = "888888";
snprintf(btPIN, sizeof(btPIN), "%06u", passkey);
int x_offset = display->width() / 2;
int y_offset = display->height() <= 80 ? 0 : 32;
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(x_offset + x, y_offset + y, "Bluetooth");
display->setFont(FONT_SMALL);
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5;
display->drawString(x_offset + x, y_offset + y, "Enter this code");
display->setFont(FONT_LARGE);
String displayPin(btPIN);
String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6);
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5;
display->drawString(x_offset + x, y_offset + y, pin);
display->setFont(FONT_SMALL);
String deviceName = "Name: ";
deviceName.concat(getDeviceName());
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5;
display->drawString(x_offset + x, y_offset + y, deviceName);
});
#endif
passkeyShowing = true;
return passkey;
@@ -94,7 +120,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
if (passkeyShowing) {
passkeyShowing = false;
screen->stopBluetoothPinScreen();
screen->endAlert();
}
}
@@ -193,7 +219,6 @@ void NimbleBluetooth::setupService()
logRadioCharacteristic = bleService->createCharacteristic(
LOGRADIO_UUID,
NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC, 512U);
logRadioCharacteristic->setValue("Init");
}
bluetoothPhoneAPI = new BluetoothPhoneAPI();
@@ -242,12 +267,12 @@ void NimbleBluetooth::clearBonds()
NimBLEDevice::deleteAllBonds();
}
void NimbleBluetooth::sendLog(const char *logMessage)
void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
{
if (!bleServer || !isConnected() || strlen(logMessage) > 512) {
if (!bleServer || !isConnected() || length > 512) {
return;
}
logRadioCharacteristic->notify(reinterpret_cast<const uint8_t *>(logMessage), strlen(logMessage), true);
logRadioCharacteristic->notify(logMessage, length, true);
}
void clearNVS()

View File

@@ -11,7 +11,7 @@ class NimbleBluetooth : BluetoothApi
bool isActive();
bool isConnected();
int getRssi();
void sendLog(const char *logMessage);
void sendLog(const uint8_t *logMessage, size_t length);
private:
void setupService();

View File

@@ -8,7 +8,7 @@
#include "nimble/NimbleBluetooth.h"
#endif
#if !MESHTASTIC_EXCLUDE_WIFI
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#endif
@@ -24,23 +24,22 @@
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH
void setBluetoothEnable(bool enable)
{
#ifndef MESHTASTIC_EXCLUDE_WIFI
#if HAS_WIFI
if (!isWifiAvailable() && config.bluetooth.enabled == true)
#else
if (config.bluetooth.enabled == true)
#endif
#ifdef MESHTASTIC_EXCLUDE_WIFI
if (config.bluetooth.enabled == true)
#endif
{
if (!nimbleBluetooth) {
nimbleBluetooth = new NimbleBluetooth();
}
if (enable && !nimbleBluetooth->isActive()) {
nimbleBluetooth->setup();
}
// For ESP32, no way to recover from bluetooth shutdown without reboot
// BLE advertising automatically stops when MCU enters light-sleep(?)
// For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse
{
if (!nimbleBluetooth) {
nimbleBluetooth = new NimbleBluetooth();
}
if (enable && !nimbleBluetooth->isActive()) {
nimbleBluetooth->setup();
}
// For ESP32, no way to recover from bluetooth shutdown without reboot
// BLE advertising automatically stops when MCU enters light-sleep(?)
// For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse
}
}
#else
void setBluetoothEnable(bool enable) {}

View File

@@ -74,11 +74,16 @@ void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value)
LOG_INFO("CCCD Updated: %u\n", cccd_value);
// Check the characteristic this CCCD update is associated with in case
// this handler is used for multiple CCCD records.
// According to the GATT spec: cccd value = 0x0001 means notifications are enabled
// and cccd value = 0x0002 means indications are enabled
if (chr->uuid == fromNum.uuid || chr->uuid == logRadio.uuid) {
if (chr->notifyEnabled(conn_hdl)) {
LOG_INFO("fromNum 'Notify' enabled\n");
auto result = cccd_value == 2 ? chr->indicateEnabled(conn_hdl) : chr->notifyEnabled(conn_hdl);
if (result) {
LOG_INFO("Notify/Indicate enabled\n");
} else {
LOG_INFO("fromNum 'Notify' disabled\n");
LOG_INFO("Notify/Indicate disabled\n");
}
}
}
@@ -176,7 +181,7 @@ void setupMeshService(void)
toRadio.setWriteCallback(onToRadioWrite, false);
toRadio.begin();
logRadio.setProperties(CHR_PROPS_NOTIFY | CHR_PROPS_READ);
logRadio.setProperties(CHR_PROPS_INDICATE | CHR_PROPS_NOTIFY | CHR_PROPS_READ);
logRadio.setPermission(secMode, SECMODE_NO_ACCESS);
logRadio.setMaxLen(512);
logRadio.setCccdWriteCallback(onCccd);
@@ -290,7 +295,31 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke
{
LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3);
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
screen->startBluetoothPinScreen(configuredPasskey);
screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
char btPIN[16] = "888888";
snprintf(btPIN, sizeof(btPIN), "%06u", configuredPasskey);
int x_offset = display->width() / 2;
int y_offset = display->height() <= 80 ? 0 : 32;
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(x_offset + x, y_offset + y, "Bluetooth");
display->setFont(FONT_SMALL);
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5;
display->drawString(x_offset + x, y_offset + y, "Enter this code");
display->setFont(FONT_LARGE);
String displayPin(btPIN);
String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6);
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5;
display->drawString(x_offset + x, y_offset + y, pin);
display->setFont(FONT_SMALL);
String deviceName = "Name: ";
deviceName.concat(getDeviceName());
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5;
display->drawString(x_offset + x, y_offset + y, deviceName);
});
if (match_request) {
uint32_t start_time = millis();
while (millis() < start_time + 30000) {
@@ -307,12 +336,15 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu
LOG_INFO("BLE pairing success\n");
else
LOG_INFO("BLE pairing failed\n");
screen->stopBluetoothPinScreen();
screen->endAlert();
}
void NRF52Bluetooth::sendLog(const char *logMessage)
void NRF52Bluetooth::sendLog(const uint8_t *logMessage, size_t length)
{
if (!isConnected() || strlen(logMessage) > 512)
if (!isConnected() || length > 512)
return;
logRadio.notify(logMessage);
if (logRadio.indicateEnabled())
logRadio.indicate(logMessage, (uint16_t)length);
else
logRadio.notify(logMessage, (uint16_t)length);
}

View File

@@ -13,7 +13,7 @@ class NRF52Bluetooth : BluetoothApi
void clearBonds();
bool isConnected();
int getRssi();
void sendLog(const char *logMessage);
void sendLog(const uint8_t *logMessage, size_t length);
private:
static void onConnectionSecured(uint16_t conn_handle);

View File

@@ -63,7 +63,8 @@ static void initBrownout()
// We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice
}
static const bool useSoftDevice = true; // Set to false for easier debugging
// This is a public global so that the debugger can set it to false automatically from our gdbinit
bool useSoftDevice = true; // Set to false for easier debugging
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
void setBluetoothEnable(bool enable)
@@ -149,13 +150,43 @@ void nrf52Loop()
checkSDEvents();
}
#ifdef USE_SEMIHOSTING
#include <SemihostingStream.h>
/**
* Note: this variable is in BSS and therfore false by default. But the gdbinit
* file will be installing a temporary breakpoint that changes wantSemihost to true.
*/
bool wantSemihost;
/**
* Turn on semihosting if the ICE debugger wants it.
*/
void nrf52InitSemiHosting()
{
if (wantSemihost) {
static SemihostingStream semiStream;
// We must dynamically alloc because the constructor does semihost operations which
// would crash any load not talking to a debugger
semiStream.open();
semiStream.println("Semihosting starts!");
// Redirect our serial output to instead go via the ICE port
console->setDestination(&semiStream);
}
}
#endif
void nrf52Setup()
{
auto why = NRF_POWER->RESETREAS;
uint32_t why = NRF_POWER->RESETREAS;
// per
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpower.html
LOG_DEBUG("Reset reason: 0x%x\n", why);
#ifdef USE_SEMIHOSTING
nrf52InitSemiHosting();
#endif
// Per
// https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse
// This is the recommended setting for Monitor Mode Debugging

View File

@@ -0,0 +1,380 @@
/*
* Copyright (c) Nordic Semiconductor ASA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form, except as embedded into a Nordic
* Semiconductor ASA integrated circuit in a product or a software update for
* such product, must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. Neither the name of Nordic Semiconductor ASA nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* 4. This software, with or without modification, must only be used with a
* Nordic Semiconductor ASA integrated circuit.
*
* 5. Any software provided in binary form under this license must not be reverse
* engineered, decompiled, modified and/or disassembled.
*
* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
@defgroup nrf_sdm_api SoftDevice Manager API
@{
@brief APIs for SoftDevice management.
*/
#ifndef NRF_SDM_H__
#define NRF_SDM_H__
#include "nrf.h"
#include "nrf_error.h"
#include "nrf_error_sdm.h"
#include "nrf_soc.h"
#include "nrf_svc.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @addtogroup NRF_SDM_DEFINES Defines
* @{ */
#ifdef NRFSOC_DOXYGEN
/// Declared in nrf_mbr.h
#define MBR_SIZE 0
#warning test
#endif
/** @brief The major version for the SoftDevice binary distributed with this header file. */
#define SD_MAJOR_VERSION (7)
/** @brief The minor version for the SoftDevice binary distributed with this header file. */
#define SD_MINOR_VERSION (3)
/** @brief The bugfix version for the SoftDevice binary distributed with this header file. */
#define SD_BUGFIX_VERSION (0)
/** @brief The SoftDevice variant of this firmware. */
#define SD_VARIANT_ID 140
/** @brief The full version number for the SoftDevice binary this header file was distributed
* with, as a decimal number in the form Mmmmbbb, where:
* - M is major version (one or more digits)
* - mmm is minor version (three digits)
* - bbb is bugfix version (three digits). */
#define SD_VERSION (SD_MAJOR_VERSION * 1000000 + SD_MINOR_VERSION * 1000 + SD_BUGFIX_VERSION)
/** @brief SoftDevice Manager SVC Base number. */
#define SDM_SVC_BASE 0x10
/** @brief SoftDevice unique string size in bytes. */
#define SD_UNIQUE_STR_SIZE 20
/** @brief Invalid info field. Returned when an info field does not exist. */
#define SDM_INFO_FIELD_INVALID (0)
/** @brief Defines the SoftDevice Information Structure location (address) as an offset from
the start of the SoftDevice (without MBR)*/
#define SOFTDEVICE_INFO_STRUCT_OFFSET (0x2000)
/** @brief Defines the absolute SoftDevice Information Structure location (address) when the
* SoftDevice is installed just above the MBR (the usual case). */
#define SOFTDEVICE_INFO_STRUCT_ADDRESS (SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE)
/** @brief Defines the offset for the SoftDevice Information Structure size value relative to the
* SoftDevice base address. The size value is of type uint8_t. */
#define SD_INFO_STRUCT_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET)
/** @brief Defines the offset for the SoftDevice size value relative to the SoftDevice base address.
* The size value is of type uint32_t. */
#define SD_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x08)
/** @brief Defines the offset for FWID value relative to the SoftDevice base address. The FWID value
* is of type uint16_t. */
#define SD_FWID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x0C)
/** @brief Defines the offset for the SoftDevice ID relative to the SoftDevice base address. The ID
* is of type uint32_t. */
#define SD_ID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10)
/** @brief Defines the offset for the SoftDevice version relative to the SoftDevice base address in
* the same format as @ref SD_VERSION, stored as an uint32_t. */
#define SD_VERSION_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14)
/** @brief Defines the offset for the SoftDevice unique string relative to the SoftDevice base address.
* The SD_UNIQUE_STR is stored as an array of uint8_t. The size of array is @ref SD_UNIQUE_STR_SIZE.
*/
#define SD_UNIQUE_STR_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x18)
/** @brief Defines a macro for retrieving the actual SoftDevice Information Structure size value
* from a given base address. Use @ref MBR_SIZE as the argument when the SoftDevice is
* installed just above the MBR (the usual case). */
#define SD_INFO_STRUCT_SIZE_GET(baseaddr) (*((uint8_t *)((baseaddr) + SD_INFO_STRUCT_SIZE_OFFSET)))
/** @brief Defines a macro for retrieving the actual SoftDevice size value from a given base
* address. Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above
* the MBR (the usual case). */
#define SD_SIZE_GET(baseaddr) (*((uint32_t *)((baseaddr) + SD_SIZE_OFFSET)))
/** @brief Defines the amount of flash that is used by the SoftDevice.
* Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed
* just above the MBR (the usual case).
*/
#define SD_FLASH_SIZE 0x27000
/** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use
* @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual
* case). */
#define SD_FWID_GET(baseaddr) (*((uint16_t *)((baseaddr) + SD_FWID_OFFSET)))
/** @brief Defines a macro for retrieving the actual SoftDevice ID from a given base address. Use
* @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the
* usual case). */
#define SD_ID_GET(baseaddr) \
((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \
? (*((uint32_t *)((baseaddr) + SD_ID_OFFSET))) \
: SDM_INFO_FIELD_INVALID)
/** @brief Defines a macro for retrieving the actual SoftDevice version from a given base address.
* Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR
* (the usual case). */
#define SD_VERSION_GET(baseaddr) \
((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \
? (*((uint32_t *)((baseaddr) + SD_VERSION_OFFSET))) \
: SDM_INFO_FIELD_INVALID)
/** @brief Defines a macro for retrieving the address of SoftDevice unique str based on a given base address.
* Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR
* (the usual case). */
#define SD_UNIQUE_STR_ADDR_GET(baseaddr) \
((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_UNIQUE_STR_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \
? (((uint8_t *)((baseaddr) + SD_UNIQUE_STR_OFFSET))) \
: SDM_INFO_FIELD_INVALID)
/**@defgroup NRF_FAULT_ID_RANGES Fault ID ranges
* @{ */
#define NRF_FAULT_ID_SD_RANGE_START 0x00000000 /**< SoftDevice ID range start. */
#define NRF_FAULT_ID_APP_RANGE_START 0x00001000 /**< Application ID range start. */
/**@} */
/**@defgroup NRF_FAULT_IDS Fault ID types
* @{ */
#define NRF_FAULT_ID_SD_ASSERT \
(NRF_FAULT_ID_SD_RANGE_START + 1) /**< SoftDevice assertion. The info parameter is reserved for future used. */
#define NRF_FAULT_ID_APP_MEMACC \
(NRF_FAULT_ID_APP_RANGE_START + 1) /**< Application invalid memory access. The info parameter will contain 0x00000000, \
in case of SoftDevice RAM access violation. In case of SoftDevice peripheral \
register violation the info parameter will contain the sub-region number of \
PREGION[0], on whose address range the disallowed write access caused the \
memory access fault. */
/**@} */
/** @} */
/** @addtogroup NRF_SDM_ENUMS Enumerations
* @{ */
/**@brief nRF SoftDevice Manager API SVC numbers. */
enum NRF_SD_SVCS {
SD_SOFTDEVICE_ENABLE = SDM_SVC_BASE, /**< ::sd_softdevice_enable */
SD_SOFTDEVICE_DISABLE, /**< ::sd_softdevice_disable */
SD_SOFTDEVICE_IS_ENABLED, /**< ::sd_softdevice_is_enabled */
SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, /**< ::sd_softdevice_vector_table_base_set */
SVC_SDM_LAST /**< Placeholder for last SDM SVC */
};
/** @} */
/** @addtogroup NRF_SDM_DEFINES Defines
* @{ */
/**@defgroup NRF_CLOCK_LF_ACCURACY Clock accuracy
* @{ */
#define NRF_CLOCK_LF_ACCURACY_250_PPM (0) /**< Default: 250 ppm */
#define NRF_CLOCK_LF_ACCURACY_500_PPM (1) /**< 500 ppm */
#define NRF_CLOCK_LF_ACCURACY_150_PPM (2) /**< 150 ppm */
#define NRF_CLOCK_LF_ACCURACY_100_PPM (3) /**< 100 ppm */
#define NRF_CLOCK_LF_ACCURACY_75_PPM (4) /**< 75 ppm */
#define NRF_CLOCK_LF_ACCURACY_50_PPM (5) /**< 50 ppm */
#define NRF_CLOCK_LF_ACCURACY_30_PPM (6) /**< 30 ppm */
#define NRF_CLOCK_LF_ACCURACY_20_PPM (7) /**< 20 ppm */
#define NRF_CLOCK_LF_ACCURACY_10_PPM (8) /**< 10 ppm */
#define NRF_CLOCK_LF_ACCURACY_5_PPM (9) /**< 5 ppm */
#define NRF_CLOCK_LF_ACCURACY_2_PPM (10) /**< 2 ppm */
#define NRF_CLOCK_LF_ACCURACY_1_PPM (11) /**< 1 ppm */
/** @} */
/**@defgroup NRF_CLOCK_LF_SRC Possible LFCLK oscillator sources
* @{ */
#define NRF_CLOCK_LF_SRC_RC (0) /**< LFCLK RC oscillator. */
#define NRF_CLOCK_LF_SRC_XTAL (1) /**< LFCLK crystal oscillator. */
#define NRF_CLOCK_LF_SRC_SYNTH (2) /**< LFCLK Synthesized from HFCLK. */
/** @} */
/** @} */
/** @addtogroup NRF_SDM_TYPES Types
* @{ */
/**@brief Type representing LFCLK oscillator source. */
typedef struct {
uint8_t source; /**< LF oscillator clock source, see @ref NRF_CLOCK_LF_SRC. */
uint8_t rc_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: Calibration timer interval in 1/4 second
units (nRF52: 1-32).
@note To avoid excessive clock drift, 0.5 degrees Celsius is the
maximum temperature change allowed in one calibration timer
interval. The interval should be selected to ensure this.
@note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. */
uint8_t rc_temp_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: How often (in number of calibration
intervals) the RC oscillator shall be calibrated if the temperature
hasn't changed.
0: Always calibrate even if the temperature hasn't changed.
1: Only calibrate if the temperature has changed (legacy - nRF51 only).
2-33: Check the temperature and only calibrate if it has changed,
however calibration will take place every rc_temp_ctiv
intervals in any case.
@note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC.
@note For nRF52, the application must ensure calibration at least once
every 8 seconds to ensure +/-500 ppm clock stability. The
recommended configuration for ::NRF_CLOCK_LF_SRC_RC on nRF52 is
rc_ctiv=16 and rc_temp_ctiv=2. This will ensure calibration at
least once every 8 seconds and for temperature changes of 0.5
degrees Celsius every 4 seconds. See the Product Specification
for the nRF52 device being used for more information.*/
uint8_t accuracy; /**< External clock accuracy used in the LL to compute timing
windows, see @ref NRF_CLOCK_LF_ACCURACY.*/
} nrf_clock_lf_cfg_t;
/**@brief Fault Handler type.
*
* When certain unrecoverable errors occur within the application or SoftDevice the fault handler will be called back.
* The protocol stack will be in an undefined state when this happens and the only way to recover will be to
* perform a reset, using e.g. CMSIS NVIC_SystemReset().
* If the application returns from the fault handler the SoftDevice will call NVIC_SystemReset().
*
* @note It is recommended to either perform a reset in the fault handler or to let the SoftDevice reset the device.
* Otherwise SoC peripherals may behave in an undefined way. For example, the RADIO peripherial may
* continously transmit packets.
*
* @note This callback is executed in HardFault context, thus SVC functions cannot be called from the fault callback.
*
* @param[in] id Fault identifier. See @ref NRF_FAULT_IDS.
* @param[in] pc The program counter of the instruction that triggered the fault.
* @param[in] info Optional additional information regarding the fault. Refer to each Fault identifier for details.
*
* @note When id is set to @ref NRF_FAULT_ID_APP_MEMACC, pc will contain the address of the instruction being executed at the time
* when the fault is detected by the CPU. The CPU program counter may have advanced up to 2 instructions (no branching) after the
* one that triggered the fault.
*/
typedef void (*nrf_fault_handler_t)(uint32_t id, uint32_t pc, uint32_t info);
/** @} */
/** @addtogroup NRF_SDM_FUNCTIONS Functions
* @{ */
/**@brief Enables the SoftDevice and by extension the protocol stack.
*
* @note Some care must be taken if a low frequency clock source is already running when calling this function:
* If the LF clock has a different source then the one currently running, it will be stopped. Then, the new
* clock source will be started.
*
* @note This function has no effect when returning with an error.
*
* @post If return code is ::NRF_SUCCESS
* - SoC library and protocol stack APIs are made available.
* - A portion of RAM will be unavailable (see relevant SDS documentation).
* - Some peripherals will be unavailable or available only through the SoC API (see relevant SDS documentation).
* - Interrupts will not arrive from protected peripherals or interrupts.
* - nrf_nvic_ functions must be used instead of CMSIS NVIC_ functions for reliable usage of the SoftDevice.
* - Interrupt latency may be affected by the SoftDevice (see relevant SDS documentation).
* - Chosen low frequency clock source will be running.
*
* @param p_clock_lf_cfg Low frequency clock source and accuracy.
If NULL the clock will be configured as an RC source with rc_ctiv = 16 and .rc_temp_ctiv = 2
In the case of XTAL source, the PPM accuracy of the chosen clock source must be greater than or equal to
the actual characteristics of your XTAL clock.
* @param fault_handler Callback to be invoked in case of fault, cannot be NULL.
*
* @retval ::NRF_SUCCESS
* @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied.
* @retval ::NRF_ERROR_INVALID_STATE SoftDevice is already enabled, and the clock source and fault handler cannot be updated.
* @retval ::NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION SoftDevice interrupt is already enabled, or an enabled interrupt has
an illegal priority level.
* @retval ::NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN Unknown low frequency clock source selected.
* @retval ::NRF_ERROR_INVALID_PARAM Invalid clock source configuration supplied in p_clock_lf_cfg.
*/
SVCALL(SD_SOFTDEVICE_ENABLE, uint32_t,
sd_softdevice_enable(nrf_clock_lf_cfg_t const *p_clock_lf_cfg, nrf_fault_handler_t fault_handler));
/**@brief Disables the SoftDevice and by extension the protocol stack.
*
* Idempotent function to disable the SoftDevice.
*
* @post SoC library and protocol stack APIs are made unavailable.
* @post All interrupts that was protected by the SoftDevice will be disabled and initialized to priority 0 (highest).
* @post All peripherals used by the SoftDevice will be reset to default values.
* @post All of RAM become available.
* @post All interrupts are forwarded to the application.
* @post LFCLK source chosen in ::sd_softdevice_enable will be left running.
*
* @retval ::NRF_SUCCESS
*/
SVCALL(SD_SOFTDEVICE_DISABLE, uint32_t, sd_softdevice_disable(void));
/**@brief Check if the SoftDevice is enabled.
*
* @param[out] p_softdevice_enabled If the SoftDevice is enabled: 1 else 0.
*
* @retval ::NRF_SUCCESS
*/
SVCALL(SD_SOFTDEVICE_IS_ENABLED, uint32_t, sd_softdevice_is_enabled(uint8_t *p_softdevice_enabled));
/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the SoftDevice
*
* This function is only intended to be called when a bootloader is enabled.
*
* @param[in] address The base address of the interrupt vector table for forwarded interrupts.
* @retval ::NRF_SUCCESS
*/
SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table_base_set(uint32_t address));
/** @} */
#ifdef __cplusplus
}
#endif
#endif // NRF_SDM_H__
/**
@}
*/

View File

@@ -38,7 +38,7 @@ void powerCommandsCheck()
#if defined(ARCH_ESP32) || defined(ARCH_NRF52)
if (shutdownAtMsec) {
screen->startShutdownScreen();
screen->startAlert("Shutting down...");
}
#endif

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