Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a3afc756a | ||
|
|
9d222c9a55 | ||
|
|
2dd4ca8c52 | ||
|
|
2a50ae05dd |
@@ -20,7 +20,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \
|
||||
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \
|
||||
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev libsdl2-dev && \
|
||||
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||
pip install --no-cache-dir -U \
|
||||
platformio==6.1.16 \
|
||||
|
||||
@@ -96,7 +96,6 @@ jobs:
|
||||
pio_platform: ${{ matrix.check.platform }}
|
||||
pio_env: ${{ matrix.check.board }}
|
||||
pio_target: check
|
||||
pio_opts: --fail-on-defect=low
|
||||
|
||||
build:
|
||||
needs: [setup, version]
|
||||
|
||||
@@ -33,7 +33,6 @@ __pycache__
|
||||
*~
|
||||
|
||||
venv/
|
||||
.venv/
|
||||
release/
|
||||
.vscode/extensions.json
|
||||
/compile_commands.json
|
||||
|
||||
+2
-2
@@ -9,14 +9,14 @@ plugins:
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.501
|
||||
- renovate@43.10.3
|
||||
- renovate@43.8.5
|
||||
- prettier@3.8.1
|
||||
- trufflehog@3.93.3
|
||||
- yamllint@1.38.0
|
||||
- bandit@1.9.3
|
||||
- trivy@0.69.1
|
||||
- taplo@0.10.0
|
||||
- ruff@0.15.1
|
||||
- ruff@0.15.0
|
||||
- isort@7.0.0
|
||||
- markdownlint@0.47.0
|
||||
- oxipng@10.1.0
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
curl wget g++ zip git ca-certificates pkg-config \
|
||||
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
||||
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& pip install --no-cache-dir -U platformio \
|
||||
&& mkdir /tmp/firmware
|
||||
@@ -53,7 +53,7 @@ USER root
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install \
|
||||
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
|
||||
liborcania2.3 libulfius2.7t64 libssl3t64 \
|
||||
libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 \
|
||||
libx11-6 libinput10 libxkbcommon-x11-0 \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ RUN apk --no-cache add \
|
||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
|
||||
libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
||||
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& pip install --no-cache-dir -U platformio \
|
||||
&& mkdir /tmp/firmware
|
||||
@@ -42,7 +42,7 @@ USER root
|
||||
|
||||
RUN apk --no-cache add \
|
||||
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
|
||||
i2c-tools libuv libx11 libinput libxkbcommon sdl2 \
|
||||
i2c-tools libuv libx11 libinput libxkbcommon \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=0"
|
||||
],
|
||||
|
||||
Vendored
+1
-2
@@ -26,8 +26,7 @@ Build-Depends: debhelper-compat (= 13),
|
||||
libx11-dev,
|
||||
libinput-dev,
|
||||
libxkbcommon-x11-dev,
|
||||
libsqlite3-dev,
|
||||
libsdl2-dev
|
||||
libsqlite3-dev
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/meshtastic/firmware
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@@ -49,7 +49,6 @@ BuildRequires: pkgconfig(libulfius)
|
||||
BuildRequires: pkgconfig(x11)
|
||||
BuildRequires: pkgconfig(libinput)
|
||||
BuildRequires: pkgconfig(xkbcommon-x11)
|
||||
BuildRequires: pkgconfig(sdl2)
|
||||
|
||||
# libbsd is needed on older Fedora/RHEL to provide 'strlcpy'
|
||||
%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10
|
||||
@@ -60,14 +59,8 @@ BuildRequires: pkgconfig(libbsd-overlay)
|
||||
|
||||
Requires: systemd-udev
|
||||
|
||||
# Declare that this package provides the user/group it creates in %pre
|
||||
# Required for Fedora 43+ which tracks users/groups as RPM dependencies
|
||||
Provides: user(%{meshtasticd_user})
|
||||
Provides: group(%{meshtasticd_user})
|
||||
Provides: group(spi)
|
||||
|
||||
%description
|
||||
Meshtastic daemon. Meshtastic is an off-grid
|
||||
Meshtastic daemon for controlling Meshtastic devices. Meshtastic is an off-grid
|
||||
text communication platform that uses inexpensive LoRa radios.
|
||||
|
||||
%prep
|
||||
@@ -158,7 +151,6 @@ fi
|
||||
%license LICENSE
|
||||
%doc README.md
|
||||
%{_bindir}/meshtasticd
|
||||
%{_bindir}/meshtasticd-start.sh
|
||||
%dir %{_localstatedir}/lib/meshtasticd
|
||||
%{_udevrulesdir}/99-meshtasticd-udev.rules
|
||||
%dir %{_sysconfdir}/meshtasticd
|
||||
|
||||
+35
-25
@@ -50,13 +50,11 @@ build_flags = -Wno-missing-field-initializers
|
||||
-DRADIOLIB_EXCLUDE_APRS=1
|
||||
-DRADIOLIB_EXCLUDE_LORAWAN=1
|
||||
-DMESHTASTIC_EXCLUDE_DROPZONE=1
|
||||
-DMESHTASTIC_EXCLUDE_REPLYBOT=1
|
||||
-DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1
|
||||
-DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
|
||||
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
|
||||
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
|
||||
-DMESHTASTIC_EXCLUDE_POWERMON=1
|
||||
-DMESHTASTIC_EXCLUDE_STATUS=1
|
||||
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
|
||||
-DLED_BUILTIN=-1
|
||||
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
|
||||
@@ -117,7 +115,7 @@ lib_deps =
|
||||
[radiolib_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||
jgromes/RadioLib@7.6.0
|
||||
jgromes/RadioLib@7.5.0
|
||||
|
||||
[device-ui_base]
|
||||
lib_deps =
|
||||
@@ -184,8 +182,40 @@ lib_deps =
|
||||
# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/library/BH1750_WE
|
||||
wollewald/BH1750_WE@1.1.10
|
||||
|
||||
; Common environmental sensor libraries (not included in native / portduino)
|
||||
[environmental_extra_common]
|
||||
; (not included in native / portduino)
|
||||
[environmental_extra]
|
||||
lib_deps =
|
||||
# renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library
|
||||
adafruit/Adafruit BMP3XX Library@2.1.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X
|
||||
adafruit/Adafruit MAX1704X@1.0.3
|
||||
# renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library
|
||||
adafruit/Adafruit SHTC3 Library@1.0.2
|
||||
# renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X
|
||||
adafruit/Adafruit LPS2X@2.0.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library
|
||||
adafruit/Adafruit SHT31 Library@2.2.2
|
||||
# renovate: datasource=custom.pio depName=Adafruit VEML7700 packageName=adafruit/library/Adafruit VEML7700 Library
|
||||
adafruit/Adafruit VEML7700 Library@2.1.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit SHT4x packageName=adafruit/library/Adafruit SHT4x Library
|
||||
adafruit/Adafruit SHT4x Library@1.0.5
|
||||
# renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library
|
||||
sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6
|
||||
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
|
||||
closedcube/ClosedCube OPT3001@1.1.2
|
||||
# renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2
|
||||
boschsensortec/bsec2@1.10.2610
|
||||
# renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library
|
||||
boschsensortec/BME68x Sensor Library@1.3.40408
|
||||
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
|
||||
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
|
||||
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
|
||||
sensirion/Sensirion Core@0.7.3
|
||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
||||
sensirion/Sensirion I2C SCD4x@1.1.0
|
||||
|
||||
; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
|
||||
[environmental_extra_no_bsec]
|
||||
lib_deps =
|
||||
# renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library
|
||||
adafruit/Adafruit BMP3XX Library@2.1.6
|
||||
@@ -211,23 +241,3 @@ lib_deps =
|
||||
sensirion/Sensirion Core@0.7.3
|
||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
||||
sensirion/Sensirion I2C SCD4x@1.1.0
|
||||
# renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x
|
||||
sensirion/Sensirion I2C SFA3x@1.0.0
|
||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD30 packageName=sensirion/library/Sensirion I2C SCD30
|
||||
sensirion/Sensirion I2C SCD30@1.0.0
|
||||
|
||||
; Environmental sensors with BSEC2 (Bosch proprietary IAQ)
|
||||
[environmental_extra]
|
||||
lib_deps =
|
||||
${environmental_extra_common.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2
|
||||
boschsensortec/bsec2@1.10.2610
|
||||
# renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library
|
||||
boschsensortec/BME68x Sensor Library@1.3.40408
|
||||
|
||||
; Environmental sensors without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
|
||||
[environmental_extra_no_bsec]
|
||||
lib_deps =
|
||||
${environmental_extra_common.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680
|
||||
adafruit/Adafruit BME680 Library@^2.0.5
|
||||
|
||||
+1
-1
Submodule protobufs updated: 44298d374f...e1a6b3a868
+59
-68
@@ -1,23 +1,19 @@
|
||||
#ifndef AMBIENTLIGHTINGTHREAD_H
|
||||
#define AMBIENTLIGHTINGTHREAD_H
|
||||
|
||||
#include "Observer.h"
|
||||
#include "configuration.h"
|
||||
#include "detect/ScanI2C.h"
|
||||
#include "sleep.h"
|
||||
|
||||
#ifdef HAS_NCP5623
|
||||
#include <Wire.h>
|
||||
|
||||
#include <NCP5623.h>
|
||||
#include <graphics/RAKled.h>
|
||||
NCP5623 rgb;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_LP5562
|
||||
#include <graphics/NomadStarLED.h>
|
||||
LP5562 rgbw;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_NEOPIXEL
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
#include <graphics/NeoPixel.h>
|
||||
Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE);
|
||||
#endif
|
||||
|
||||
#ifdef UNPHONE
|
||||
@@ -25,24 +21,10 @@
|
||||
extern unPhone unphone;
|
||||
#endif
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
class AmbientLightingThread : public concurrency::OSThread
|
||||
{
|
||||
friend class StatusLEDModule; // Let the LEDStatusModule trigger the ambient lighting for notifications and battery status.
|
||||
friend class ExternalNotificationModule; // Let the ExternalNotificationModule trigger the ambient lighting for notifications.
|
||||
|
||||
private:
|
||||
#ifdef HAS_NCP5623
|
||||
NCP5623 rgb;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_LP5562
|
||||
LP5562 rgbw;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_NEOPIXEL
|
||||
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE);
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit AmbientLightingThread(ScanI2C::DeviceType type) : OSThread("AmbientLighting")
|
||||
{
|
||||
@@ -54,15 +36,14 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
moduleConfig.ambient_lighting.led_state = true;
|
||||
#endif
|
||||
#endif
|
||||
#if AMBIENT_LIGHTING_TEST
|
||||
// define to enable test
|
||||
moduleConfig.ambient_lighting.led_state = true;
|
||||
moduleConfig.ambient_lighting.current = 10;
|
||||
// Uncomment to test module
|
||||
// moduleConfig.ambient_lighting.led_state = true;
|
||||
// moduleConfig.ambient_lighting.current = 10;
|
||||
// Default to a color based on our node number
|
||||
moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16;
|
||||
moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8;
|
||||
moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF;
|
||||
#endif
|
||||
// moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16;
|
||||
// moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8;
|
||||
// moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF;
|
||||
|
||||
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
|
||||
_type = type;
|
||||
if (_type == ScanI2C::DeviceType::NONE) {
|
||||
@@ -72,6 +53,11 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_RGB_LED
|
||||
if (!moduleConfig.ambient_lighting.led_state) {
|
||||
LOG_DEBUG("AmbientLighting Disable due to moduleConfig.ambient_lighting.led_state OFF");
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("AmbientLighting init");
|
||||
#ifdef HAS_NCP5623
|
||||
if (_type == ScanI2C::NCP5623) {
|
||||
@@ -91,13 +77,7 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
pixels.clear(); // Set all pixel colors to 'off'
|
||||
pixels.setBrightness(moduleConfig.ambient_lighting.current);
|
||||
#endif
|
||||
if (!moduleConfig.ambient_lighting.led_state) {
|
||||
LOG_DEBUG("AmbientLighting Disable due to moduleConfig.ambient_lighting.led_state OFF");
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
setLighting(moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
setLighting();
|
||||
#endif
|
||||
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
|
||||
}
|
||||
@@ -111,8 +91,7 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
|
||||
if ((_type == ScanI2C::NCP5623 || _type == ScanI2C::LP5562) && moduleConfig.ambient_lighting.led_state) {
|
||||
#endif
|
||||
setLighting(moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
setLighting();
|
||||
return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification
|
||||
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
|
||||
}
|
||||
@@ -169,53 +148,65 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
void setLighting(float current, uint8_t red, uint8_t green, uint8_t blue)
|
||||
void setLighting()
|
||||
{
|
||||
#ifdef HAS_NCP5623
|
||||
rgb.setCurrent(current);
|
||||
rgb.setRed(red);
|
||||
rgb.setGreen(green);
|
||||
rgb.setBlue(blue);
|
||||
LOG_DEBUG("Init NCP5623 Ambient light w/ current=%f, red=%d, green=%d, blue=%d", current, red, green, blue);
|
||||
rgb.setCurrent(moduleConfig.ambient_lighting.current);
|
||||
rgb.setRed(moduleConfig.ambient_lighting.red);
|
||||
rgb.setGreen(moduleConfig.ambient_lighting.green);
|
||||
rgb.setBlue(moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init NCP5623 Ambient light w/ current=%d, red=%d, green=%d, blue=%d",
|
||||
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef HAS_LP5562
|
||||
rgbw.setCurrent(current);
|
||||
rgbw.setRed(red);
|
||||
rgbw.setGreen(green);
|
||||
rgbw.setBlue(blue);
|
||||
LOG_DEBUG("Init LP5562 Ambient light w/ current=%f, red=%d, green=%d, blue=%d", current, red, green, blue);
|
||||
rgbw.setCurrent(moduleConfig.ambient_lighting.current);
|
||||
rgbw.setRed(moduleConfig.ambient_lighting.red);
|
||||
rgbw.setGreen(moduleConfig.ambient_lighting.green);
|
||||
rgbw.setBlue(moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init LP5562 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.current,
|
||||
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef HAS_NEOPIXEL
|
||||
pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT);
|
||||
pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
|
||||
moduleConfig.ambient_lighting.blue),
|
||||
0, NEOPIXEL_COUNT);
|
||||
|
||||
// RadioMaster Bandit has addressable LED at the two buttons
|
||||
// this allow us to set different lighting for them in variant.h file.
|
||||
#ifdef RADIOMASTER_900_BANDIT
|
||||
#if defined(BUTTON1_COLOR) && defined(BUTTON1_COLOR_INDEX)
|
||||
pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1);
|
||||
#endif
|
||||
#if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX)
|
||||
pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1);
|
||||
#endif
|
||||
#endif
|
||||
pixels.show();
|
||||
// LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%f, red=%d, green=%d, blue=%d",
|
||||
// current, red, green, blue);
|
||||
// LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
|
||||
// moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
|
||||
// moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef RGBLED_CA
|
||||
analogWrite(RGBLED_RED, 255 - red);
|
||||
analogWrite(RGBLED_GREEN, 255 - green);
|
||||
analogWrite(RGBLED_BLUE, 255 - blue);
|
||||
LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", red, green, blue);
|
||||
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
|
||||
analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green);
|
||||
analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#elif defined(RGBLED_RED)
|
||||
analogWrite(RGBLED_RED, red);
|
||||
analogWrite(RGBLED_GREEN, green);
|
||||
analogWrite(RGBLED_BLUE, blue);
|
||||
LOG_DEBUG("Init Ambient light RGB Common Cathode w/ red=%d, green=%d, blue=%d", red, green, blue);
|
||||
analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red);
|
||||
analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green);
|
||||
analogWrite(RGBLED_BLUE, moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init Ambient light RGB Common Cathode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef UNPHONE
|
||||
unphone.rgb(red, green, blue);
|
||||
LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", red, green, blue);
|
||||
unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
|
||||
moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
#endif // AMBIENTLIGHTINGTHREAD_H
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
+13
-10
@@ -4,7 +4,6 @@
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "sleep.h"
|
||||
#include <memory>
|
||||
|
||||
#ifdef HAS_I2S
|
||||
#include <AudioFileSourcePROGMEM.h>
|
||||
@@ -30,9 +29,9 @@ class AudioThread : public concurrency::OSThread
|
||||
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
|
||||
#endif
|
||||
setCPUFast(true);
|
||||
rtttlFile = std::unique_ptr<AudioFileSourcePROGMEM>(new AudioFileSourcePROGMEM(data, len));
|
||||
i2sRtttl = std::unique_ptr<AudioGeneratorRTTTL>(new AudioGeneratorRTTTL());
|
||||
i2sRtttl->begin(rtttlFile.get(), audioOut.get());
|
||||
rtttlFile = new AudioFileSourcePROGMEM(data, len);
|
||||
i2sRtttl = new AudioGeneratorRTTTL();
|
||||
i2sRtttl->begin(rtttlFile, audioOut);
|
||||
}
|
||||
|
||||
// Also handles actually playing the RTTTL, needs to be called in loop
|
||||
@@ -48,10 +47,12 @@ class AudioThread : public concurrency::OSThread
|
||||
{
|
||||
if (i2sRtttl != nullptr) {
|
||||
i2sRtttl->stop();
|
||||
delete i2sRtttl;
|
||||
i2sRtttl = nullptr;
|
||||
}
|
||||
|
||||
if (rtttlFile != nullptr) {
|
||||
delete rtttlFile;
|
||||
rtttlFile = nullptr;
|
||||
}
|
||||
|
||||
@@ -65,14 +66,16 @@ class AudioThread : public concurrency::OSThread
|
||||
{
|
||||
if (i2sRtttl != nullptr) {
|
||||
i2sRtttl->stop();
|
||||
delete i2sRtttl;
|
||||
i2sRtttl = nullptr;
|
||||
}
|
||||
|
||||
#ifdef T_LORA_PAGER
|
||||
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
|
||||
#endif
|
||||
auto sam = std::unique_ptr<ESP8266SAM>(new ESP8266SAM);
|
||||
sam->Say(audioOut.get(), text);
|
||||
ESP8266SAM *sam = new ESP8266SAM;
|
||||
sam->Say(audioOut, text);
|
||||
delete sam;
|
||||
setCPUFast(false);
|
||||
#ifdef T_LORA_PAGER
|
||||
io.digitalWrite(EXPANDS_AMP_EN, LOW);
|
||||
@@ -93,15 +96,15 @@ class AudioThread : public concurrency::OSThread
|
||||
private:
|
||||
void initOutput()
|
||||
{
|
||||
audioOut = std::unique_ptr<AudioOutputI2S>(new AudioOutputI2S(1, AudioOutputI2S::EXTERNAL_I2S));
|
||||
audioOut = new AudioOutputI2S(1, AudioOutputI2S::EXTERNAL_I2S);
|
||||
audioOut->SetPinout(DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT, DAC_I2S_MCLK);
|
||||
audioOut->SetGain(0.2);
|
||||
};
|
||||
|
||||
std::unique_ptr<AudioGeneratorRTTTL> i2sRtttl = nullptr;
|
||||
std::unique_ptr<AudioOutputI2S> audioOut = nullptr;
|
||||
AudioGeneratorRTTTL *i2sRtttl = nullptr;
|
||||
AudioOutputI2S *audioOut = nullptr;
|
||||
|
||||
std::unique_ptr<AudioFileSourcePROGMEM> rtttlFile = nullptr;
|
||||
AudioFileSourcePROGMEM *rtttlFile = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+10
-2
@@ -89,14 +89,22 @@ class BluetoothStatus : public Status
|
||||
case ConnectionState::CONNECTED:
|
||||
LOG_DEBUG("BluetoothStatus CONNECTED");
|
||||
#ifdef BLE_LED
|
||||
digitalWrite(BLE_LED, LED_STATE_ON);
|
||||
#ifdef BLE_LED_INVERTED
|
||||
digitalWrite(BLE_LED, LOW);
|
||||
#else
|
||||
digitalWrite(BLE_LED, HIGH);
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
|
||||
case ConnectionState::DISCONNECTED:
|
||||
LOG_DEBUG("BluetoothStatus DISCONNECTED");
|
||||
#ifdef BLE_LED
|
||||
digitalWrite(BLE_LED, LED_STATE_OFF);
|
||||
#ifdef BLE_LED_INVERTED
|
||||
digitalWrite(BLE_LED, HIGH);
|
||||
#else
|
||||
digitalWrite(BLE_LED, LOW);
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
#include "Led.h"
|
||||
#include "PowerMon.h"
|
||||
#include "main.h"
|
||||
#include "power.h"
|
||||
|
||||
GpioVirtPin ledForceOn, ledBlink;
|
||||
|
||||
#if defined(LED_PIN)
|
||||
// Most boards have a GPIO for LED control
|
||||
static GpioHwPin ledRawHwPin(LED_PIN);
|
||||
#else
|
||||
static GpioVirtPin ledRawHwPin; // Dummy pin for no hardware
|
||||
#endif
|
||||
|
||||
#if LED_STATE_ON == 0
|
||||
static GpioVirtPin ledHwPin;
|
||||
static GpioNotTransformer ledInverter(&ledHwPin, &ledRawHwPin);
|
||||
#else
|
||||
static GpioPin &ledHwPin = ledRawHwPin;
|
||||
#endif
|
||||
|
||||
#if defined(HAS_PMU)
|
||||
/**
|
||||
* A GPIO controlled by the PMU
|
||||
*/
|
||||
class GpioPmuPin : public GpioPin
|
||||
{
|
||||
public:
|
||||
void set(bool value)
|
||||
{
|
||||
if (pmu_found && PMU) {
|
||||
// blink the axp led
|
||||
PMU->setChargingLedMode(value ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF);
|
||||
}
|
||||
}
|
||||
} ledPmuHwPin;
|
||||
|
||||
// In some cases we need to drive a PMU LED and a normal LED
|
||||
static GpioSplitter ledFinalPin(&ledHwPin, &ledPmuHwPin);
|
||||
#else
|
||||
static GpioPin &ledFinalPin = ledHwPin;
|
||||
#endif
|
||||
|
||||
#ifdef USE_POWERMON
|
||||
/**
|
||||
* We monitor changes to the LED drive output because we use that as a sanity test in our power monitor stuff.
|
||||
*/
|
||||
class MonitoredLedPin : public GpioPin
|
||||
{
|
||||
public:
|
||||
void set(bool value)
|
||||
{
|
||||
if (powerMon) {
|
||||
if (value)
|
||||
powerMon->setState(meshtastic_PowerMon_State_LED_On);
|
||||
else
|
||||
powerMon->clearState(meshtastic_PowerMon_State_LED_On);
|
||||
}
|
||||
ledFinalPin.set(value);
|
||||
}
|
||||
} monitoredLedPin;
|
||||
#else
|
||||
static GpioPin &monitoredLedPin = ledFinalPin;
|
||||
#endif
|
||||
|
||||
static GpioBinaryTransformer ledForcer(&ledForceOn, &ledBlink, &monitoredLedPin, GpioBinaryTransformer::Or);
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "GpioLogic.h"
|
||||
#include "configuration.h"
|
||||
|
||||
/**
|
||||
* ledForceOn and ledForceOff both override the normal ledBlinker behavior (which is controlled by main)
|
||||
*/
|
||||
extern GpioVirtPin ledForceOn, ledBlink;
|
||||
+12
-99
@@ -459,8 +459,6 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
}
|
||||
// if it's not HIGH - check the battery
|
||||
#endif
|
||||
// If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it.
|
||||
return false;
|
||||
|
||||
// technically speaking this should work for all(?) NRF52 boards
|
||||
// but needs testing across multiple devices. NRF52 USB would not even work if
|
||||
@@ -692,9 +690,7 @@ bool Power::setup()
|
||||
bool found = false;
|
||||
if (axpChipInit()) {
|
||||
found = true;
|
||||
} else if (cw2015Init()) {
|
||||
found = true;
|
||||
} else if (max17048Init()) {
|
||||
} else if (lipoInit()) {
|
||||
found = true;
|
||||
} else if (lipoChargerInit()) {
|
||||
found = true;
|
||||
@@ -704,11 +700,11 @@ bool Power::setup()
|
||||
found = true;
|
||||
} else if (analogInit()) {
|
||||
found = true;
|
||||
} else {
|
||||
#ifdef NRF_APM
|
||||
found = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef NRF_APM
|
||||
found = true;
|
||||
#endif
|
||||
#ifdef EXT_PWR_DETECT
|
||||
attachInterrupt(
|
||||
EXT_PWR_DETECT,
|
||||
@@ -846,10 +842,8 @@ void Power::readPowerStatus()
|
||||
|
||||
if (batteryLevel) {
|
||||
hasBattery = batteryLevel->isBatteryConnect() ? OptTrue : OptFalse;
|
||||
#ifndef NRF_APM
|
||||
usbPowered = batteryLevel->isVbusIn() ? OptTrue : OptFalse;
|
||||
isChargingNow = batteryLevel->isCharging() ? OptTrue : OptFalse;
|
||||
#endif
|
||||
if (hasBattery) {
|
||||
batteryVoltageMv = batteryLevel->getBattVoltage();
|
||||
// If the AXP192 returns a valid battery percentage, use it
|
||||
@@ -1325,7 +1319,7 @@ bool Power::axpChipInit()
|
||||
/**
|
||||
* Wrapper class for an I2C MAX17048 Lipo battery sensor.
|
||||
*/
|
||||
class MAX17048BatteryLevel : public HasBatteryLevel
|
||||
class LipoBatteryLevel : public HasBatteryLevel
|
||||
{
|
||||
private:
|
||||
MAX17048Singleton *max17048 = nullptr;
|
||||
@@ -1373,18 +1367,18 @@ class MAX17048BatteryLevel : public HasBatteryLevel
|
||||
virtual bool isCharging() override { return max17048->isBatteryCharging(); }
|
||||
};
|
||||
|
||||
MAX17048BatteryLevel max17048Level;
|
||||
LipoBatteryLevel lipoLevel;
|
||||
|
||||
/**
|
||||
* Init the Lipo battery level sensor
|
||||
*/
|
||||
bool Power::max17048Init()
|
||||
bool Power::lipoInit()
|
||||
{
|
||||
bool result = max17048Level.runOnce();
|
||||
LOG_DEBUG("Power::max17048Init lipo sensor is %s", result ? "ready" : "not ready yet");
|
||||
bool result = lipoLevel.runOnce();
|
||||
LOG_DEBUG("Power::lipoInit lipo sensor is %s", result ? "ready" : "not ready yet");
|
||||
if (!result)
|
||||
return false;
|
||||
batteryLevel = &max17048Level;
|
||||
batteryLevel = &lipoLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1392,88 +1386,7 @@ bool Power::max17048Init()
|
||||
/**
|
||||
* The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel
|
||||
*/
|
||||
bool Power::max17048Init()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C && HAS_CW2015
|
||||
|
||||
class CW2015BatteryLevel : public AnalogBatteryLevel
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||
*/
|
||||
virtual int getBatteryPercent() override
|
||||
{
|
||||
int data = -1;
|
||||
Wire.beginTransmission(CW2015_ADDR);
|
||||
Wire.write(0x04);
|
||||
if (Wire.endTransmission() == 0) {
|
||||
if (Wire.requestFrom(CW2015_ADDR, (uint8_t)1)) {
|
||||
data = Wire.read();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||||
*/
|
||||
virtual uint16_t getBattVoltage() override
|
||||
{
|
||||
uint16_t mv = 0;
|
||||
Wire.beginTransmission(CW2015_ADDR);
|
||||
Wire.write(0x02);
|
||||
if (Wire.endTransmission() == 0) {
|
||||
if (Wire.requestFrom(CW2015_ADDR, (uint8_t)2)) {
|
||||
mv = Wire.read();
|
||||
mv <<= 8;
|
||||
mv |= Wire.read();
|
||||
// Voltage is read in 305uV units, convert to mV
|
||||
mv = mv * 305 / 1000;
|
||||
}
|
||||
}
|
||||
return mv;
|
||||
}
|
||||
};
|
||||
|
||||
CW2015BatteryLevel cw2015Level;
|
||||
|
||||
/**
|
||||
* Init the CW2015 battery level sensor
|
||||
*/
|
||||
bool Power::cw2015Init()
|
||||
{
|
||||
|
||||
Wire.beginTransmission(CW2015_ADDR);
|
||||
uint8_t getInfo[] = {0x0a, 0x00};
|
||||
Wire.write(getInfo, 2);
|
||||
Wire.endTransmission();
|
||||
delay(10);
|
||||
Wire.beginTransmission(CW2015_ADDR);
|
||||
Wire.write(0x00);
|
||||
bool result = false;
|
||||
if (Wire.endTransmission() == 0) {
|
||||
if (Wire.requestFrom(CW2015_ADDR, (uint8_t)1)) {
|
||||
uint8_t data = Wire.read();
|
||||
LOG_DEBUG("CW2015 init read data: 0x%x", data);
|
||||
if (data == 0x73) {
|
||||
result = true;
|
||||
batteryLevel = &cw2015Level;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
/**
|
||||
* The CW2015 battery level sensor is unavailable - default to AnalogBatteryLevel
|
||||
*/
|
||||
bool Power::cw2015Init()
|
||||
bool Power::lipoInit()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
+4
-4
@@ -9,13 +9,13 @@
|
||||
*/
|
||||
#include "PowerFSM.h"
|
||||
#include "Default.h"
|
||||
#include "Led.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerMon.h"
|
||||
#include "configuration.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#include "modules/StatusLEDModule.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
|
||||
@@ -103,7 +103,7 @@ static void lsIdle()
|
||||
uint32_t sleepTime = SLEEP_TIME;
|
||||
|
||||
powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep);
|
||||
statusLEDModule->setPowerLED(false);
|
||||
ledBlink.set(false); // Never leave led on while in light sleep
|
||||
esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL);
|
||||
powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep);
|
||||
|
||||
@@ -111,7 +111,7 @@ static void lsIdle()
|
||||
case ESP_SLEEP_WAKEUP_TIMER:
|
||||
// Normal case: timer expired, we should just go back to sleep ASAP
|
||||
|
||||
statusLEDModule->setPowerLED(true);
|
||||
ledBlink.set(true); // briefly turn on led
|
||||
wakeCause2 = doLightSleep(100); // leave led on for 1ms
|
||||
|
||||
secsSlept += sleepTime;
|
||||
@@ -146,7 +146,7 @@ static void lsIdle()
|
||||
}
|
||||
} else {
|
||||
// Time to stop sleeping!
|
||||
statusLEDModule->setPowerLED(false);
|
||||
ledBlink.set(false);
|
||||
LOG_INFO("Reached ls_secs, service loop()");
|
||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||
}
|
||||
|
||||
+29
-18
@@ -227,21 +227,34 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_
|
||||
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);
|
||||
vsprintf(logRecord.message, format, arg);
|
||||
strcpy(logRecord.message, message);
|
||||
if (thread)
|
||||
strcpy(logRecord.source, thread->ThreadName.c_str());
|
||||
logRecord.time = getValidTime(RTCQuality::RTCQualityDevice, true);
|
||||
|
||||
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[meshtastic_LogRecord_size]);
|
||||
size_t size = pb_encode_to_bytes(buffer.get(), meshtastic_LogRecord_size, meshtastic_LogRecord_fields, &logRecord);
|
||||
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.get(), size);
|
||||
nimbleBluetooth->sendLog(buffer, size);
|
||||
#elif defined(ARCH_NRF52)
|
||||
nrf52Bluetooth->sendLog(buffer.get(), size);
|
||||
nrf52Bluetooth->sendLog(buffer, size);
|
||||
#endif
|
||||
delete[] message;
|
||||
delete[] buffer;
|
||||
}
|
||||
}
|
||||
#else
|
||||
@@ -279,8 +292,8 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
||||
|
||||
// append \n to format
|
||||
size_t len = strlen(format);
|
||||
auto newFormat = std::unique_ptr<char[]>(new char[len + 2]);
|
||||
strcpy(newFormat.get(), format);
|
||||
char *newFormat = new char[len + 2];
|
||||
strcpy(newFormat, format);
|
||||
newFormat[len] = '\n';
|
||||
newFormat[len + 1] = '\0';
|
||||
|
||||
@@ -297,18 +310,23 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
||||
va_end(arg);
|
||||
}
|
||||
if (portduino_config.logoutputlevel < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
|
||||
delete[] newFormat;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (portduino_config.logoutputlevel < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
|
||||
delete[] newFormat;
|
||||
return;
|
||||
} else if (portduino_config.logoutputlevel < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) {
|
||||
delete[] newFormat;
|
||||
return;
|
||||
} else if (portduino_config.logoutputlevel < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) {
|
||||
delete[] newFormat;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
|
||||
delete[] newFormat;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -320,19 +338,11 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
||||
#endif
|
||||
|
||||
va_list arg;
|
||||
va_list arg_copy;
|
||||
|
||||
va_start(arg, format);
|
||||
|
||||
va_copy(arg_copy, arg);
|
||||
log_to_serial(logLevel, newFormat.get(), arg_copy);
|
||||
va_end(arg_copy);
|
||||
|
||||
va_copy(arg_copy, arg);
|
||||
log_to_syslog(logLevel, newFormat.get(), arg_copy);
|
||||
va_end(arg_copy);
|
||||
|
||||
log_to_ble(logLevel, newFormat.get(), arg);
|
||||
log_to_serial(logLevel, newFormat, arg);
|
||||
log_to_syslog(logLevel, newFormat, arg);
|
||||
log_to_ble(logLevel, newFormat, arg);
|
||||
|
||||
va_end(arg);
|
||||
#ifdef HAS_FREE_RTOS
|
||||
@@ -342,6 +352,7 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
||||
#endif
|
||||
}
|
||||
|
||||
delete[] newFormat;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,10 +76,8 @@ bool NotifiedWorkerThread::notifyLater(uint32_t delay, uint32_t v, bool overwrit
|
||||
|
||||
void NotifiedWorkerThread::checkNotification()
|
||||
{
|
||||
// Atomically read and clear. (This avoids a potential race condition where an interrupt handler could set a new notification
|
||||
// after checkNotification reads but before it clears, which would cause us to miss that notification until the next one comes
|
||||
// in.)
|
||||
auto n = notification.exchange(0); // read+clear atomically: like `n = notification; notification = 0;` but interrupt-safe
|
||||
auto n = notification;
|
||||
notification = 0; // clear notification
|
||||
if (n) {
|
||||
onNotify(n);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "OSThread.h"
|
||||
#include <atomic>
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
@@ -14,7 +13,7 @@ class NotifiedWorkerThread : public OSThread
|
||||
/**
|
||||
* The notification that was most recently used to wake the thread. Read from runOnce()
|
||||
*/
|
||||
std::atomic<uint32_t> notification{0};
|
||||
uint32_t notification = 0;
|
||||
|
||||
public:
|
||||
NotifiedWorkerThread(const char *name) : OSThread(name) {}
|
||||
|
||||
@@ -217,7 +217,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define SHTC3_ADDR 0x70
|
||||
#define LPS22HB_ADDR 0x5C
|
||||
#define LPS22HB_ADDR_ALT 0x5D
|
||||
#define SFA30_ADDR 0x5D
|
||||
#define SHT31_4x_ADDR 0x44
|
||||
#define SHT31_4x_ADDR_ALT 0x45
|
||||
#define PMSA003I_ADDR 0x12
|
||||
@@ -234,7 +233,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define NAU7802_ADDR 0x2A
|
||||
#define MAX30102_ADDR 0x57
|
||||
#define SCD4X_ADDR 0x62
|
||||
#define CW2015_ADDR 0x62
|
||||
#define MLX90614_ADDR_DEF 0x5A
|
||||
#define CGRADSENS_ADDR 0x66
|
||||
#define LTR390UV_ADDR 0x53
|
||||
@@ -244,7 +242,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BQ25896_ADDR 0x6B
|
||||
#define LTR553ALS_ADDR 0x23
|
||||
#define SEN5X_ADDR 0x69
|
||||
#define SCD30_ADDR 0x61
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ACCELEROMETER
|
||||
|
||||
@@ -43,8 +43,8 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAQI() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X, SFA30};
|
||||
return firstOfOrNONE(4, types);
|
||||
ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X};
|
||||
return firstOfOrNONE(2, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstRGBLED() const
|
||||
|
||||
@@ -89,10 +89,7 @@ class ScanI2C
|
||||
DA217,
|
||||
CHSC6X,
|
||||
CST226SE,
|
||||
SEN5X,
|
||||
SFA30,
|
||||
CW2015,
|
||||
SCD30
|
||||
SEN5X
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "ScanI2CTwoWire.h"
|
||||
#include "configuration.h"
|
||||
#include "detect/ScanI2C.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
|
||||
@@ -117,25 +115,6 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
|
||||
return value;
|
||||
}
|
||||
|
||||
bool ScanI2CTwoWire::i2cCommandResponseLength(ScanI2C::DeviceAddress addr, uint16_t command, uint8_t expectedLength) const
|
||||
{
|
||||
TwoWire *i2cBus = fetchI2CBus(addr);
|
||||
i2cBus->beginTransmission(addr.address);
|
||||
if (command > 0xFF) {
|
||||
i2cBus->write((uint8_t)(command >> 8));
|
||||
}
|
||||
i2cBus->write((uint8_t)(command & 0xFF));
|
||||
if (i2cBus->endTransmission() != 0) {
|
||||
return false;
|
||||
}
|
||||
delay(20);
|
||||
uint8_t received = i2cBus->requestFrom(addr.address, expectedLength);
|
||||
bool match = (received == expectedLength);
|
||||
while (i2cBus->available())
|
||||
i2cBus->read();
|
||||
return match;
|
||||
}
|
||||
|
||||
/// for SEN5X detection
|
||||
// Note, this code needs to be called before setting the I2C bus speed
|
||||
// for the screen at high speed. The speed needs to be at 100kHz, otherwise
|
||||
@@ -451,7 +430,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
|
||||
type = OPT3001;
|
||||
logFoundDevice("OPT3001", (uint8_t)addr.address);
|
||||
} else if (i2cCommandResponseLength(addr, 0x89, 6)) { // SHT4x serial number (6 bytes inc. CRC)
|
||||
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 6) !=
|
||||
0) { // unique SHT4x serial number (6 bytes inc. CRC)
|
||||
type = SHT4X;
|
||||
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
||||
} else {
|
||||
@@ -476,19 +456,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
break;
|
||||
|
||||
case LPS22HB_ADDR_ALT:
|
||||
// SFA30 detection: send 2-byte command 0xD060 (Get Device Marking) and check for 48-byte response
|
||||
if (i2cCommandResponseLength(addr, 0xD060, 48)) {
|
||||
type = SFA30;
|
||||
logFoundDevice("SFA30", (uint8_t)addr.address);
|
||||
break;
|
||||
}
|
||||
// Fallback: LPS22HB detection at alternate address using WHO_AM_I register (0x0F == 0xB1)
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1);
|
||||
if (registerValue == 0xB1) {
|
||||
type = LPS22HB;
|
||||
logFoundDevice("LPS22HB", (uint8_t)addr.address);
|
||||
}
|
||||
break;
|
||||
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address)
|
||||
SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address)
|
||||
|
||||
@@ -581,7 +548,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(SCD30_ADDR, SCD30, "SCD30", (uint8_t)addr.address);
|
||||
case CST328_ADDR:
|
||||
// Do we have the CST328 or the CST226SE
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1);
|
||||
@@ -615,17 +581,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
break;
|
||||
|
||||
SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
|
||||
case SCD4X_ADDR: {
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x8), 1);
|
||||
if (registerValue == 0x18) {
|
||||
logFoundDevice("CW2015", (uint8_t)addr.address);
|
||||
type = CW2015;
|
||||
} else {
|
||||
logFoundDevice("SCD4X", (uint8_t)addr.address);
|
||||
type = SCD4X;
|
||||
}
|
||||
break;
|
||||
}
|
||||
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
|
||||
#ifdef HAS_TPS65233
|
||||
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
|
||||
|
||||
@@ -55,8 +55,6 @@ class ScanI2CTwoWire : public ScanI2C
|
||||
|
||||
uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth, bool) const;
|
||||
|
||||
bool i2cCommandResponseLength(DeviceAddress addr, uint16_t command, uint8_t expectedLength) const;
|
||||
|
||||
DeviceType probeOLED(ScanI2C::DeviceAddress) const;
|
||||
|
||||
static void logFoundDevice(const char *device, uint8_t address);
|
||||
|
||||
+12
-10
@@ -52,7 +52,7 @@ SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT;
|
||||
HardwareSerial *GPS::_serial_gps = nullptr;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<GPS> gps = nullptr;
|
||||
GPS *gps = nullptr;
|
||||
|
||||
static GPSUpdateScheduling scheduling;
|
||||
|
||||
@@ -127,7 +127,7 @@ static int32_t gpsSwitch()
|
||||
return 1000;
|
||||
}
|
||||
|
||||
static std::unique_ptr<concurrency::Periodic> gpsPeriodic;
|
||||
static concurrency::Periodic *gpsPeriodic;
|
||||
#endif
|
||||
|
||||
static void UBXChecksum(uint8_t *message, size_t length)
|
||||
@@ -1485,7 +1485,7 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
|
||||
if (bufferSize > 2048)
|
||||
bufferSize = 2048;
|
||||
|
||||
auto response = std::unique_ptr<char[]>(new char[bufferSize]); // Dynamically allocate based on baud rate
|
||||
char *response = new char[bufferSize](); // Dynamically allocate based on baud rate
|
||||
uint16_t responseLen = 0;
|
||||
unsigned long start = millis();
|
||||
while (millis() - start < timeout) {
|
||||
@@ -1501,18 +1501,19 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
|
||||
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
|
||||
// check if we can see our chips
|
||||
for (const auto &chipInfo : responseMap) {
|
||||
if (strstr(response.get(), chipInfo.detectionString.c_str()) != nullptr) {
|
||||
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(response.get());
|
||||
LOG_DEBUG(response);
|
||||
#endif
|
||||
LOG_INFO("%s detected", chipInfo.chipName.c_str());
|
||||
delete[] response; // Cleanup before return
|
||||
return chipInfo.driver;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(response.get());
|
||||
LOG_DEBUG(response);
|
||||
#endif
|
||||
// Reset the response buffer for the next potential message
|
||||
responseLen = 0;
|
||||
@@ -1521,12 +1522,13 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
|
||||
}
|
||||
}
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(response.get());
|
||||
LOG_DEBUG(response);
|
||||
#endif
|
||||
delete[] response; // Cleanup before return
|
||||
return GNSS_MODEL_UNKNOWN; // Return unknown on timeout
|
||||
}
|
||||
|
||||
std::unique_ptr<GPS> GPS::createGps()
|
||||
GPS *GPS::createGps()
|
||||
{
|
||||
int8_t _rx_gpio = config.position.rx_gpio;
|
||||
int8_t _tx_gpio = config.position.tx_gpio;
|
||||
@@ -1551,7 +1553,7 @@ std::unique_ptr<GPS> GPS::createGps()
|
||||
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
|
||||
return nullptr;
|
||||
|
||||
auto new_gps = std::unique_ptr<GPS>(new GPS());
|
||||
GPS *new_gps = new GPS;
|
||||
new_gps->rx_gpio = _rx_gpio;
|
||||
new_gps->tx_gpio = _tx_gpio;
|
||||
|
||||
@@ -1579,7 +1581,7 @@ std::unique_ptr<GPS> GPS::createGps()
|
||||
#ifdef PIN_GPS_SWITCH
|
||||
// toggle GPS via external GPIO switch
|
||||
pinMode(PIN_GPS_SWITCH, INPUT);
|
||||
gpsPeriodic = std::unique_ptr<concurrency::Periodic>(new concurrency::Periodic("GPSSwitch", gpsSwitch));
|
||||
gpsPeriodic = new concurrency::Periodic("GPSSwitch", gpsSwitch);
|
||||
#endif
|
||||
|
||||
// Currently disabled per issue #525 (TinyGPS++ crash bug)
|
||||
|
||||
+2
-4
@@ -2,8 +2,6 @@
|
||||
#include "configuration.h"
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "GPSStatus.h"
|
||||
#include "GpioLogic.h"
|
||||
#include "Observer.h"
|
||||
@@ -120,7 +118,7 @@ class GPS : private concurrency::OSThread
|
||||
|
||||
// Creates an instance of the GPS class.
|
||||
// Returns the new instance or null if the GPS is not present.
|
||||
static std::unique_ptr<GPS> createGps();
|
||||
static GPS *createGps();
|
||||
|
||||
// Wake the GPS hardware - ready for an update
|
||||
void up();
|
||||
@@ -258,5 +256,5 @@ class GPS : private concurrency::OSThread
|
||||
uint8_t fixeddelayCtr = 0;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<GPS> gps;
|
||||
extern GPS *gps;
|
||||
#endif // Exclude GPS
|
||||
|
||||
+1
-1
@@ -312,7 +312,7 @@ const char *RtcName(RTCQuality quality)
|
||||
* @param t The time to potentially set the RTC to.
|
||||
* @return True if the RTC was set to the provided time, false otherwise.
|
||||
*/
|
||||
RTCSetResult perhapsSetRTC(RTCQuality q, const struct tm &t)
|
||||
RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||
{
|
||||
/* Convert to unix time
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ extern uint32_t lastSetFromPhoneNtpOrGps;
|
||||
|
||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
|
||||
RTCSetResult perhapsSetRTC(RTCQuality q, const struct tm &t);
|
||||
RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t);
|
||||
|
||||
/// Return a string name for the quality
|
||||
const char *RtcName(RTCQuality quality);
|
||||
|
||||
@@ -10,7 +10,7 @@ EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDI
|
||||
{
|
||||
// If tracking ghost pixels, grab memory
|
||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||
dirtyPixels = std::unique_ptr<uint8_t[]>(new uint8_t[EInkDisplay::displayBufferSize]()); // Init with zeros
|
||||
dirtyPixels = new uint8_t[EInkDisplay::displayBufferSize](); // Init with zeros
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ EInkDynamicDisplay::~EInkDynamicDisplay()
|
||||
{
|
||||
// If we were tracking ghost pixels, free the memory
|
||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||
dirtyPixels = nullptr;
|
||||
delete[] dirtyPixels;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ void EInkDynamicDisplay::checkExcessiveGhosting()
|
||||
void EInkDynamicDisplay::resetGhostPixelTracking()
|
||||
{
|
||||
// Copy the current frame into dirtyPixels[] from the display buffer
|
||||
memcpy(dirtyPixels.get(), EInkDisplay::buffer, EInkDisplay::displayBufferSize);
|
||||
memcpy(dirtyPixels, EInkDisplay::buffer, EInkDisplay::displayBufferSize);
|
||||
}
|
||||
#endif // EINK_LIMIT_GHOSTING_PX
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
#include <memory>
|
||||
|
||||
#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
|
||||
|
||||
@@ -117,11 +116,11 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo
|
||||
// Optional - track ghosting, pixel by pixel
|
||||
// May 2024: no longer used by any display. Kept for possible future use.
|
||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||
void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh
|
||||
void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit
|
||||
void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display.
|
||||
std::unique_ptr<uint8_t[]> dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem)
|
||||
uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use
|
||||
void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh
|
||||
void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit
|
||||
void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display.
|
||||
uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem)
|
||||
uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use
|
||||
#endif
|
||||
|
||||
// Conditional - async full refresh - only with modified meshtastic/GxEPD2
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#ifdef HAS_NEOPIXEL
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
extern Adafruit_NeoPixel pixels;
|
||||
#endif
|
||||
@@ -1,6 +1,4 @@
|
||||
#ifdef HAS_LP5562
|
||||
#include <Wire.h>
|
||||
|
||||
#include <LP5562.h>
|
||||
extern LP5562 rgbw;
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#ifdef HAS_NCP5623
|
||||
#include <NCP5623.h>
|
||||
extern NCP5623 rgb;
|
||||
|
||||
#endif
|
||||
@@ -221,6 +221,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
||||
|
||||
if (rtc_sec > 0) {
|
||||
// === Build Time String ===
|
||||
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
|
||||
int hour, minute, second;
|
||||
graphics::decomposeTime(rtc_sec, hour, minute, second);
|
||||
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
|
||||
@@ -427,33 +428,39 @@ const int *getTextPositions(OLEDDisplay *display)
|
||||
// *************************
|
||||
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
|
||||
{
|
||||
if (!isAPIConnected(service->api_state))
|
||||
return;
|
||||
bool drawConnectionState = false;
|
||||
if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI ||
|
||||
service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET ||
|
||||
service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) {
|
||||
drawConnectionState = true;
|
||||
}
|
||||
|
||||
const int scale = (currentResolution == ScreenResolution::High) ? 2 : 1;
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(0, SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale), (connection_icon_width * scale),
|
||||
(connection_icon_height * scale) + (2 * scale));
|
||||
display->setColor(WHITE);
|
||||
if (currentResolution == ScreenResolution::High) {
|
||||
const int bytesPerRow = (connection_icon_width + 7) / 8;
|
||||
int iconX = 0;
|
||||
int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
|
||||
if (drawConnectionState) {
|
||||
const int scale = (currentResolution == ScreenResolution::High) ? 2 : 1;
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(0, SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale), (connection_icon_width * scale),
|
||||
(connection_icon_height * scale) + (2 * scale));
|
||||
display->setColor(WHITE);
|
||||
if (currentResolution == ScreenResolution::High) {
|
||||
const int bytesPerRow = (connection_icon_width + 7) / 8;
|
||||
int iconX = 0;
|
||||
int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
|
||||
|
||||
for (int yy = 0; yy < connection_icon_height; ++yy) {
|
||||
const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
|
||||
for (int xx = 0; xx < connection_icon_width; ++xx) {
|
||||
const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3));
|
||||
const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first
|
||||
if (byteVal & bitMask) {
|
||||
display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale);
|
||||
for (int yy = 0; yy < connection_icon_height; ++yy) {
|
||||
const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
|
||||
for (int xx = 0; xx < connection_icon_width; ++xx) {
|
||||
const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3));
|
||||
const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first
|
||||
if (byteVal & bitMask) {
|
||||
display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
|
||||
connection_icon);
|
||||
} else {
|
||||
display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
|
||||
connection_icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,18 +63,4 @@ bool isAllowedPunctuation(char c);
|
||||
|
||||
std::string sanitizeString(const std::string &input);
|
||||
|
||||
static inline bool isAPIConnected(uint8_t state)
|
||||
{
|
||||
static constexpr bool connectedStates[] = {
|
||||
/* STATE_NONE */ false,
|
||||
/* STATE_BLE */ true,
|
||||
/* STATE_WIFI */ true,
|
||||
/* STATE_SERIAL */ true,
|
||||
/* STATE_PACKET */ true,
|
||||
/* STATE_HTTP */ true,
|
||||
/* STATE_ETH */ true,
|
||||
};
|
||||
return state < sizeof(connectedStates) ? connectedStates[state] : false;
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
@@ -110,14 +110,14 @@ void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, ui
|
||||
uint32_t secs = (uptimeMillis % 60000) / 1000;
|
||||
|
||||
if (days) {
|
||||
snprintf(uptimeStr, maxLength, "%s%ud %uh", prefix, days, hours);
|
||||
snprintf(uptimeStr, maxLength, "%s: %ud %uh", prefix, days, hours);
|
||||
} else if (hours) {
|
||||
snprintf(uptimeStr, maxLength, "%s%uh %um", prefix, hours, mins);
|
||||
snprintf(uptimeStr, maxLength, "%s: %uh %um", prefix, hours, mins);
|
||||
} else if (!includeSecs) {
|
||||
snprintf(uptimeStr, maxLength, "%s%um", prefix, mins);
|
||||
snprintf(uptimeStr, maxLength, "%s: %um", prefix, mins);
|
||||
} else if (mins) {
|
||||
snprintf(uptimeStr, maxLength, "%s%um %us", prefix, mins, secs);
|
||||
snprintf(uptimeStr, maxLength, "%s: %um %us", prefix, mins, secs);
|
||||
} else {
|
||||
snprintf(uptimeStr, maxLength, "%s%us", prefix, secs);
|
||||
snprintf(uptimeStr, maxLength, "%s: %us", prefix, secs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,10 +429,6 @@ void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool
|
||||
c = c - 'a' + 'A';
|
||||
}
|
||||
keyText = (key.character == ' ' || key.character == '_') ? "_" : std::string(1, c);
|
||||
// Show the common "/" pairing next to "?" like on a real keyboard
|
||||
if (key.type == VK_CHAR && key.character == '?') {
|
||||
keyText = "?/";
|
||||
}
|
||||
}
|
||||
|
||||
int textWidth = display->getStringWidth(keyText.c_str());
|
||||
@@ -522,13 +518,9 @@ char VirtualKeyboard::getCharForKey(const VirtualKey &key, bool isLongPress)
|
||||
|
||||
char c = key.character;
|
||||
|
||||
// Long-press: letters become uppercase; for "?" provide "/" like a typical keyboard
|
||||
if (isLongPress) {
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
c = (char)(c - 'a' + 'A');
|
||||
} else if (c == '?') {
|
||||
c = '/';
|
||||
}
|
||||
// Long-press: only keep letter lowercase->uppercase conversion; remove other symbol mappings
|
||||
if (isLongPress && c >= 'a' && c <= 'z') {
|
||||
c = (char)(c - 'a' + 'A');
|
||||
}
|
||||
|
||||
return c;
|
||||
|
||||
@@ -663,7 +663,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
||||
|
||||
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it
|
||||
char uptimeStr[32] = "";
|
||||
getUptimeStr(millis(), "Up: ", uptimeStr, sizeof(uptimeStr));
|
||||
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
|
||||
textWidth = display->getStringWidth(uptimeStr);
|
||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||
display->drawString(nameX, getTextPositions(display)[line++], uptimeStr);
|
||||
|
||||
@@ -539,7 +539,7 @@ void menuHandler::messageResponseMenu()
|
||||
// If viewing ALL chats, hide “Mute Chat”
|
||||
if (mode != graphics::MessageRenderer::ThreadMode::ALL && mode != graphics::MessageRenderer::ThreadMode::DIRECT) {
|
||||
const uint8_t chIndex = (threadChannel != 0) ? (uint8_t)threadChannel : channels.getPrimaryIndex();
|
||||
const auto &chan = channels.getByIndex(chIndex);
|
||||
auto &chan = channels.getByIndex(chIndex);
|
||||
|
||||
optionsArray[options] = chan.settings.module_settings.is_muted ? "Unmute Channel" : "Mute Channel";
|
||||
optionsEnumArray[options++] = MuteChannel;
|
||||
@@ -831,7 +831,7 @@ void menuHandler::messageViewModeMenu()
|
||||
// Gather unique peers
|
||||
auto dms = messageStore.getDirectMessages();
|
||||
std::vector<uint32_t> uniquePeers;
|
||||
for (const auto &m : dms) {
|
||||
for (auto &m : dms) {
|
||||
uint32_t peer = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender;
|
||||
if (peer != nodeDB->getNodeNum() && std::find(uniquePeers.begin(), uniquePeers.end(), peer) == uniquePeers.end())
|
||||
uniquePeers.push_back(peer);
|
||||
@@ -1397,7 +1397,7 @@ void menuHandler::manageNodeMenu()
|
||||
}
|
||||
|
||||
if (selected == Favorite) {
|
||||
const auto *n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
|
||||
auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
|
||||
if (!n) {
|
||||
return;
|
||||
}
|
||||
@@ -2292,13 +2292,14 @@ void menuHandler::wifiToggleMenu()
|
||||
void menuHandler::screenOptionsMenu()
|
||||
{
|
||||
// Check if brightness is supported
|
||||
bool hasSupportBrightness = false;
|
||||
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
|
||||
hasSupportBrightness = true;
|
||||
#endif
|
||||
|
||||
#if defined(T_DECK)
|
||||
// TDeck Doesn't seem to support brightness at all, at least not reliably
|
||||
bool hasSupportBrightness = false;
|
||||
#elif defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
|
||||
bool hasSupportBrightness = true;
|
||||
#else
|
||||
bool hasSupportBrightness = false;
|
||||
hasSupportBrightness = false;
|
||||
#endif
|
||||
|
||||
enum optionsNumbers { Back, Brightness, ScreenColor, FrameToggles, DisplayUnits, MessageBubbles };
|
||||
@@ -2443,7 +2444,7 @@ void menuHandler::frameTogglesMenu()
|
||||
nodelist_hopsignal,
|
||||
nodelist_distance,
|
||||
nodelist_bearings,
|
||||
gps_position,
|
||||
gps,
|
||||
lora,
|
||||
clock,
|
||||
show_favorites,
|
||||
@@ -2481,7 +2482,7 @@ void menuHandler::frameTogglesMenu()
|
||||
#endif
|
||||
|
||||
optionsArray[options] = screen->isFrameHidden("gps") ? "Show Position" : "Hide Position";
|
||||
optionsEnumArray[options++] = gps_position;
|
||||
optionsEnumArray[options++] = gps;
|
||||
#endif
|
||||
|
||||
optionsArray[options] = screen->isFrameHidden("lora") ? "Show LoRa" : "Hide LoRa";
|
||||
@@ -2544,7 +2545,7 @@ void menuHandler::frameTogglesMenu()
|
||||
screen->toggleFrameVisibility("nodelist_bearings");
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
} else if (selected == gps_position) {
|
||||
} else if (selected == gps) {
|
||||
screen->toggleFrameVisibility("gps");
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
|
||||
@@ -171,7 +171,7 @@ unsigned long getModeCycleIntervalMs()
|
||||
|
||||
int calculateMaxScroll(int totalEntries, int visibleRows)
|
||||
{
|
||||
return max(0, (totalEntries - 1) / (visibleRows * 2));
|
||||
return std::max(0, (totalEntries - 1) / (visibleRows * 2));
|
||||
}
|
||||
|
||||
void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd)
|
||||
@@ -187,12 +187,13 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
|
||||
if (totalEntries <= visibleNodeRows * columns)
|
||||
return;
|
||||
|
||||
int scrollbarHeight = display->getHeight() - scrollStartY - 10;
|
||||
int thumbHeight = max(4, (scrollbarHeight * visibleNodeRows * columns) / totalEntries);
|
||||
int thumbY = scrollStartY + (scrollIndex * (scrollbarHeight - thumbHeight)) /
|
||||
max(1, max(0, (totalEntries - 1) / (visibleNodeRows * columns)));
|
||||
|
||||
int scrollbarX = display->getWidth() - 2;
|
||||
int scrollbarHeight = display->getHeight() - scrollStartY - 10;
|
||||
int thumbHeight = std::max(4, (scrollbarHeight * visibleNodeRows * columns) / totalEntries);
|
||||
int perPage = visibleNodeRows * columns;
|
||||
int maxScroll = std::max(0, (totalEntries - 1) / perPage);
|
||||
int thumbY = scrollStartY + (scrollIndex * (scrollbarHeight - thumbHeight)) / std::max(1, maxScroll);
|
||||
|
||||
for (int i = 0; i < thumbHeight; i++) {
|
||||
display->setPixel(scrollbarX, thumbY + i);
|
||||
}
|
||||
@@ -555,13 +556,13 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
|
||||
int maxScroll = 0;
|
||||
if (perPage > 0) {
|
||||
maxScroll = max(0, (totalEntries - 1) / perPage);
|
||||
maxScroll = std::max(0, (totalEntries - 1) / perPage);
|
||||
}
|
||||
|
||||
if (scrollIndex > maxScroll)
|
||||
scrollIndex = maxScroll;
|
||||
int startIndex = scrollIndex * visibleNodeRows * totalColumns;
|
||||
int endIndex = min(startIndex + visibleNodeRows * totalColumns, totalEntries);
|
||||
int endIndex = std::min(startIndex + visibleNodeRows * totalColumns, totalEntries);
|
||||
int yOffset = 0;
|
||||
int col = 0;
|
||||
int lastNodeY = y;
|
||||
@@ -579,7 +580,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
if (extras)
|
||||
extras(display, node, xPos, yPos, columnWidth, heading, lat, lon);
|
||||
|
||||
lastNodeY = max(lastNodeY, yPos + FONT_HEIGHT_SMALL);
|
||||
lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL);
|
||||
yOffset += rowYOffset;
|
||||
shownCount++;
|
||||
rowCount++;
|
||||
@@ -612,11 +613,13 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
if (millis() - popupTime < POPUP_DURATION_MS) {
|
||||
popupTotal = totalEntries;
|
||||
|
||||
int perPage = visibleNodeRows * totalColumns;
|
||||
|
||||
popupStart = startIndex + 1;
|
||||
popupEnd = min(startIndex + perPage, totalEntries);
|
||||
popupEnd = std::min(startIndex + perPage, totalEntries);
|
||||
|
||||
popupPage = (scrollIndex + 1);
|
||||
popupMaxPage = max(1, (totalEntries + perPage - 1) / perPage);
|
||||
popupMaxPage = std::max(1, (totalEntries + perPage - 1) / perPage);
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%d-%d/%d Pg %d/%d", popupStart, popupEnd, popupTotal, popupPage, popupMaxPage);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#if HAS_SCREEN
|
||||
#include "CompassRenderer.h"
|
||||
#include "GPSStatus.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "NodeListRenderer.h"
|
||||
#include "UIRenderer.h"
|
||||
@@ -12,7 +11,6 @@
|
||||
#include "graphics/TimeFormatters.h"
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "modules/CannedMessageModule.h"
|
||||
#include "target_specific.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <RTC.h>
|
||||
@@ -289,8 +287,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
|
||||
// **********************
|
||||
// * Favorite Node Info *
|
||||
// **********************
|
||||
// cppcheck-suppress constParameterPointer; signature must match FrameCallback typedef from OLEDDisplayUi library
|
||||
void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
if (favoritedNodes.empty())
|
||||
return;
|
||||
@@ -316,7 +313,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
// === Create the shortName and title string ===
|
||||
const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node";
|
||||
char titlestr[32] = {0};
|
||||
snprintf(titlestr, sizeof(titlestr), "*%s*", shortName);
|
||||
snprintf(titlestr, sizeof(titlestr), "Fav: %s", shortName);
|
||||
|
||||
// === Draw battery/time/mail header (common across screens) ===
|
||||
graphics::drawCommonHeader(display, x, y, titlestr);
|
||||
@@ -345,162 +342,34 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
}
|
||||
|
||||
// === 2. Signal and Hops (combined on one line, if available) ===
|
||||
// If both are present: "Sig: 97% [2hops]"
|
||||
// If only one: show only that one
|
||||
char signalHopsStr[32] = "";
|
||||
bool haveSignal = false;
|
||||
int bars = 0;
|
||||
int percentSignal = clamp((int)((node->snr + 10) * 5), 0, 100);
|
||||
|
||||
// Helper to get SNR limit based on modem preset
|
||||
auto getSnrLimit = [](meshtastic_Config_LoRaConfig_ModemPreset preset) -> float {
|
||||
switch (preset) {
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
|
||||
return -6.0f;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
|
||||
return -5.5f;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
|
||||
return -4.5f;
|
||||
default:
|
||||
return -6.0f;
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate signal grade using modem preset and SNR only
|
||||
float snrLimit = getSnrLimit(config.lora.modem_preset);
|
||||
float snr = node->snr;
|
||||
|
||||
// Determine signal quality label and bars using SNR-only grading
|
||||
const char *qualityLabel = nullptr;
|
||||
|
||||
if (snr > snrLimit + 10) {
|
||||
qualityLabel = "Good";
|
||||
bars = 4;
|
||||
} else if (snr > snrLimit + 6) {
|
||||
qualityLabel = "Good";
|
||||
bars = 3;
|
||||
} else if (snr > snrLimit + 2) {
|
||||
qualityLabel = "Good";
|
||||
bars = 2;
|
||||
} else if (snr > snrLimit - 4) {
|
||||
qualityLabel = "Fair";
|
||||
bars = 1;
|
||||
} else {
|
||||
qualityLabel = "Bad";
|
||||
bars = 1;
|
||||
}
|
||||
|
||||
// Add extra spacing on the left if we have an API connection to account for the common footer icons
|
||||
const char *leftSideSpacing =
|
||||
graphics::isAPIConnected(service->api_state) ? (currentResolution == ScreenResolution::High ? " " : " ") : " ";
|
||||
// Always use "Sig" for the label
|
||||
const char *signalLabel = " Sig";
|
||||
|
||||
// --- Build the Signal/Hops line ---
|
||||
// Only show signal if we have valid SNR
|
||||
if (snr > -100 && snr != 0) {
|
||||
snprintf(signalHopsStr, sizeof(signalHopsStr), "%sSig:%s", leftSideSpacing, qualityLabel);
|
||||
// If SNR looks reasonable, show signal
|
||||
if ((int)((node->snr + 10) * 5) >= 0 && node->snr > -100) {
|
||||
snprintf(signalHopsStr, sizeof(signalHopsStr), "%s: %d%%", signalLabel, percentSignal);
|
||||
haveSignal = true;
|
||||
}
|
||||
|
||||
// If hops is valid (>0), show right after signal
|
||||
if (node->hops_away > 0) {
|
||||
size_t len = strlen(signalHopsStr);
|
||||
// Decide between "1 Hop" and "N Hops"
|
||||
if (haveSignal) {
|
||||
snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [#]");
|
||||
snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [%d %s]", node->hops_away,
|
||||
(node->hops_away == 1 ? "Hop" : "Hops"));
|
||||
} else {
|
||||
snprintf(signalHopsStr, sizeof(signalHopsStr), "[#]");
|
||||
snprintf(signalHopsStr, sizeof(signalHopsStr), "[%d %s]", node->hops_away, (node->hops_away == 1 ? "Hop" : "Hops"));
|
||||
}
|
||||
}
|
||||
|
||||
if (signalHopsStr[0]) {
|
||||
int yPos = getTextPositions(display)[line++];
|
||||
int curX = x;
|
||||
|
||||
// Split combined string into signal text and hop suffix
|
||||
char sigPart[20] = "";
|
||||
const char *hopPart = nullptr;
|
||||
|
||||
char *bracket = strchr(signalHopsStr, '[');
|
||||
if (bracket) {
|
||||
size_t n = (size_t)(bracket - signalHopsStr);
|
||||
if (n >= sizeof(sigPart))
|
||||
n = sizeof(sigPart) - 1;
|
||||
memcpy(sigPart, signalHopsStr, n);
|
||||
sigPart[n] = '\0';
|
||||
|
||||
// Trim trailing spaces
|
||||
while (strlen(sigPart) && sigPart[strlen(sigPart) - 1] == ' ') {
|
||||
sigPart[strlen(sigPart) - 1] = '\0';
|
||||
}
|
||||
|
||||
hopPart = bracket; // "[n Hop(s)]"
|
||||
} else {
|
||||
strncpy(sigPart, signalHopsStr, sizeof(sigPart) - 1);
|
||||
sigPart[sizeof(sigPart) - 1] = '\0';
|
||||
}
|
||||
|
||||
// Draw signal quality text
|
||||
display->drawString(curX, yPos, sigPart);
|
||||
curX += display->getStringWidth(sigPart) + 4;
|
||||
|
||||
// Draw signal bars (skip on UltraLow, text only)
|
||||
if (currentResolution != ScreenResolution::UltraLow && haveSignal && bars > 0) {
|
||||
const int kMaxBars = 4;
|
||||
if (bars < 1)
|
||||
bars = 1;
|
||||
if (bars > kMaxBars)
|
||||
bars = kMaxBars;
|
||||
|
||||
int barX = curX;
|
||||
|
||||
const bool hi = (currentResolution == ScreenResolution::High);
|
||||
int barWidth = hi ? 2 : 1;
|
||||
int barGap = hi ? 2 : 1;
|
||||
int maxBarHeight = FONT_HEIGHT_SMALL - 7;
|
||||
if (!hi)
|
||||
maxBarHeight -= 1;
|
||||
int barY = yPos + (FONT_HEIGHT_SMALL - maxBarHeight) / 2;
|
||||
|
||||
for (int bi = 0; bi < kMaxBars; bi++) {
|
||||
int barHeight = maxBarHeight * (bi + 1) / kMaxBars;
|
||||
if (barHeight < 2)
|
||||
barHeight = 2;
|
||||
|
||||
int bx = barX + bi * (barWidth + barGap);
|
||||
int by = barY + maxBarHeight - barHeight;
|
||||
|
||||
if (bi < bars) {
|
||||
display->fillRect(bx, by, barWidth, barHeight);
|
||||
} else {
|
||||
int baseY = barY + maxBarHeight - 1;
|
||||
display->drawHorizontalLine(bx, baseY, barWidth);
|
||||
}
|
||||
}
|
||||
|
||||
curX += (kMaxBars * barWidth) + ((kMaxBars - 1) * barGap) + 2;
|
||||
}
|
||||
|
||||
// Draw hops AFTER the bars as: [ number + hop icon ]
|
||||
if (hopPart && node->hops_away > 0) {
|
||||
|
||||
// open bracket
|
||||
display->drawString(curX, yPos, "[");
|
||||
curX += display->getStringWidth("[") + 1;
|
||||
|
||||
// hop count
|
||||
char hopCount[6];
|
||||
snprintf(hopCount, sizeof(hopCount), "%d", node->hops_away);
|
||||
display->drawString(curX, yPos, hopCount);
|
||||
curX += display->getStringWidth(hopCount) + 2;
|
||||
|
||||
// hop icon
|
||||
const int iconY = yPos + (FONT_HEIGHT_SMALL - hop_height) / 2;
|
||||
display->drawXbm(curX, iconY, hop_width, hop_height, hop);
|
||||
curX += hop_width + 1;
|
||||
|
||||
// closing bracket
|
||||
display->drawString(curX, yPos, "]");
|
||||
}
|
||||
if (signalHopsStr[0] && line < 5) {
|
||||
display->drawString(x, getTextPositions(display)[line++], signalHopsStr);
|
||||
}
|
||||
|
||||
// === 3. Heard (last seen, skip if node never seen) ===
|
||||
@@ -508,8 +377,8 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
uint32_t seconds = sinceLastSeen(node);
|
||||
if (seconds != 0 && seconds != UINT32_MAX) {
|
||||
uint32_t minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
|
||||
// Format as "Heard:Xm ago", "Heard:Xh ago", or "Heard:Xd ago"
|
||||
snprintf(seenStr, sizeof(seenStr), (days > 365 ? " Heard:?" : "%sHeard:%d%c ago"), leftSideSpacing,
|
||||
// Format as "Heard: Xm ago", "Heard: Xh ago", or "Heard: Xd ago"
|
||||
snprintf(seenStr, sizeof(seenStr), (days > 365 ? " Heard: ?" : " Heard: %d%c ago"),
|
||||
(days ? days
|
||||
: hours ? hours
|
||||
: minutes),
|
||||
@@ -517,18 +386,16 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
: hours ? 'h'
|
||||
: 'm'));
|
||||
}
|
||||
if (seenStr[0]) {
|
||||
if (seenStr[0] && line < 5) {
|
||||
display->drawString(x, getTextPositions(display)[line++], seenStr);
|
||||
}
|
||||
#if !defined(M5STACK_UNITC6L)
|
||||
// === 4. Uptime (only show if metric is present) ===
|
||||
char uptimeStr[32] = "";
|
||||
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
|
||||
char upPrefix[12]; // enough for leftSideSpacing + "Up:"
|
||||
snprintf(upPrefix, sizeof(upPrefix), "%sUp:", leftSideSpacing);
|
||||
getUptimeStr(node->device_metrics.uptime_seconds * 1000, upPrefix, uptimeStr, sizeof(uptimeStr));
|
||||
getUptimeStr(node->device_metrics.uptime_seconds * 1000, " Up", uptimeStr, sizeof(uptimeStr));
|
||||
}
|
||||
if (uptimeStr[0]) {
|
||||
if (uptimeStr[0] && line < 5) {
|
||||
display->drawString(x, getTextPositions(display)[line++], uptimeStr);
|
||||
}
|
||||
|
||||
@@ -555,16 +422,16 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
if (miles < 0.1) {
|
||||
int feet = (int)(miles * 5280);
|
||||
if (feet > 0 && feet < 1000) {
|
||||
snprintf(distStr, sizeof(distStr), "%sDistance:%dft", leftSideSpacing, feet);
|
||||
snprintf(distStr, sizeof(distStr), " Distance: %dft", feet);
|
||||
haveDistance = true;
|
||||
} else if (feet >= 1000) {
|
||||
snprintf(distStr, sizeof(distStr), "%sDistance:¼mi", leftSideSpacing);
|
||||
snprintf(distStr, sizeof(distStr), " Distance: ¼mi");
|
||||
haveDistance = true;
|
||||
}
|
||||
} else {
|
||||
int roundedMiles = (int)(miles + 0.5);
|
||||
if (roundedMiles > 0 && roundedMiles < 1000) {
|
||||
snprintf(distStr, sizeof(distStr), "%sDistance:%dmi", leftSideSpacing, roundedMiles);
|
||||
snprintf(distStr, sizeof(distStr), " Distance: %dmi", roundedMiles);
|
||||
haveDistance = true;
|
||||
}
|
||||
}
|
||||
@@ -572,74 +439,26 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
if (distanceKm < 1.0) {
|
||||
int meters = (int)(distanceKm * 1000);
|
||||
if (meters > 0 && meters < 1000) {
|
||||
snprintf(distStr, sizeof(distStr), "%sDistance:%dm", leftSideSpacing, meters);
|
||||
snprintf(distStr, sizeof(distStr), " Distance: %dm", meters);
|
||||
haveDistance = true;
|
||||
} else if (meters >= 1000) {
|
||||
snprintf(distStr, sizeof(distStr), "%sDistance:1km", leftSideSpacing);
|
||||
snprintf(distStr, sizeof(distStr), " Distance: 1km");
|
||||
haveDistance = true;
|
||||
}
|
||||
} else {
|
||||
int km = (int)(distanceKm + 0.5);
|
||||
if (km > 0 && km < 1000) {
|
||||
snprintf(distStr, sizeof(distStr), "%sDistance:%dkm", leftSideSpacing, km);
|
||||
snprintf(distStr, sizeof(distStr), " Distance: %dkm", km);
|
||||
haveDistance = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (haveDistance && distStr[0]) {
|
||||
// Only display if we actually have a value!
|
||||
if (haveDistance && distStr[0] && line < 5) {
|
||||
display->drawString(x, getTextPositions(display)[line++], distStr);
|
||||
}
|
||||
|
||||
// === 6. Battery after Distance line, otherwise next available line ===
|
||||
char batLine[32] = "";
|
||||
bool haveBatLine = false;
|
||||
|
||||
if (node->has_device_metrics) {
|
||||
bool hasPct = node->device_metrics.has_battery_level;
|
||||
bool hasVolt = node->device_metrics.has_voltage && node->device_metrics.voltage > 0.001f;
|
||||
|
||||
int pct = 0;
|
||||
float volt = 0.0f;
|
||||
|
||||
if (hasPct) {
|
||||
pct = (int)node->device_metrics.battery_level;
|
||||
}
|
||||
|
||||
if (hasVolt) {
|
||||
volt = node->device_metrics.voltage;
|
||||
}
|
||||
|
||||
if (hasPct && pct > 0 && pct <= 100) {
|
||||
// Normal battery percentage
|
||||
if (hasVolt) {
|
||||
snprintf(batLine, sizeof(batLine), "%sBat:%d%% (%.2fV)", leftSideSpacing, pct, volt);
|
||||
} else {
|
||||
snprintf(batLine, sizeof(batLine), "%sBat:%d%%", leftSideSpacing, pct);
|
||||
}
|
||||
haveBatLine = true;
|
||||
} else if (hasPct && pct > 100) {
|
||||
// Plugged in
|
||||
if (hasVolt) {
|
||||
snprintf(batLine, sizeof(batLine), "%sPlugged In (%.2fV)", leftSideSpacing, volt);
|
||||
} else {
|
||||
snprintf(batLine, sizeof(batLine), "%sPlugged In", leftSideSpacing);
|
||||
}
|
||||
haveBatLine = true;
|
||||
} else if (!hasPct && hasVolt) {
|
||||
// Voltage only
|
||||
snprintf(batLine, sizeof(batLine), "%sBat:%.2fV", leftSideSpacing, volt);
|
||||
haveBatLine = true;
|
||||
}
|
||||
}
|
||||
|
||||
const int maxTextLines = (currentResolution == ScreenResolution::High) ? 6 : 5;
|
||||
|
||||
// Only draw battery if it fits within the allowed lines
|
||||
if (haveBatLine && line <= maxTextLines) {
|
||||
display->drawString(x, getTextPositions(display)[line++], batLine);
|
||||
}
|
||||
|
||||
// --- Compass Rendering: landscape (wide) screens use the original side-aligned logic ---
|
||||
if (SCREEN_WIDTH > SCREEN_HEIGHT) {
|
||||
bool showCompass = false;
|
||||
@@ -774,7 +593,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
}
|
||||
char uptimeStr[32] = "";
|
||||
if (currentResolution != ScreenResolution::UltraLow) {
|
||||
getUptimeStr(millis(), "Up: ", uptimeStr, sizeof(uptimeStr));
|
||||
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
|
||||
}
|
||||
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
|
||||
|
||||
@@ -1165,6 +984,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
||||
config.display.heading_bold = false;
|
||||
|
||||
const char *displayLine = ""; // Initialize to empty string by default
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||
if (config.position.fixed_position) {
|
||||
@@ -1209,10 +1029,10 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
||||
char uptimeStr[32];
|
||||
#if defined(USE_EINK)
|
||||
// E-Ink: skip seconds, show only days/hours/mins
|
||||
getUptimeStr(delta, "Last: ", uptimeStr, sizeof(uptimeStr), false);
|
||||
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), false);
|
||||
#else
|
||||
// Non E-Ink: include seconds where useful
|
||||
getUptimeStr(delta, "Last: ", uptimeStr, sizeof(uptimeStr), true);
|
||||
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), true);
|
||||
#endif
|
||||
|
||||
display->drawString(0, getTextPositions(display)[line++], uptimeStr);
|
||||
@@ -1390,7 +1210,6 @@ static int8_t lastFrameIndex = -1;
|
||||
static uint32_t lastFrameChangeTime = 0;
|
||||
constexpr uint32_t ICON_DISPLAY_DURATION_MS = 2000;
|
||||
|
||||
// cppcheck-suppress constParameterPointer; signature must match OverlayCallback typedef from OLEDDisplayUi library
|
||||
void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||
{
|
||||
int currentFrame = state->currentFrame;
|
||||
@@ -1423,11 +1242,6 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
const int totalWidth = (pageEnd - pageStart) * iconSize + (pageEnd - pageStart - 1) * spacing;
|
||||
const int xStart = (SCREEN_WIDTH - totalWidth) / 2;
|
||||
|
||||
// Suppress nav overlay entirely
|
||||
if (cannedMessageModule && cannedMessageModule->isFreeTextUIActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool navBarVisible = millis() - lastFrameChangeTime <= ICON_DISPLAY_DURATION_MS;
|
||||
int y = navBarVisible ? (SCREEN_HEIGHT - iconSize - 1) : SCREEN_HEIGHT;
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class UIRenderer
|
||||
// Navigation bar overlay
|
||||
static void drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||
|
||||
static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
static void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
|
||||
@@ -83,12 +83,6 @@ static const unsigned char mail[] PROGMEM = {
|
||||
0b11111111, 0b00 // Bottom line
|
||||
};
|
||||
|
||||
// Hop icon (9x10)
|
||||
#define hop_width 9
|
||||
#define hop_height 10
|
||||
const uint8_t hop[] PROGMEM = {0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x38, 0x00, 0x28, 0x00,
|
||||
0x38, 0x00, 0xC0, 0x01, 0x40, 0x01, 0xC0, 0x01, 0x40, 0x00};
|
||||
|
||||
// 📬 Mail / Message
|
||||
const uint8_t icon_mail[] PROGMEM = {
|
||||
0b11111111, // ████████ top border
|
||||
|
||||
@@ -42,7 +42,7 @@ int LatchingBacklight::beforeDeepSleep(void *unused)
|
||||
{
|
||||
// Contingency only
|
||||
// - pin wasn't set
|
||||
if (pin != static_cast<uint8_t>(-1)) {
|
||||
if (pin != (uint8_t)-1) {
|
||||
off();
|
||||
pinMode(pin, INPUT); // High impedance - unnecessary?
|
||||
} else
|
||||
@@ -55,7 +55,7 @@ int LatchingBacklight::beforeDeepSleep(void *unused)
|
||||
// The effect on the backlight is the same; peek and latch are separated to simplify short vs long press button handling
|
||||
void LatchingBacklight::peek()
|
||||
{
|
||||
assert(pin != static_cast<uint8_t>(-1));
|
||||
assert(pin != (uint8_t)-1);
|
||||
digitalWrite(pin, logicActive); // On
|
||||
on = true;
|
||||
latched = false;
|
||||
@@ -67,7 +67,7 @@ void LatchingBacklight::peek()
|
||||
// The effect on the backlight is the same; peek and latch are separated to simplify short vs long press button handling
|
||||
void LatchingBacklight::latch()
|
||||
{
|
||||
assert(pin != static_cast<uint8_t>(-1));
|
||||
assert(pin != (uint8_t)-1);
|
||||
|
||||
// Blink if moving from peek to latch
|
||||
// Indicates to user that the transition has taken place
|
||||
@@ -89,7 +89,7 @@ void LatchingBacklight::latch()
|
||||
// Suitable for ending both peek and latch
|
||||
void LatchingBacklight::off()
|
||||
{
|
||||
assert(pin != static_cast<uint8_t>(-1));
|
||||
assert(pin != (uint8_t)-1);
|
||||
digitalWrite(pin, !logicActive); // Off
|
||||
on = false;
|
||||
latched = false;
|
||||
|
||||
@@ -40,7 +40,7 @@ class LatchingBacklight
|
||||
CallbackObserver<LatchingBacklight, void *> deepSleepObserver =
|
||||
CallbackObserver<LatchingBacklight, void *>(this, &LatchingBacklight::beforeDeepSleep);
|
||||
|
||||
uint8_t pin = static_cast<uint8_t>(-1);
|
||||
uint8_t pin = (uint8_t)-1;
|
||||
bool logicActive = HIGH; // Is light active HIGH or active LOW
|
||||
|
||||
bool on = false; // Is light on (either peek or latched)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#include "graphics/niche/InkHUD/Tile.h"
|
||||
#include <cstdint>
|
||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
||||
|
||||
#include "./Applet.h"
|
||||
@@ -34,7 +32,7 @@ void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color)
|
||||
{
|
||||
// Only render pixels if they fall within user's cropped region
|
||||
if (x >= cropLeft && x < (cropLeft + cropWidth) && y >= cropTop && y < (cropTop + cropHeight))
|
||||
assignedTile->handleAppletPixel(x, y, static_cast<Color>(color));
|
||||
assignedTile->handleAppletPixel(x, y, (Color)color);
|
||||
}
|
||||
|
||||
// Link our applet to a tile
|
||||
@@ -144,21 +142,6 @@ void InkHUD::Applet::resetDrawingSpace()
|
||||
setFont(fontSmall);
|
||||
}
|
||||
|
||||
// Sets one or more inputs to enabled/disabled for this applet and if they should be sent to it
|
||||
void InkHUD::Applet::setInputsSubscribed(uint8_t input, bool captured)
|
||||
{
|
||||
if (captured)
|
||||
subscribedInputs |= input;
|
||||
else
|
||||
subscribedInputs &= ~input;
|
||||
}
|
||||
|
||||
// Checks if a specific input is enabled for this applet and should be sent to it
|
||||
bool InkHUD::Applet::isInputSubscribed(InputMask input)
|
||||
{
|
||||
return (subscribedInputs & input) == input;
|
||||
}
|
||||
|
||||
// Tell InkHUD::Renderer that we want to render now
|
||||
// Applets should internally listen for events they are interested in, via MeshModule, CallbackObserver etc
|
||||
// When an applet decides it has heard something important, and wants to redraw, it calls this method
|
||||
@@ -327,7 +310,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, const char *text, HorizontalA
|
||||
}
|
||||
|
||||
// Print text, specifying the position of any edge / corner of the textbox
|
||||
void InkHUD::Applet::printAt(int16_t x, int16_t y, const std::string &text, HorizontalAlignment ha, VerticalAlignment va)
|
||||
void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha, VerticalAlignment va)
|
||||
{
|
||||
printAt(x, y, text.c_str(), ha, va);
|
||||
}
|
||||
@@ -349,7 +332,7 @@ InkHUD::AppletFont InkHUD::Applet::getFont()
|
||||
|
||||
// Parse any text which might have "special characters"
|
||||
// Re-encodes UTF-8 characters to match our 8-bit encoded fonts
|
||||
std::string InkHUD::Applet::parse(const std::string &text)
|
||||
std::string InkHUD::Applet::parse(std::string text)
|
||||
{
|
||||
return getFont().decodeUTF8(text);
|
||||
}
|
||||
@@ -376,10 +359,10 @@ std::string InkHUD::Applet::parseShortName(meshtastic_NodeInfoLite *node)
|
||||
}
|
||||
|
||||
// Determine if all characters of a string are printable using the current font
|
||||
bool InkHUD::Applet::isPrintable(const std::string &text)
|
||||
bool InkHUD::Applet::isPrintable(std::string text)
|
||||
{
|
||||
// Scan for SUB (0x1A), which is the value assigned by AppletFont::applyEncoding if a unicode character is not handled
|
||||
for (const char &c : text) {
|
||||
for (char &c : text) {
|
||||
if (c == '\x1A')
|
||||
return false;
|
||||
}
|
||||
@@ -402,7 +385,7 @@ uint16_t InkHUD::Applet::getTextWidth(const char *text)
|
||||
|
||||
// Gets rendered width of a string
|
||||
// Wrapper for getTextBounds
|
||||
uint16_t InkHUD::Applet::getTextWidth(const std::string &text)
|
||||
uint16_t InkHUD::Applet::getTextWidth(std::string text)
|
||||
{
|
||||
return getTextWidth(text.c_str());
|
||||
}
|
||||
@@ -450,7 +433,7 @@ std::string InkHUD::Applet::hexifyNodeNum(NodeNum num)
|
||||
|
||||
// Print text, with word wrapping
|
||||
// Avoids splitting words in half, instead moving the entire word to a new line wherever possible
|
||||
void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, const std::string &text)
|
||||
void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std::string text)
|
||||
{
|
||||
// Place the AdafruitGFX cursor to suit our "top" coord
|
||||
setCursor(left, top + getFont().heightAboveCursor());
|
||||
@@ -507,15 +490,15 @@ void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, con
|
||||
|
||||
// Todo: rewrite making use of AdafruitGFX native text wrapping
|
||||
char cstr[] = {0, 0};
|
||||
int16_t bx, by;
|
||||
uint16_t bw, bh;
|
||||
int16_t l, t;
|
||||
uint16_t w, h;
|
||||
for (uint16_t c = 0; c < word.length(); c++) {
|
||||
// Shove next char into a c string
|
||||
cstr[0] = word[c];
|
||||
getTextBounds(cstr, getCursorX(), getCursorY(), &bx, &by, &bw, &bh);
|
||||
getTextBounds(cstr, getCursorX(), getCursorY(), &l, &t, &w, &h);
|
||||
|
||||
// Manual newline, if next character will spill beyond screen edge
|
||||
if ((bx + bw) > left + width)
|
||||
if ((l + w) > left + width)
|
||||
setCursor(left, getCursorY() + getFont().lineHeight());
|
||||
|
||||
// Print next character
|
||||
@@ -534,7 +517,7 @@ void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, con
|
||||
|
||||
// Simulate running printWrapped, to determine how tall the block of text will be.
|
||||
// This is a wasteful way of handling things. Maybe some way to optimize in future?
|
||||
uint32_t InkHUD::Applet::getWrappedTextHeight(int16_t left, uint16_t width, const std::string &text)
|
||||
uint32_t InkHUD::Applet::getWrappedTextHeight(int16_t left, uint16_t width, std::string text)
|
||||
{
|
||||
// Cache the current crop region
|
||||
int16_t cL = cropLeft;
|
||||
@@ -664,7 +647,7 @@ uint16_t InkHUD::Applet::getActiveNodeCount()
|
||||
|
||||
// For each node in db
|
||||
for (uint16_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Check if heard recently, and not our own node
|
||||
if (sinceLastSeen(node) < settings->recentlyActiveSeconds && node->num != nodeDB->getNodeNum())
|
||||
@@ -717,7 +700,7 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters)
|
||||
}
|
||||
|
||||
// Print text with a "faux bold" effect, by drawing it multiple times, offsetting slightly
|
||||
void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, const std::string &text, uint8_t thicknessX, uint8_t thicknessY)
|
||||
void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY)
|
||||
{
|
||||
// How many times to draw along x axis
|
||||
int16_t xStart;
|
||||
@@ -785,7 +768,7 @@ bool InkHUD::Applet::approveNotification(NicheGraphics::InkHUD::Notification &n)
|
||||
│ │
|
||||
└───────────────────────────────┘
|
||||
*/
|
||||
void InkHUD::Applet::drawHeader(const std::string &text)
|
||||
void InkHUD::Applet::drawHeader(std::string text)
|
||||
{
|
||||
// Y position for divider
|
||||
// - between header text and messages
|
||||
@@ -802,16 +785,6 @@ void InkHUD::Applet::drawHeader(const std::string &text)
|
||||
drawPixel(x, 0, BLACK);
|
||||
drawPixel(x, headerDivY, BLACK); // Dotted 50%
|
||||
}
|
||||
|
||||
// Dither near battery
|
||||
if (settings->optionalFeatures.batteryIcon) {
|
||||
constexpr uint16_t ditherSizePx = 4;
|
||||
Tile *batteryTile = ((Applet *)inkhud->getSystemApplet("BatteryIcon"))->getTile();
|
||||
const uint16_t batteryTileLeft = batteryTile->getLeft();
|
||||
const uint16_t batteryTileTop = batteryTile->getTop();
|
||||
const uint16_t batteryTileHeight = batteryTile->getHeight();
|
||||
hatchRegion(batteryTileLeft - ditherSizePx, batteryTileTop, ditherSizePx, batteryTileHeight, 2, WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the height of the standard applet header
|
||||
|
||||
@@ -89,9 +89,6 @@ class Applet : public GFX
|
||||
virtual void onForeground() {}
|
||||
virtual void onBackground() {}
|
||||
virtual void onShutdown() {}
|
||||
|
||||
// Input Events
|
||||
|
||||
virtual void onButtonShortPress() {}
|
||||
virtual void onButtonLongPress() {}
|
||||
virtual void onExitShort() {}
|
||||
@@ -103,18 +100,6 @@ class Applet : public GFX
|
||||
virtual void onFreeText(char c) {}
|
||||
virtual void onFreeTextDone() {}
|
||||
virtual void onFreeTextCancel() {}
|
||||
// List of inputs which can be subscribed to
|
||||
enum InputMask { // | No Joystick | With Joystick |
|
||||
BUTTON_SHORT = 1, // | Button Click | Joystick Center Click |
|
||||
BUTTON_LONG = 2, // | Button Hold | Joystick Center Hold |
|
||||
EXIT_SHORT = 4, // | no-op | Back Button Click |
|
||||
EXIT_LONG = 8, // | no-op | Back Button Hold |
|
||||
NAV_UP = 16, // | no-op | Joystick Up |
|
||||
NAV_DOWN = 32, // | no-op | Joystick Down |
|
||||
NAV_LEFT = 64, // | no-op | Joystick Left |
|
||||
NAV_RIGHT = 128 // | no-op | Joystick Right |
|
||||
};
|
||||
bool isInputSubscribed(InputMask input); // Check if input should be handled by applet, this should not be overloaded.
|
||||
|
||||
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
||||
|
||||
@@ -136,28 +121,20 @@ class Applet : public GFX
|
||||
void setCrop(int16_t left, int16_t top, uint16_t width, uint16_t height); // Ignore pixels drawn outside a certain region
|
||||
void resetCrop(); // Removes setCrop()
|
||||
|
||||
// User Input Handling
|
||||
|
||||
uint8_t subscribedInputs = 0b00000000; // Maybe uint16_t for futureproofing? other devices may need more inputs
|
||||
void setInputsSubscribed(uint8_t input,
|
||||
bool captured); // Set if an input should be handled by applet or not, this should not be
|
||||
// overloaded. Can take multiple inputs at once if you OR/`|` them together
|
||||
|
||||
// Text
|
||||
|
||||
void setFont(AppletFont f);
|
||||
AppletFont getFont();
|
||||
uint16_t getTextWidth(const std::string &text);
|
||||
uint16_t getTextWidth(std::string text);
|
||||
uint16_t getTextWidth(const char *text);
|
||||
uint32_t getWrappedTextHeight(int16_t left, uint16_t width, const std::string &text); // Result of printWrapped
|
||||
uint32_t getWrappedTextHeight(int16_t left, uint16_t width, std::string text); // Result of printWrapped
|
||||
void printAt(int16_t x, int16_t y, const char *text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
|
||||
void printAt(int16_t x, int16_t y, const std::string &text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
|
||||
void printThick(int16_t xCenter, int16_t yCenter, const std::string &text, uint8_t thicknessX,
|
||||
uint8_t thicknessY); // Faux bold
|
||||
void printWrapped(int16_t left, int16_t top, uint16_t width, const std::string &text); // Per-word line wrapping
|
||||
void printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
|
||||
void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY); // Faux bold
|
||||
void printWrapped(int16_t left, int16_t top, uint16_t width, std::string text); // Per-word line wrapping
|
||||
|
||||
void hatchRegion(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t spacing, Color color); // Fill with sparse lines
|
||||
void drawHeader(const std::string &text); // Draw the standard applet header
|
||||
void drawHeader(std::string text); // Draw the standard applet header
|
||||
|
||||
// Meshtastic Logo
|
||||
|
||||
@@ -173,9 +150,9 @@ class Applet : public GFX
|
||||
std::string getTimeString(); // Current time, human readable
|
||||
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
|
||||
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
|
||||
std::string parse(const std::string &text); // Handle text which might contain special chars
|
||||
std::string parse(std::string text); // Handle text which might contain special chars
|
||||
std::string parseShortName(meshtastic_NodeInfoLite *node); // Get the shortname, or a substitute if has unprintable chars
|
||||
bool isPrintable(const std::string &text); // Check for characters which the font can't print
|
||||
bool isPrintable(std::string); // Check for characters which the font can't print
|
||||
|
||||
// Convenient references
|
||||
|
||||
|
||||
@@ -39,11 +39,11 @@ InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding
|
||||
// Caution: signed and unsigned types
|
||||
int8_t glyphAscender = 0 - gfxFont->glyph[i].yOffset;
|
||||
if (glyphAscender > 0)
|
||||
this->ascenderHeight = max(this->ascenderHeight, static_cast<uint8_t>(glyphAscender));
|
||||
this->ascenderHeight = max(this->ascenderHeight, (uint8_t)glyphAscender);
|
||||
|
||||
int8_t glyphDescender = gfxFont->glyph[i].height + gfxFont->glyph[i].yOffset;
|
||||
if (glyphDescender > 0)
|
||||
this->descenderHeight = max(this->descenderHeight, static_cast<uint8_t>(glyphDescender));
|
||||
this->descenderHeight = max(this->descenderHeight, (uint8_t)glyphDescender);
|
||||
}
|
||||
|
||||
// Apply any manual padding to grow or shrink the line size
|
||||
@@ -52,7 +52,7 @@ InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding
|
||||
descenderHeight += paddingBottom;
|
||||
|
||||
// Find how far the cursor advances when we "print" a space character
|
||||
spaceCharWidth = gfxFont->glyph[static_cast<uint8_t>(' ') - gfxFont->first].xAdvance;
|
||||
spaceCharWidth = gfxFont->glyph[(uint8_t)' ' - gfxFont->first].xAdvance;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -98,7 +98,7 @@ uint8_t InkHUD::AppletFont::widthBetweenWords()
|
||||
|
||||
// Convert a unicode char from set of UTF-8 bytes to UTF-32
|
||||
// Used by AppletFont::applyEncoding, which remaps unicode chars for extended ASCII fonts, based on their UTF-32 value
|
||||
uint32_t InkHUD::AppletFont::toUtf32(const std::string &utf8)
|
||||
uint32_t InkHUD::AppletFont::toUtf32(std::string utf8)
|
||||
{
|
||||
uint32_t utf32 = 0;
|
||||
|
||||
@@ -132,7 +132,7 @@ uint32_t InkHUD::AppletFont::toUtf32(const std::string &utf8)
|
||||
|
||||
// Process a string, collating UTF-8 bytes, and sending them off for re-encoding to extended ASCII
|
||||
// Not all InkHUD text is passed through here, only text which could potentially contain non-ASCII chars
|
||||
std::string InkHUD::AppletFont::decodeUTF8(const std::string &encoded)
|
||||
std::string InkHUD::AppletFont::decodeUTF8(std::string encoded)
|
||||
{
|
||||
// Final processed output
|
||||
std::string decoded;
|
||||
@@ -141,7 +141,7 @@ std::string InkHUD::AppletFont::decodeUTF8(const std::string &encoded)
|
||||
std::string utf8Char;
|
||||
uint8_t utf8CharSize = 0;
|
||||
|
||||
for (const char &c : encoded) {
|
||||
for (char &c : encoded) {
|
||||
|
||||
// If first byte
|
||||
if (utf8Char.empty()) {
|
||||
@@ -178,7 +178,7 @@ std::string InkHUD::AppletFont::decodeUTF8(const std::string &encoded)
|
||||
|
||||
// Re-encode a single UTF-8 character to extended ASCII
|
||||
// Target encoding depends on the font
|
||||
char InkHUD::AppletFont::applyEncoding(const std::string &utf8)
|
||||
char InkHUD::AppletFont::applyEncoding(std::string utf8)
|
||||
{
|
||||
// ##################################################### Syntactic Sugar #####################################################
|
||||
#define REMAP(in, out) \
|
||||
|
||||
@@ -30,21 +30,20 @@ class AppletFont
|
||||
};
|
||||
|
||||
AppletFont();
|
||||
explicit AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding = ASCII, int8_t paddingTop = 0,
|
||||
int8_t paddingBottom = 0);
|
||||
AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding = ASCII, int8_t paddingTop = 0, int8_t paddingBottom = 0);
|
||||
|
||||
uint8_t lineHeight();
|
||||
uint8_t heightAboveCursor();
|
||||
uint8_t heightBelowCursor();
|
||||
uint8_t widthBetweenWords(); // Width of the space character
|
||||
|
||||
std::string decodeUTF8(const std::string &encoded);
|
||||
std::string decodeUTF8(std::string encoded);
|
||||
|
||||
const GFXfont *gfxFont = nullptr; // Default value: in-built AdafruitGFX font
|
||||
const GFXfont *gfxFont = NULL; // Default value: in-built AdafruitGFX font
|
||||
|
||||
private:
|
||||
uint32_t toUtf32(const std::string &utf8);
|
||||
char applyEncoding(const std::string &utf8);
|
||||
uint32_t toUtf32(std::string utf8);
|
||||
char applyEncoding(std::string utf8);
|
||||
|
||||
uint8_t height = 8; // Default value: in-built AdafruitGFX font
|
||||
uint8_t ascenderHeight = 0; // Default value: in-built AdafruitGFX font
|
||||
|
||||
@@ -81,25 +81,25 @@ ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacke
|
||||
uint8_t InkHUD::NodeListApplet::maxCards()
|
||||
{
|
||||
// Cache result. Shouldn't change during execution
|
||||
static uint8_t maxCardCount = 0;
|
||||
static uint8_t cards = 0;
|
||||
|
||||
if (!maxCardCount) {
|
||||
if (!cards) {
|
||||
const uint16_t height = Tile::maxDisplayDimension();
|
||||
|
||||
// Use a loop instead of arithmetic, because it's easier for my brain to follow
|
||||
// Add cards one by one, until the latest card extends below screen
|
||||
|
||||
uint16_t y = cardH; // First card: no margin above
|
||||
maxCardCount = 1;
|
||||
cards = 1;
|
||||
|
||||
while (y < height) {
|
||||
y += cardMarginH;
|
||||
y += cardH;
|
||||
maxCardCount++;
|
||||
cards++;
|
||||
}
|
||||
}
|
||||
|
||||
return maxCardCount;
|
||||
return cards;
|
||||
}
|
||||
|
||||
// Draw, using info which derived applet placed into NodeListApplet::cards for us
|
||||
@@ -137,12 +137,12 @@ void InkHUD::NodeListApplet::onRender(bool full)
|
||||
|
||||
// Gather info
|
||||
// ========================================
|
||||
const NodeNum &nodeNum = card->nodeNum;
|
||||
NodeNum &nodeNum = card->nodeNum;
|
||||
SignalStrength &signal = card->signal;
|
||||
std::string longName; // handled below
|
||||
std::string shortName; // handled below
|
||||
std::string distance; // handled below;
|
||||
const uint8_t &hopsAway = card->hopsAway;
|
||||
uint8_t &hopsAway = card->hopsAway;
|
||||
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
|
||||
|
||||
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
||||
#include "./UserAppletInputExample.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
void InkHUD::UserAppletInputExampleApplet::onActivate()
|
||||
{
|
||||
setGrabbed(false);
|
||||
}
|
||||
|
||||
void InkHUD::UserAppletInputExampleApplet::onRender(bool full)
|
||||
{
|
||||
drawHeader("Input Example");
|
||||
uint16_t headerHeight = getHeaderHeight();
|
||||
|
||||
std::string buttonName;
|
||||
if (settings->joystick.enabled)
|
||||
buttonName = "joystick center button";
|
||||
else
|
||||
buttonName = "user button";
|
||||
|
||||
std::string additional = " | Control is grabbed, long press " + buttonName + " to release controls";
|
||||
if (!isGrabbed)
|
||||
additional = " | Control is released, long press " + buttonName + " to grab controls";
|
||||
|
||||
printWrapped(0, headerHeight, width(), "Last button: " + lastInput + additional);
|
||||
}
|
||||
|
||||
void InkHUD::UserAppletInputExampleApplet::setGrabbed(bool grabbed)
|
||||
{
|
||||
isGrabbed = grabbed;
|
||||
setInputsSubscribed(BUTTON_SHORT | EXIT_SHORT | EXIT_LONG | NAV_UP | NAV_DOWN | NAV_LEFT | NAV_RIGHT,
|
||||
grabbed); // Enables/disables grabbing all inputs
|
||||
setInputsSubscribed(BUTTON_LONG, true); // Always grab this input
|
||||
}
|
||||
|
||||
void InkHUD::UserAppletInputExampleApplet::onButtonShortPress()
|
||||
{
|
||||
lastInput = "BUTTON_SHORT";
|
||||
requestUpdate();
|
||||
}
|
||||
void InkHUD::UserAppletInputExampleApplet::onButtonLongPress()
|
||||
{
|
||||
lastInput = "BUTTON_LONG";
|
||||
setGrabbed(!isGrabbed);
|
||||
requestUpdate();
|
||||
}
|
||||
void InkHUD::UserAppletInputExampleApplet::onExitShort()
|
||||
{
|
||||
lastInput = "EXIT_SHORT";
|
||||
requestUpdate();
|
||||
}
|
||||
void InkHUD::UserAppletInputExampleApplet::onExitLong()
|
||||
{
|
||||
lastInput = "EXIT_LONG";
|
||||
requestUpdate();
|
||||
}
|
||||
void InkHUD::UserAppletInputExampleApplet::onNavUp()
|
||||
{
|
||||
lastInput = "NAV_UP";
|
||||
requestUpdate();
|
||||
}
|
||||
void InkHUD::UserAppletInputExampleApplet::onNavDown()
|
||||
{
|
||||
lastInput = "NAV_DOWN";
|
||||
requestUpdate();
|
||||
}
|
||||
void InkHUD::UserAppletInputExampleApplet::onNavLeft()
|
||||
{
|
||||
lastInput = "NAV_LEFT";
|
||||
requestUpdate();
|
||||
}
|
||||
void InkHUD::UserAppletInputExampleApplet::onNavRight()
|
||||
{
|
||||
lastInput = "NAV_RIGHT";
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
#endif
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "graphics/niche/InkHUD/Applet.h"
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
{
|
||||
|
||||
class UserAppletInputExampleApplet : public Applet
|
||||
{
|
||||
public:
|
||||
void onActivate() override;
|
||||
|
||||
void onRender(bool full) override;
|
||||
void onButtonShortPress() override;
|
||||
void onButtonLongPress() override;
|
||||
void onExitShort() override;
|
||||
void onExitLong() override;
|
||||
void onNavUp() override;
|
||||
void onNavDown() override;
|
||||
void onNavLeft() override;
|
||||
void onNavRight() override;
|
||||
|
||||
private:
|
||||
std::string lastInput = "None";
|
||||
bool isGrabbed = false;
|
||||
|
||||
void setGrabbed(bool grabbed);
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
#endif
|
||||
@@ -29,10 +29,10 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta
|
||||
// If we get a different type of status, something has gone weird elsewhere
|
||||
assert(status->getStatusType() == STATUS_TYPE_POWER);
|
||||
|
||||
const meshtastic::PowerStatus *pwrStatus = (const meshtastic::PowerStatus *)status;
|
||||
meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)status;
|
||||
|
||||
// Get the new state of charge %, and round to the nearest 10%
|
||||
uint8_t newSocRounded = ((pwrStatus->getBatteryChargePercent() + 5) / 10) * 10;
|
||||
uint8_t newSocRounded = ((powerStatus->getBatteryChargePercent() + 5) / 10) * 10;
|
||||
|
||||
// If rounded value has changed, trigger a display update
|
||||
// It's okay to requestUpdate before we store the new value, as the update won't run until next loop()
|
||||
@@ -48,27 +48,37 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta
|
||||
|
||||
void InkHUD::BatteryIconApplet::onRender(bool full)
|
||||
{
|
||||
// Clear the region beneath the tile, including the border
|
||||
// Fill entire tile
|
||||
// - size of icon controlled by size of tile
|
||||
int16_t l = 0;
|
||||
int16_t t = 0;
|
||||
uint16_t w = width();
|
||||
int16_t h = height();
|
||||
|
||||
// Clear the region beneath the tile
|
||||
// Most applets are drawing onto an empty frame buffer and don't need to do this
|
||||
// We do need to do this with the battery though, as it is an "overlay"
|
||||
fillRect(0, 0, width(), height(), WHITE);
|
||||
fillRect(l, t, w, h, WHITE);
|
||||
|
||||
// Vertical centerline
|
||||
const int16_t m = t + (h / 2);
|
||||
|
||||
// =====================
|
||||
// Draw battery outline
|
||||
// =====================
|
||||
|
||||
// Positive terminal "bump"
|
||||
const int16_t &bumpL = l;
|
||||
const uint16_t bumpH = h / 2;
|
||||
const int16_t bumpT = m - (bumpH / 2);
|
||||
constexpr uint16_t bumpW = 2;
|
||||
const int16_t &bumpL = 1;
|
||||
const uint16_t bumpH = (height() - 2) / 2;
|
||||
const int16_t bumpT = (1 + ((height() - 2) / 2)) - (bumpH / 2);
|
||||
fillRect(bumpL, bumpT, bumpW, bumpH, BLACK);
|
||||
|
||||
// Main body of battery
|
||||
const int16_t bodyL = 1 + bumpW;
|
||||
const int16_t &bodyT = 1;
|
||||
const int16_t &bodyH = height() - 2; // Handle top/bottom padding
|
||||
const int16_t bodyW = (width() - 1) - bumpW; // Handle 1px left pad
|
||||
const int16_t bodyL = bumpL + bumpW;
|
||||
const int16_t &bodyT = t;
|
||||
const int16_t &bodyH = h;
|
||||
const int16_t bodyW = w - bumpW;
|
||||
drawRect(bodyL, bodyT, bodyW, bodyH, BLACK);
|
||||
|
||||
// Erase join between bump and body
|
||||
@@ -79,13 +89,12 @@ void InkHUD::BatteryIconApplet::onRender(bool full)
|
||||
// ===================
|
||||
|
||||
constexpr int16_t slicePad = 2;
|
||||
int16_t sliceL = bodyL + slicePad;
|
||||
const int16_t sliceL = bodyL + slicePad;
|
||||
const int16_t sliceT = bodyT + slicePad;
|
||||
const uint16_t sliceH = bodyH - (slicePad * 2);
|
||||
uint16_t sliceW = bodyW - (slicePad * 2);
|
||||
|
||||
sliceW = (sliceW * socRounded) / 100; // Apply percentage
|
||||
sliceL += ((bodyW - (slicePad * 2)) - sliceW); // Shift slice to the battery's negative terminal, correcting drain direction
|
||||
sliceW = (sliceW * socRounded) / 100; // Apply percentage
|
||||
|
||||
hatchRegion(sliceL, sliceT, sliceW, sliceH, 2, BLACK);
|
||||
drawRect(sliceL, sliceT, sliceW, sliceH, BLACK);
|
||||
|
||||
@@ -1176,7 +1176,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
items.push_back(MenuItem("Back", previousPage));
|
||||
|
||||
for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) {
|
||||
const meshtastic_Channel &ch = channels.getByIndex(i);
|
||||
meshtastic_Channel &ch = channels.getByIndex(i);
|
||||
|
||||
if (!ch.has_settings)
|
||||
continue;
|
||||
@@ -1252,7 +1252,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
case NODE_CONFIG_CHANNEL_PRECISION: {
|
||||
previousPage = MenuPage::NODE_CONFIG_CHANNEL_DETAIL;
|
||||
items.push_back(MenuItem("Back", previousPage));
|
||||
const meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex);
|
||||
meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex);
|
||||
if (!ch.settings.has_module_settings || ch.settings.module_settings.position_precision == 0) {
|
||||
items.push_back(MenuItem("Position is Off", MenuPage::NODE_CONFIG_CHANNEL_DETAIL));
|
||||
break;
|
||||
@@ -1524,15 +1524,7 @@ void InkHUD::MenuApplet::onButtonShortPress()
|
||||
if (!settings->joystick.enabled) {
|
||||
if (!cursorShown) {
|
||||
cursorShown = true;
|
||||
// Select the first item that isn't a header
|
||||
cursor = 0;
|
||||
while (cursor < items.size() && items.at(cursor).isHeader) {
|
||||
cursor++;
|
||||
}
|
||||
if (cursor >= items.size()) {
|
||||
cursorShown = false;
|
||||
cursor = 0;
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
cursor = (cursor + 1) % items.size();
|
||||
@@ -1584,15 +1576,7 @@ void InkHUD::MenuApplet::onNavUp()
|
||||
|
||||
if (!cursorShown) {
|
||||
cursorShown = true;
|
||||
// Select the last item that isn't a header
|
||||
cursor = items.size() - 1;
|
||||
while (items.at(cursor).isHeader) {
|
||||
if (cursor == 0) {
|
||||
cursorShown = false;
|
||||
break;
|
||||
}
|
||||
cursor--;
|
||||
}
|
||||
cursor = 0;
|
||||
} else {
|
||||
do {
|
||||
if (cursor == 0)
|
||||
@@ -1613,15 +1597,7 @@ void InkHUD::MenuApplet::onNavDown()
|
||||
|
||||
if (!cursorShown) {
|
||||
cursorShown = true;
|
||||
// Select the first item that isn't a header
|
||||
cursor = 0;
|
||||
while (cursor < items.size() && items.at(cursor).isHeader) {
|
||||
cursor++;
|
||||
}
|
||||
if (cursor >= items.size()) {
|
||||
cursorShown = false;
|
||||
cursor = 0;
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
cursor = (cursor + 1) % items.size();
|
||||
@@ -1783,7 +1759,7 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
||||
|
||||
for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) {
|
||||
// Get the channel, and check if it's enabled
|
||||
const meshtastic_Channel &channel = channels.getByIndex(i);
|
||||
meshtastic_Channel &channel = channels.getByIndex(i);
|
||||
if (!channel.has_settings || channel.role == meshtastic_Channel_Role_DISABLED)
|
||||
continue;
|
||||
|
||||
@@ -1853,7 +1829,7 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
}
|
||||
|
||||
void InkHUD::MenuApplet::drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, const std::string &text)
|
||||
void InkHUD::MenuApplet::drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, std::string text)
|
||||
{
|
||||
setFont(fontSmall);
|
||||
uint16_t wrapMaxH = 0;
|
||||
|
||||
@@ -55,7 +55,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
||||
void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds
|
||||
|
||||
void drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height,
|
||||
const std::string &text); // Draw input field for free text
|
||||
std::string text); // Draw input field for free text
|
||||
uint16_t getSystemInfoPanelHeight();
|
||||
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
|
||||
uint16_t *height = nullptr); // Info panel at top of root menu
|
||||
|
||||
@@ -228,17 +228,17 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
|
||||
Notification::Type::NOTIFICATION_MESSAGE_BROADCAST)) {
|
||||
|
||||
// Although we are handling DM and broadcast notifications together, we do need to treat them slightly differently
|
||||
bool msgIsBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST;
|
||||
bool isBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST;
|
||||
|
||||
// Pick source of message
|
||||
const MessageStore::Message *message =
|
||||
msgIsBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
|
||||
MessageStore::Message *message =
|
||||
isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
|
||||
|
||||
// Find info about the sender
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);
|
||||
|
||||
// Leading tag (channel vs. DM)
|
||||
text += msgIsBroadcast ? "From:" : "DM: ";
|
||||
text += isBroadcast ? "From:" : "DM: ";
|
||||
|
||||
// Sender id
|
||||
if (node && node->has_user)
|
||||
@@ -252,7 +252,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
|
||||
text.clear();
|
||||
|
||||
// Leading tag (channel vs. DM)
|
||||
text += msgIsBroadcast ? "Msg from " : "DM from ";
|
||||
text += isBroadcast ? "Msg from " : "DM from ";
|
||||
|
||||
// Sender id
|
||||
if (node && node->has_user)
|
||||
|
||||
@@ -55,12 +55,12 @@ int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *sta
|
||||
// We'll mimic that behavior, just to keep in line with the other Statuses,
|
||||
// even though I'm not sure what the original reason for jumping through these extra hoops was.
|
||||
assert(status->getStatusType() == STATUS_TYPE_BLUETOOTH);
|
||||
const auto *btStatus = static_cast<const meshtastic::BluetoothStatus *>(status);
|
||||
meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)status;
|
||||
|
||||
// When pairing begins
|
||||
if (btStatus->getConnectionState() == meshtastic::BluetoothStatus::ConnectionState::PAIRING) {
|
||||
if (bluetoothStatus->getConnectionState() == meshtastic::BluetoothStatus::ConnectionState::PAIRING) {
|
||||
// Store the passkey for rendering
|
||||
passkey = btStatus->getPasskey();
|
||||
passkey = bluetoothStatus->getPasskey();
|
||||
|
||||
// Show pairing screen
|
||||
bringToForeground();
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
||||
|
||||
#include "./FavoritesMapApplet.h"
|
||||
#include "NodeDB.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
bool InkHUD::FavoritesMapApplet::shouldDrawNode(meshtastic_NodeInfoLite *node)
|
||||
{
|
||||
// Keep our own node available as map anchor/center; all others must be favorited.
|
||||
return node && (node->num == nodeDB->getNodeNum() || node->is_favorite);
|
||||
}
|
||||
|
||||
void InkHUD::FavoritesMapApplet::onRender(bool full)
|
||||
{
|
||||
// Custom empty state text for favorites-only map.
|
||||
if (!enoughMarkers()) {
|
||||
printAt(X(0.5), Y(0.5) - (getFont().lineHeight() / 2), "Favorite node position", CENTER, MIDDLE);
|
||||
printAt(X(0.5), Y(0.5) + (getFont().lineHeight() / 2), "will appear here", CENTER, MIDDLE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw the usual map applet first.
|
||||
MapApplet::onRender(full);
|
||||
|
||||
// Draw our latest "node of interest" as a special marker.
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(lastFrom);
|
||||
if (node && node->is_favorite && nodeDB->hasValidPosition(node) && enoughMarkers())
|
||||
drawLabeledMarker(node);
|
||||
}
|
||||
|
||||
// Determine if we need to redraw the map, when we receive a new position packet.
|
||||
ProcessMessage InkHUD::FavoritesMapApplet::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
// If applet is not active, we shouldn't be handling any data.
|
||||
if (!isActive())
|
||||
return ProcessMessage::CONTINUE;
|
||||
|
||||
// Try decode a position from the packet.
|
||||
bool hasPosition = false;
|
||||
float lat;
|
||||
float lng;
|
||||
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.decoded.portnum == meshtastic_PortNum_POSITION_APP) {
|
||||
meshtastic_Position position = meshtastic_Position_init_default;
|
||||
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Position_msg, &position)) {
|
||||
if (position.has_latitude_i && position.has_longitude_i // Actually has position
|
||||
&& (position.latitude_i != 0 || position.longitude_i != 0)) // Position isn't "null island"
|
||||
{
|
||||
hasPosition = true;
|
||||
lat = position.latitude_i * 1e-7; // Convert from Meshtastic's internal int32_t format
|
||||
lng = position.longitude_i * 1e-7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip if we didn't get a valid position.
|
||||
if (!hasPosition)
|
||||
return ProcessMessage::CONTINUE;
|
||||
|
||||
const int8_t hopsAway = getHopsAway(mp);
|
||||
const bool hasHopsAway = hopsAway >= 0;
|
||||
|
||||
// Determine if the position packet would change anything on-screen.
|
||||
bool somethingChanged = false;
|
||||
|
||||
// If our own position.
|
||||
if (isFromUs(&mp)) {
|
||||
// Ignore tiny local movement to reduce update spam.
|
||||
if (GeoCoord::latLongToMeter(ourLastLat, ourLastLng, lat, lng) > 50) {
|
||||
somethingChanged = true;
|
||||
ourLastLat = lat;
|
||||
ourLastLng = lng;
|
||||
}
|
||||
} else {
|
||||
// For non-local packets, this applet only reacts to favorited nodes.
|
||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
|
||||
if (!sender || !sender->is_favorite)
|
||||
return ProcessMessage::CONTINUE;
|
||||
|
||||
// Check if this position is from someone different than our previous position packet.
|
||||
if (mp.from != lastFrom) {
|
||||
somethingChanged = true;
|
||||
lastFrom = mp.from;
|
||||
lastLat = lat;
|
||||
lastLng = lng;
|
||||
lastHopsAway = hopsAway;
|
||||
}
|
||||
|
||||
// Same sender: check if position changed.
|
||||
else if (GeoCoord::latLongToMeter(lastLat, lastLng, lat, lng) > 10) {
|
||||
somethingChanged = true;
|
||||
lastLat = lat;
|
||||
lastLng = lng;
|
||||
}
|
||||
|
||||
// Same sender, same position: check if hops changed.
|
||||
else if (hasHopsAway && (hopsAway != lastHopsAway)) {
|
||||
somethingChanged = true;
|
||||
lastHopsAway = hopsAway;
|
||||
}
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
requestAutoshow();
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
return ProcessMessage::CONTINUE;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,44 +0,0 @@
|
||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
||||
|
||||
/*
|
||||
|
||||
Plots position of favorited nodes from DB, with North facing up.
|
||||
Scaled to fit the most distant node.
|
||||
Size of marker represents hops away.
|
||||
The favorite node which most recently sent a position will be labeled.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h"
|
||||
|
||||
#include "SinglePortModule.h"
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
{
|
||||
|
||||
class FavoritesMapApplet : public MapApplet, public SinglePortModule
|
||||
{
|
||||
public:
|
||||
FavoritesMapApplet() : SinglePortModule("FavoritesMapApplet", meshtastic_PortNum_POSITION_APP) {}
|
||||
void onRender(bool full) override;
|
||||
|
||||
protected:
|
||||
bool shouldDrawNode(meshtastic_NodeInfoLite *node) override;
|
||||
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||
|
||||
NodeNum lastFrom = 0; // Sender of most recent favorited (non-local) position packet
|
||||
float lastLat = 0.0;
|
||||
float lastLng = 0.0;
|
||||
float lastHopsAway = 0;
|
||||
|
||||
float ourLastLat = 0.0; // Info about most recent local position
|
||||
float ourLastLng = 0.0;
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
#endif
|
||||
@@ -69,10 +69,9 @@ void InkHUD::HeardApplet::populateFromNodeDB()
|
||||
}
|
||||
|
||||
// Sort the collection by age
|
||||
std::sort(ordered.begin(), ordered.end(),
|
||||
[](const meshtastic_NodeInfoLite *top, const meshtastic_NodeInfoLite *bottom) -> bool {
|
||||
return (top->last_heard > bottom->last_heard);
|
||||
});
|
||||
std::sort(ordered.begin(), ordered.end(), [](meshtastic_NodeInfoLite *top, meshtastic_NodeInfoLite *bottom) -> bool {
|
||||
return (top->last_heard > bottom->last_heard);
|
||||
});
|
||||
|
||||
// Keep the most recent entries only
|
||||
// Just enough to fill the screen
|
||||
|
||||
@@ -69,7 +69,7 @@ void InkHUD::ThreadedMessageApplet::onRender(bool full)
|
||||
while (msgB >= (0 - fontSmall.lineHeight()) && i < store->messages.size()) {
|
||||
|
||||
// Grab data for message
|
||||
const MessageStore::Message &m = store->messages.at(i);
|
||||
MessageStore::Message &m = store->messages.at(i);
|
||||
bool outgoing = (m.sender == 0) || (m.sender == myNodeInfo.my_node_num); // Own NodeNum if canned message
|
||||
std::string bodyText = parse(m.text); // Parse any non-ascii chars in the message
|
||||
|
||||
|
||||
@@ -59,16 +59,10 @@ void InkHUD::Events::onButtonShort()
|
||||
if (consumer) {
|
||||
consumer->onButtonShortPress();
|
||||
} else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module
|
||||
Applet *userConsumer = inkhud->getActiveApplet();
|
||||
|
||||
if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::BUTTON_SHORT))
|
||||
userConsumer->onButtonShortPress();
|
||||
else {
|
||||
if (!settings->joystick.enabled)
|
||||
inkhud->nextApplet();
|
||||
else
|
||||
inkhud->openMenu();
|
||||
}
|
||||
if (!settings->joystick.enabled)
|
||||
inkhud->nextApplet();
|
||||
else
|
||||
inkhud->openMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,14 +84,8 @@ void InkHUD::Events::onButtonLong()
|
||||
// If no system applet is handling input, default behavior instead is to open the menu
|
||||
if (consumer)
|
||||
consumer->onButtonLongPress();
|
||||
else {
|
||||
Applet *userConsumer = inkhud->getActiveApplet();
|
||||
|
||||
if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::BUTTON_LONG))
|
||||
userConsumer->onButtonLongPress();
|
||||
else
|
||||
inkhud->openMenu();
|
||||
}
|
||||
else
|
||||
inkhud->openMenu();
|
||||
}
|
||||
|
||||
void InkHUD::Events::onExitShort()
|
||||
@@ -122,14 +110,8 @@ void InkHUD::Events::onExitShort()
|
||||
// If no system applet is handling input, default behavior instead is change tiles
|
||||
if (consumer)
|
||||
consumer->onExitShort();
|
||||
else if (!dismissedExt) { // Don't change tile if this button press silenced the external notification module
|
||||
Applet *userConsumer = inkhud->getActiveApplet();
|
||||
|
||||
if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::EXIT_SHORT))
|
||||
userConsumer->onExitShort();
|
||||
else
|
||||
inkhud->nextTile();
|
||||
}
|
||||
else if (!dismissedExt) // Don't change tile if this button press silenced the external notification module
|
||||
inkhud->nextTile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,13 +133,6 @@ void InkHUD::Events::onExitLong()
|
||||
|
||||
if (consumer)
|
||||
consumer->onExitLong();
|
||||
else {
|
||||
Applet *userConsumer = inkhud->getActiveApplet();
|
||||
|
||||
if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::EXIT_LONG))
|
||||
userConsumer->onExitLong();
|
||||
// Nothing uses exit long yet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,12 +157,6 @@ void InkHUD::Events::onNavUp()
|
||||
|
||||
if (consumer)
|
||||
consumer->onNavUp();
|
||||
else if (!dismissedExt) {
|
||||
Applet *userConsumer = inkhud->getActiveApplet();
|
||||
|
||||
if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_UP))
|
||||
userConsumer->onNavUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,12 +181,6 @@ void InkHUD::Events::onNavDown()
|
||||
|
||||
if (consumer)
|
||||
consumer->onNavDown();
|
||||
else if (!dismissedExt) {
|
||||
Applet *userConsumer = inkhud->getActiveApplet();
|
||||
|
||||
if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_DOWN))
|
||||
userConsumer->onNavDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,14 +206,8 @@ void InkHUD::Events::onNavLeft()
|
||||
// If no system applet is handling input, default behavior instead is to cycle applets
|
||||
if (consumer)
|
||||
consumer->onNavLeft();
|
||||
else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module
|
||||
Applet *userConsumer = inkhud->getActiveApplet();
|
||||
|
||||
if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_LEFT))
|
||||
userConsumer->onNavLeft();
|
||||
else
|
||||
inkhud->prevApplet();
|
||||
}
|
||||
else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module
|
||||
inkhud->prevApplet();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,14 +233,8 @@ void InkHUD::Events::onNavRight()
|
||||
// If no system applet is handling input, default behavior instead is to cycle applets
|
||||
if (consumer)
|
||||
consumer->onNavRight();
|
||||
else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module
|
||||
Applet *userConsumer = inkhud->getActiveApplet();
|
||||
|
||||
if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_RIGHT))
|
||||
userConsumer->onNavRight();
|
||||
else
|
||||
inkhud->nextApplet();
|
||||
}
|
||||
else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module
|
||||
inkhud->nextApplet();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -210,12 +210,6 @@ void InkHUD::InkHUD::prevApplet()
|
||||
windowManager->prevApplet();
|
||||
}
|
||||
|
||||
// Returns the currently active applet
|
||||
InkHUD::Applet *InkHUD::InkHUD::getActiveApplet()
|
||||
{
|
||||
return windowManager->getActiveApplet();
|
||||
}
|
||||
|
||||
// Show the menu (on the the focused tile)
|
||||
// The applet previously displayed there will be restored once the menu closes
|
||||
void InkHUD::InkHUD::openMenu()
|
||||
|
||||
@@ -74,7 +74,6 @@ class InkHUD
|
||||
|
||||
void nextApplet();
|
||||
void prevApplet();
|
||||
NicheGraphics::InkHUD::Applet *getActiveApplet();
|
||||
void openMenu();
|
||||
void openAlignStick();
|
||||
void openKeyboard();
|
||||
|
||||
@@ -12,7 +12,7 @@ using namespace NicheGraphics;
|
||||
constexpr uint8_t MAX_MESSAGES_SAVED = 10;
|
||||
constexpr uint32_t MAX_MESSAGE_SIZE = 250;
|
||||
|
||||
InkHUD::MessageStore::MessageStore(const std::string &label)
|
||||
InkHUD::MessageStore::MessageStore(std::string label)
|
||||
{
|
||||
filename = "";
|
||||
filename += "/NicheGraphics";
|
||||
@@ -50,13 +50,12 @@ void InkHUD::MessageStore::saveToFlash()
|
||||
// For each message
|
||||
for (uint8_t i = 0; i < messages.size() && i < MAX_MESSAGES_SAVED; i++) {
|
||||
Message &m = messages.at(i);
|
||||
f.write(reinterpret_cast<const uint8_t *>(&m.timestamp), sizeof(m.timestamp)); // Write timestamp. 4 bytes
|
||||
f.write(reinterpret_cast<const uint8_t *>(&m.sender), sizeof(m.sender)); // Write sender NodeId. 4 Bytes
|
||||
f.write(reinterpret_cast<const uint8_t *>(&m.channelIndex), sizeof(m.channelIndex)); // Write channel index. 1 Byte
|
||||
f.write(reinterpret_cast<const uint8_t *>(m.text.c_str()), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text
|
||||
f.write('\0'); // Append null term
|
||||
LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast<uint32_t>(i), min(MAX_MESSAGE_SIZE, m.text.size()),
|
||||
m.text.c_str());
|
||||
f.write((uint8_t *)&m.timestamp, sizeof(m.timestamp)); // Write timestamp. 4 bytes
|
||||
f.write((uint8_t *)&m.sender, sizeof(m.sender)); // Write sender NodeId. 4 Bytes
|
||||
f.write((uint8_t *)&m.channelIndex, sizeof(m.channelIndex)); // Write channel index. 1 Byte
|
||||
f.write((uint8_t *)m.text.c_str(), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text. Variable length
|
||||
f.write('\0'); // Append null term
|
||||
LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", (uint32_t)i, min(MAX_MESSAGE_SIZE, m.text.size()), m.text.c_str());
|
||||
}
|
||||
|
||||
// Release firmware's SPI lock, because SafeFile::close needs it
|
||||
@@ -112,17 +111,17 @@ void InkHUD::MessageStore::loadFromFlash()
|
||||
|
||||
// First byte: how many messages are in the flash store
|
||||
uint8_t flashMessageCount = 0;
|
||||
f.readBytes(reinterpret_cast<char *>(&flashMessageCount), 1);
|
||||
LOG_DEBUG("Messages available: %u", static_cast<uint32_t>(flashMessageCount));
|
||||
f.readBytes((char *)&flashMessageCount, 1);
|
||||
LOG_DEBUG("Messages available: %u", (uint32_t)flashMessageCount);
|
||||
|
||||
// For each message
|
||||
for (uint8_t i = 0; i < flashMessageCount && i < MAX_MESSAGES_SAVED; i++) {
|
||||
Message m;
|
||||
|
||||
// Read meta data (fixed width)
|
||||
f.readBytes(reinterpret_cast<char *>(&m.timestamp), sizeof(m.timestamp));
|
||||
f.readBytes(reinterpret_cast<char *>(&m.sender), sizeof(m.sender));
|
||||
f.readBytes(reinterpret_cast<char *>(&m.channelIndex), sizeof(m.channelIndex));
|
||||
f.readBytes((char *)&m.timestamp, sizeof(m.timestamp));
|
||||
f.readBytes((char *)&m.sender, sizeof(m.sender));
|
||||
f.readBytes((char *)&m.channelIndex, sizeof(m.channelIndex));
|
||||
|
||||
// Read characters until we find a null term
|
||||
char c;
|
||||
@@ -137,8 +136,7 @@ void InkHUD::MessageStore::loadFromFlash()
|
||||
// Store in RAM
|
||||
messages.push_back(m);
|
||||
|
||||
LOG_DEBUG("#%u, timestamp=%u, sender(num)=%u, text=\"%s\"", static_cast<uint32_t>(i), m.timestamp, m.sender,
|
||||
m.text.c_str());
|
||||
LOG_DEBUG("#%u, timestamp=%u, sender(num)=%u, text=\"%s\"", (uint32_t)i, m.timestamp, m.sender, m.text.c_str());
|
||||
}
|
||||
|
||||
f.close();
|
||||
|
||||
@@ -31,7 +31,7 @@ class MessageStore
|
||||
};
|
||||
|
||||
MessageStore() = delete;
|
||||
explicit MessageStore(const std::string &label); // Label determines filename in flash
|
||||
explicit MessageStore(std::string label); // Label determines filename in flash
|
||||
|
||||
void saveToFlash();
|
||||
void loadFromFlash();
|
||||
|
||||
@@ -8,5 +8,5 @@ build_flags =
|
||||
-D MESHTASTIC_EXCLUDE_INPUTBROKER ; Suppress default input handling
|
||||
-D HAS_BUTTON=0 ; Suppress default ButtonThread
|
||||
lib_deps =
|
||||
# renovate: datasource=github-tags depName=GFX_Root packageName=ZinggJM/GFX_Root
|
||||
https://github.com/ZinggJM/GFX_Root/archive/3195764e352a0d2567c8d277ac408ca7293a99b0.zip ; Used by InkHUD as a "slimmer" version of AdafruitGFX
|
||||
# TODO renovate
|
||||
https://github.com/ZinggJM/GFX_Root#2.0.0 ; Used by InkHUD as a "slimmer" version of AdafruitGFX
|
||||
|
||||
@@ -269,42 +269,42 @@ void InkHUD::Renderer::clearTile(Tile *t)
|
||||
// Rotate the tile dimensions
|
||||
int16_t left = 0;
|
||||
int16_t top = 0;
|
||||
uint16_t tileW = 0;
|
||||
uint16_t tileH = 0;
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
switch (settings->rotation) {
|
||||
case 0:
|
||||
left = t->getLeft();
|
||||
top = t->getTop();
|
||||
tileW = t->getWidth();
|
||||
tileH = t->getHeight();
|
||||
width = t->getWidth();
|
||||
height = t->getHeight();
|
||||
break;
|
||||
case 1:
|
||||
left = driver->width - (t->getTop() + t->getHeight());
|
||||
top = t->getLeft();
|
||||
tileW = t->getHeight();
|
||||
tileH = t->getWidth();
|
||||
width = t->getHeight();
|
||||
height = t->getWidth();
|
||||
break;
|
||||
case 2:
|
||||
left = driver->width - (t->getLeft() + t->getWidth());
|
||||
top = driver->height - (t->getTop() + t->getHeight());
|
||||
tileW = t->getWidth();
|
||||
tileH = t->getHeight();
|
||||
width = t->getWidth();
|
||||
height = t->getHeight();
|
||||
break;
|
||||
case 3:
|
||||
left = t->getTop();
|
||||
top = driver->height - (t->getLeft() + t->getWidth());
|
||||
tileW = t->getHeight();
|
||||
tileH = t->getWidth();
|
||||
width = t->getHeight();
|
||||
height = t->getWidth();
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate the bounds to clear
|
||||
uint16_t xStart = (left < 0) ? 0 : left;
|
||||
uint16_t yStart = (top < 0) ? 0 : top;
|
||||
if (xStart >= driver->width || yStart >= driver->height || left + tileW < 0 || top + tileH < 0)
|
||||
if (xStart >= driver->width || yStart >= driver->height || left + width < 0 || top + height < 0)
|
||||
return; // the box is completely off the screen
|
||||
uint16_t xEnd = left + tileW;
|
||||
uint16_t yEnd = top + tileH;
|
||||
uint16_t xEnd = left + width;
|
||||
uint16_t yEnd = top + height;
|
||||
if (xEnd > driver->width)
|
||||
xEnd = driver->width;
|
||||
if (yEnd > driver->height)
|
||||
|
||||
@@ -273,12 +273,6 @@ void InkHUD::WindowManager::prevApplet()
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST); // bringToForeground already requested, but we're manually forcing FAST
|
||||
}
|
||||
|
||||
// Returns active applet
|
||||
NicheGraphics::InkHUD::Applet *InkHUD::WindowManager::getActiveApplet()
|
||||
{
|
||||
return userTiles.at(settings->userTiles.focused)->getAssignedApplet();
|
||||
}
|
||||
|
||||
// Rotate the display image by 90 degrees
|
||||
void InkHUD::WindowManager::rotate()
|
||||
{
|
||||
@@ -402,7 +396,7 @@ void InkHUD::WindowManager::autoshow()
|
||||
{
|
||||
// Don't perform autoshow if a system applet has exclusive use of the display right now
|
||||
// Note: lockRequests prevents autoshow attempting to hide menuApplet
|
||||
for (const SystemApplet *sa : inkhud->systemApplets) {
|
||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
||||
if (sa->lockRendering || sa->lockRequests)
|
||||
return;
|
||||
}
|
||||
@@ -516,10 +510,10 @@ void InkHUD::WindowManager::placeSystemTiles()
|
||||
const uint16_t batteryIconWidth = batteryIconHeight * 1.8;
|
||||
inkhud->getSystemApplet("BatteryIcon")
|
||||
->getTile()
|
||||
->setRegion(inkhud->width() - batteryIconWidth - 1, // x
|
||||
1, // y
|
||||
batteryIconWidth + 1, // width
|
||||
batteryIconHeight + 2); // height
|
||||
->setRegion(inkhud->width() - batteryIconWidth, // x
|
||||
2, // y
|
||||
batteryIconWidth, // width
|
||||
batteryIconHeight); // height
|
||||
|
||||
// Note: the tiles of placeholder and menu applets are manipulated specially
|
||||
// - menuApplet borrows user tiles
|
||||
|
||||
@@ -29,7 +29,6 @@ class WindowManager
|
||||
|
||||
void nextTile();
|
||||
void prevTile();
|
||||
Applet *getActiveApplet();
|
||||
void openMenu();
|
||||
void openAlignStick();
|
||||
void openKeyboard();
|
||||
|
||||
@@ -146,7 +146,7 @@ void CannedMessageStore::handleGet(meshtastic_AdminMessage *response)
|
||||
std::string merged;
|
||||
if (!messages.empty()) { // Don't run if no messages: error on pop_back with size=0
|
||||
merged.reserve(201);
|
||||
for (const std::string &s : messages) {
|
||||
for (std::string &s : messages) {
|
||||
merged += s;
|
||||
merged += '|';
|
||||
}
|
||||
|
||||
@@ -106,8 +106,8 @@ static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{},
|
||||
{}};
|
||||
|
||||
HackadayCommunicatorKeyboard::HackadayCommunicatorKeyboard()
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX),
|
||||
next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0)
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
|
||||
last_tap(0L), char_idx(0), tap_interval(0)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
@@ -147,6 +147,7 @@ void HackadayCommunicatorKeyboard::pressed(uint8_t key)
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
@@ -186,8 +187,8 @@ void HackadayCommunicatorKeyboard::released()
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = UINT8_MAX;
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ class HackadayCommunicatorKeyboard : public TCA8418KeyboardBase
|
||||
private:
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||
uint8_t last_key;
|
||||
uint8_t next_key;
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
|
||||
@@ -91,7 +91,7 @@ MPR121Keyboard::MPR121Keyboard() : m_wire(nullptr), m_addr(0), readCallback(null
|
||||
{
|
||||
// LOG_DEBUG("MPR121 @ %02x", m_addr);
|
||||
state = Init;
|
||||
last_key = UINT8_MAX;
|
||||
last_key = -1;
|
||||
last_tap = 0L;
|
||||
char_idx = 0;
|
||||
queue = "";
|
||||
@@ -359,8 +359,8 @@ void MPR121Keyboard::released()
|
||||
return;
|
||||
}
|
||||
// would clear longpress callback... later.
|
||||
if (last_key >= _NUM_KEYS) { // reset to idle if last_key out of bounds
|
||||
last_key = UINT8_MAX;
|
||||
if (last_key < 0 || last_key > _NUM_KEYS) { // reset to idle if last_key out of bounds
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class MPR121Keyboard
|
||||
|
||||
MPR121States state;
|
||||
|
||||
uint8_t last_key;
|
||||
int8_t last_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@ static unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = {
|
||||
};
|
||||
|
||||
TCA8418Keyboard::TCA8418Keyboard()
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(UINT8_MAX), next_key(UINT8_MAX), last_tap(0L), char_idx(0),
|
||||
tap_interval(0), should_backspace(false)
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(-1), next_key(-1), last_tap(0L), char_idx(0), tap_interval(0),
|
||||
should_backspace(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ void TCA8418Keyboard::pressed(uint8_t key)
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
|
||||
@@ -71,7 +72,7 @@ void TCA8418Keyboard::pressed(uint8_t key)
|
||||
}
|
||||
|
||||
// Compute key index based on dynamic row/column
|
||||
next_key = (uint8_t)(row * _TCA8418_COLS + col);
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
|
||||
// LOG_DEBUG("TCA8418: Key %u -> Next Key %u", key, next_key);
|
||||
|
||||
@@ -105,8 +106,8 @@ void TCA8418Keyboard::released()
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key >= _TCA8418_NUM_KEYS) { // reset to idle if last_key out of bounds
|
||||
last_key = UINT8_MAX;
|
||||
if (last_key < 0 || last_key > _TCA8418_NUM_KEYS) { // reset to idle if last_key out of bounds
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ class TCA8418Keyboard : public TCA8418KeyboardBase
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
|
||||
uint8_t last_key;
|
||||
uint8_t next_key;
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
|
||||
@@ -62,8 +62,8 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = {
|
||||
};
|
||||
|
||||
TDeckProKeyboard::TDeckProKeyboard()
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX),
|
||||
next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0)
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
|
||||
last_tap(0L), char_idx(0), tap_interval(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ void TDeckProKeyboard::pressed(uint8_t key)
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
|
||||
@@ -141,8 +142,8 @@ void TDeckProKeyboard::released()
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = UINT8_MAX;
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ class TDeckProKeyboard : public TCA8418KeyboardBase
|
||||
private:
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||
uint8_t last_key;
|
||||
uint8_t next_key;
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
|
||||
@@ -65,8 +65,8 @@ static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'},
|
||||
{' ', 0x00, Key::BL_TOGGLE}};
|
||||
|
||||
TLoraPagerKeyboard::TLoraPagerKeyboard()
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX),
|
||||
next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0)
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
|
||||
last_tap(0L), char_idx(0), tap_interval(0)
|
||||
{
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
|
||||
@@ -129,6 +129,7 @@ void TLoraPagerKeyboard::pressed(uint8_t key)
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
|
||||
@@ -169,8 +170,8 @@ void TLoraPagerKeyboard::released()
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = UINT8_MAX;
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase
|
||||
private:
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||
uint8_t last_key;
|
||||
uint8_t next_key;
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
|
||||
+85
-34
@@ -7,13 +7,13 @@
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "PowerMon.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "ReliableRouter.h"
|
||||
#include "airtime.h"
|
||||
#include "buzz.h"
|
||||
#include "power/PowerHAL.h"
|
||||
|
||||
#include "FSCommon.h"
|
||||
#include "Led.h"
|
||||
#include "RTC.h"
|
||||
#include "SPILock.h"
|
||||
#include "Throttle.h"
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <Wire.h>
|
||||
#endif
|
||||
#include "detect/einkScan.h"
|
||||
#include "graphics/RAKled.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#include "mesh/generated/meshtastic/config.pb.h"
|
||||
@@ -241,13 +242,33 @@ const char *getDeviceName()
|
||||
return name;
|
||||
}
|
||||
|
||||
// TODO remove from main.cpp
|
||||
static int32_t ledBlinker()
|
||||
{
|
||||
// Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if
|
||||
// config.device.led_heartbeat_disabled is changed
|
||||
if (config.device.led_heartbeat_disabled)
|
||||
return 1000;
|
||||
|
||||
static bool ledOn;
|
||||
ledOn ^= 1;
|
||||
|
||||
ledBlink.set(ledOn);
|
||||
|
||||
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
||||
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000);
|
||||
}
|
||||
|
||||
uint32_t timeLastPowered = 0;
|
||||
|
||||
static Periodic *ledPeriodic;
|
||||
static OSThread *powerFSMthread;
|
||||
OSThread *ambientLightingThread;
|
||||
static OSThread *ambientLightingThread;
|
||||
|
||||
RadioInterface *rIf = NULL;
|
||||
#ifdef ARCH_PORTDUINO
|
||||
RadioLibHal *RadioLibHAL = NULL;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Some platforms (nrf52) might provide an alterate version that suppresses calling delay from sleep.
|
||||
@@ -278,16 +299,21 @@ void earlyInitVariant() {}
|
||||
// blink user led in 3 flashes sequence to indicate what is happening
|
||||
void waitUntilPowerLevelSafe()
|
||||
{
|
||||
|
||||
#ifdef LED_PIN
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
#endif
|
||||
|
||||
while (powerHAL_isPowerLevelSafe() == false) {
|
||||
|
||||
#ifdef LED_POWER
|
||||
#ifdef LED_PIN
|
||||
|
||||
// 3x: blink for 300 ms, pause for 300 ms
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
digitalWrite(LED_POWER, LED_STATE_ON);
|
||||
digitalWrite(LED_PIN, LED_STATE_ON);
|
||||
delay(300);
|
||||
digitalWrite(LED_POWER, LED_STATE_OFF);
|
||||
digitalWrite(LED_PIN, LED_STATE_OFF);
|
||||
delay(300);
|
||||
}
|
||||
#endif
|
||||
@@ -311,11 +337,6 @@ void setup()
|
||||
// initialize power HAL layer as early as possible
|
||||
powerHAL_init();
|
||||
|
||||
#ifdef LED_POWER
|
||||
pinMode(LED_POWER, OUTPUT);
|
||||
digitalWrite(LED_POWER, LED_STATE_ON);
|
||||
#endif
|
||||
|
||||
// prevent booting if device is in power failure mode
|
||||
// boot sequence will follow when battery level raises to safe mode
|
||||
waitUntilPowerLevelSafe();
|
||||
@@ -328,6 +349,11 @@ void setup()
|
||||
digitalWrite(PIN_POWER_EN, HIGH);
|
||||
#endif
|
||||
|
||||
#ifdef LED_POWER
|
||||
pinMode(LED_POWER, OUTPUT);
|
||||
digitalWrite(LED_POWER, LED_STATE_ON);
|
||||
#endif
|
||||
|
||||
#ifdef LED_NOTIFICATION
|
||||
pinMode(LED_NOTIFICATION, OUTPUT);
|
||||
digitalWrite(LED_NOTIFICATION, HIGH ^ LED_STATE_ON);
|
||||
@@ -340,7 +366,11 @@ void setup()
|
||||
|
||||
#ifdef BLE_LED
|
||||
pinMode(BLE_LED, OUTPUT);
|
||||
digitalWrite(BLE_LED, LED_STATE_OFF);
|
||||
#ifdef BLE_LED_INVERTED
|
||||
digitalWrite(BLE_LED, HIGH);
|
||||
#else
|
||||
digitalWrite(BLE_LED, LOW);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
concurrency::hasBeenSetup = true;
|
||||
@@ -459,6 +489,14 @@ void setup()
|
||||
|
||||
OSThread::setup();
|
||||
|
||||
// TODO make this ifdef based on defined pins and move from main.cpp
|
||||
#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
|
||||
// The ThinkNodes have their own blink logic
|
||||
// ledPeriodic = new Periodic("Blink", elecrowLedBlinker);
|
||||
#else
|
||||
ledPeriodic = new Periodic("Blink", ledBlinker);
|
||||
#endif
|
||||
|
||||
fsInit();
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
@@ -677,12 +715,20 @@ void setup()
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_SDCARD
|
||||
setupSDCard();
|
||||
#endif
|
||||
|
||||
// LED init
|
||||
|
||||
#ifdef LED_PIN
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, LED_STATE_ON); // turn on for now
|
||||
#endif
|
||||
|
||||
// Hello
|
||||
printInfo();
|
||||
#ifdef BUILD_EPOCH
|
||||
@@ -721,18 +767,23 @@ void setup()
|
||||
playStartMelody();
|
||||
|
||||
#if HAS_SCREEN
|
||||
// fixed screen override?
|
||||
#if defined(USE_SH1107)
|
||||
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128
|
||||
screen_geometry = GEOMETRY_128_128;
|
||||
#elif defined(USE_SH1107_128_64)
|
||||
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64
|
||||
#else
|
||||
// fixed screen override?
|
||||
if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO)
|
||||
screen_model = config.display.oled;
|
||||
|
||||
#if defined(USE_SH1107_128_64)
|
||||
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64
|
||||
screen_geometry = GEOMETRY_128_64;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// if we have one of the fixed overrides in the settings, adjust display type accordingly.
|
||||
if (screen_model == meshtastic_Config_DisplayConfig_OledType_OLED_SH1107) {
|
||||
screen_geometry = GEOMETRY_128_64;
|
||||
} else if (screen_model == meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128) {
|
||||
screen_geometry = GEOMETRY_128_128;
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
#if !defined(ARCH_STM32WL)
|
||||
if (acc_info.type != ScanI2C::DeviceType::NONE) {
|
||||
@@ -790,7 +841,7 @@ void setup()
|
||||
SPI.begin();
|
||||
#endif
|
||||
#else
|
||||
// ESP32
|
||||
// ESP32
|
||||
#if defined(HW_SPI1_DEVICE)
|
||||
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
@@ -923,6 +974,12 @@ void setup()
|
||||
setupNicheGraphics();
|
||||
#endif
|
||||
|
||||
#ifdef LED_PIN
|
||||
// Turn LED off after boot, if heartbeat by config
|
||||
if (config.device.led_heartbeat_disabled)
|
||||
digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON);
|
||||
#endif
|
||||
|
||||
// Do this after service.init (because that clears error_code)
|
||||
#ifdef HAS_PMU
|
||||
if (!pmu_found)
|
||||
@@ -948,7 +1005,7 @@ void setup()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
auto rIf = initLoRa();
|
||||
initLoRa();
|
||||
|
||||
lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation)
|
||||
|
||||
@@ -997,12 +1054,12 @@ void setup()
|
||||
if (!rIf)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_NO_RADIO);
|
||||
else {
|
||||
router->addInterface(rIf);
|
||||
|
||||
// Log bit rate to debug output
|
||||
LOG_DEBUG("LoRA bitrate = %f bytes / sec", (float(meshtastic_Constants_DATA_PAYLOAD_LEN) /
|
||||
(float(rIf->getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN)))) *
|
||||
1000);
|
||||
|
||||
router->addInterface(std::move(rIf));
|
||||
}
|
||||
|
||||
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
|
||||
@@ -1119,14 +1176,6 @@ void loop()
|
||||
#endif
|
||||
power->powerCommandsCheck();
|
||||
|
||||
if (RadioLibInterface::instance != nullptr) {
|
||||
static uint32_t lastRadioMissedIrqPoll;
|
||||
if (!Throttle::isWithinTimespanMs(lastRadioMissedIrqPoll, 1000)) {
|
||||
lastRadioMissedIrqPoll = millis();
|
||||
RadioLibInterface::instance->pollMissedIrqs();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_STACK
|
||||
static uint32_t lastPrint = 0;
|
||||
if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) {
|
||||
@@ -1146,7 +1195,10 @@ void loop()
|
||||
}
|
||||
if (portduino_status.LoRa_in_error && rebootAtMsec == 0) {
|
||||
LOG_ERROR("LoRa in error detected, attempting to recover");
|
||||
router->addInterface(nullptr);
|
||||
if (rIf != nullptr) {
|
||||
delete rIf;
|
||||
rIf = nullptr;
|
||||
}
|
||||
if (portduino_config.lora_spi_dev == "ch341") {
|
||||
if (ch341Hal != nullptr) {
|
||||
delete ch341Hal;
|
||||
@@ -1162,9 +1214,8 @@ void loop()
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
auto rIf = initLoRa();
|
||||
if (rIf) {
|
||||
router->addInterface(std::move(rIf));
|
||||
if (initLoRa()) {
|
||||
router->addInterface(rIf);
|
||||
portduino_status.LoRa_in_error = false;
|
||||
} else {
|
||||
LOG_WARN("Reconfigure failed, rebooting");
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "CryptoEngine.h"
|
||||
// #include "NodeDB.h"
|
||||
#include "architecture.h"
|
||||
#include <memory>
|
||||
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
#include "NodeDB.h"
|
||||
@@ -170,9 +169,10 @@ void CryptoEngine::hash(uint8_t *bytes, size_t numBytes)
|
||||
|
||||
void CryptoEngine::aesSetKey(const uint8_t *key_bytes, size_t key_len)
|
||||
{
|
||||
delete aes;
|
||||
aes = nullptr;
|
||||
if (key_len != 0) {
|
||||
aes = std::unique_ptr<AESSmall256>(new AESSmall256());
|
||||
aes = new AESSmall256();
|
||||
aes->setKey(key_bytes, key_len);
|
||||
}
|
||||
}
|
||||
@@ -231,11 +231,12 @@ void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes
|
||||
// Generic implementation of AES-CTR encryption.
|
||||
void CryptoEngine::encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes)
|
||||
{
|
||||
std::unique_ptr<CTRCommon> ctr;
|
||||
delete ctr;
|
||||
ctr = nullptr;
|
||||
if (_key.length == 16)
|
||||
ctr = std::unique_ptr<CTRCommon>(new CTR<AES128>());
|
||||
ctr = new CTR<AES128>();
|
||||
else
|
||||
ctr = std::unique_ptr<CTRCommon>(new CTR<AES256>());
|
||||
ctr = new CTR<AES256>();
|
||||
ctr->setKey(_key.bytes, _key.length);
|
||||
static uint8_t scratch[MAX_BLOCKSIZE];
|
||||
memcpy(scratch, bytes, numBytes);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "configuration.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include <Arduino.h>
|
||||
#include <memory>
|
||||
|
||||
extern concurrency::Lock *cryptLock;
|
||||
|
||||
@@ -49,7 +48,7 @@ class CryptoEngine
|
||||
virtual void aesSetKey(const uint8_t *key, size_t key_len);
|
||||
|
||||
virtual void aesEncrypt(uint8_t *in, uint8_t *out);
|
||||
std::unique_ptr<AESSmall256> aes = nullptr;
|
||||
AESSmall256 *aes = NULL;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -78,6 +77,7 @@ class CryptoEngine
|
||||
/** Our per packet nonce */
|
||||
uint8_t nonce[16] = {0};
|
||||
CryptoKey key = {};
|
||||
CTRCommon *ctr = NULL;
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
uint8_t shared_key[32] = {0};
|
||||
uint8_t private_key[32] = {0};
|
||||
|
||||
@@ -263,7 +263,6 @@ template <typename T> void LR11x0Interface<T>::startReceive()
|
||||
|
||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
||||
enableInterrupt(isrRxLevel0);
|
||||
checkRxDoneIrqFlag();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+6
-18
@@ -814,28 +814,26 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.has_store_forward = true;
|
||||
moduleConfig.has_telemetry = true;
|
||||
moduleConfig.has_external_notification = true;
|
||||
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
#endif
|
||||
#if defined(PIN_BUZZER)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.output_buzzer = PIN_BUZZER;
|
||||
moduleConfig.external_notification.use_pwm = true;
|
||||
moduleConfig.external_notification.alert_message_buzzer = true;
|
||||
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
||||
#endif
|
||||
#if defined(PIN_VIBRATION)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.output_vibra = PIN_VIBRATION;
|
||||
moduleConfig.external_notification.alert_message_vibra = true;
|
||||
moduleConfig.external_notification.output_ms = 500;
|
||||
moduleConfig.external_notification.nag_timeout = 2;
|
||||
#endif
|
||||
#if defined(LED_NOTIFICATION)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.output = LED_NOTIFICATION;
|
||||
moduleConfig.external_notification.active = LED_STATE_ON;
|
||||
moduleConfig.external_notification.alert_message = true;
|
||||
moduleConfig.external_notification.output_ms = 1000;
|
||||
#endif
|
||||
#if defined(PIN_VIBRATION)
|
||||
moduleConfig.external_notification.nag_timeout = 2;
|
||||
#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION)
|
||||
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
||||
#endif
|
||||
|
||||
@@ -1410,15 +1408,6 @@ void NodeDB::loadFromDisk()
|
||||
if (portduino_config.has_configDisplayMode) {
|
||||
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode;
|
||||
}
|
||||
if (portduino_config.has_statusMessage) {
|
||||
moduleConfig.has_statusmessage = true;
|
||||
strncpy(moduleConfig.statusmessage.node_status, portduino_config.statusMessage.c_str(),
|
||||
sizeof(moduleConfig.statusmessage.node_status));
|
||||
moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0';
|
||||
}
|
||||
if (portduino_config.enable_UDP) {
|
||||
config.network.enabled_protocols = meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
@@ -1559,7 +1548,6 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
||||
moduleConfig.has_ambient_lighting = true;
|
||||
moduleConfig.has_audio = true;
|
||||
moduleConfig.has_paxcounter = true;
|
||||
moduleConfig.has_statusmessage = true;
|
||||
|
||||
success &=
|
||||
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
|
||||
@@ -1774,7 +1762,7 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
info->has_device_metrics = false;
|
||||
info->has_position = false;
|
||||
info->user.public_key.size = 0;
|
||||
memset(info->user.public_key.bytes, 0, sizeof(info->user.public_key.bytes));
|
||||
info->user.public_key.bytes[0] = 0;
|
||||
} else {
|
||||
/* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
|
||||
* public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
|
||||
|
||||
@@ -439,7 +439,7 @@ void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, cons
|
||||
}
|
||||
|
||||
// Getters and setters for hop limit fields packed in hop_limit
|
||||
inline uint8_t PacketHistory::getHighestHopLimit(const PacketRecord &r)
|
||||
inline uint8_t PacketHistory::getHighestHopLimit(PacketRecord &r)
|
||||
{
|
||||
return r.hop_limit & HOP_LIMIT_HIGHEST_MASK;
|
||||
}
|
||||
@@ -449,7 +449,7 @@ inline void PacketHistory::setHighestHopLimit(PacketRecord &r, uint8_t hopLimit)
|
||||
r.hop_limit = (r.hop_limit & ~HOP_LIMIT_HIGHEST_MASK) | (hopLimit & HOP_LIMIT_HIGHEST_MASK);
|
||||
}
|
||||
|
||||
inline uint8_t PacketHistory::getOurTxHopLimit(const PacketRecord &r)
|
||||
inline uint8_t PacketHistory::getOurTxHopLimit(PacketRecord &r)
|
||||
{
|
||||
return (r.hop_limit & HOP_LIMIT_OUR_TX_MASK) >> HOP_LIMIT_OUR_TX_SHIFT;
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ class PacketHistory
|
||||
* @return true if node was indeed a relayer, false if not */
|
||||
bool wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole = nullptr);
|
||||
|
||||
uint8_t getHighestHopLimit(const PacketRecord &r);
|
||||
uint8_t getHighestHopLimit(PacketRecord &r);
|
||||
void setHighestHopLimit(PacketRecord &r, uint8_t hopLimit);
|
||||
uint8_t getOurTxHopLimit(const PacketRecord &r);
|
||||
uint8_t getOurTxHopLimit(PacketRecord &r);
|
||||
void setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit);
|
||||
|
||||
PacketHistory(const PacketHistory &); // non construction-copyable
|
||||
|
||||
@@ -82,6 +82,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
||||
// it would be better to update even if the message was destined to others.
|
||||
|
||||
auto &p = mp.decoded;
|
||||
LOG_INFO("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d", name, mp.from, mp.id, p.portnum, p.payload.size);
|
||||
|
||||
T scratch;
|
||||
T *decoded = NULL;
|
||||
@@ -89,8 +90,6 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) {
|
||||
decoded = &scratch;
|
||||
LOG_INFO("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d", name, mp.from, mp.id, p.portnum,
|
||||
p.payload.size);
|
||||
} else {
|
||||
LOG_ERROR("Error decoding proto module!");
|
||||
// if we can't decode it, nobody can process it!
|
||||
|
||||
@@ -301,7 +301,6 @@ void RF95Interface::startReceive()
|
||||
|
||||
// Must be done AFTER, starting receive, because startReceive clears (possibly stale) interrupt pending register bits
|
||||
enableInterrupt(isrRxLevel0);
|
||||
checkRxDoneIrqFlag();
|
||||
}
|
||||
|
||||
bool RF95Interface::isChannelActive()
|
||||
|
||||
+63
-55
@@ -227,19 +227,23 @@ static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1];
|
||||
// Global LoRa radio type
|
||||
LoRaRadioType radioType = NO_RADIO;
|
||||
|
||||
extern RadioInterface *rIf;
|
||||
extern RadioLibHal *RadioLibHAL;
|
||||
#if defined(HW_SPI1_DEVICE) && defined(ARCH_ESP32)
|
||||
extern SPIClass SPI1;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<RadioInterface> initLoRa()
|
||||
bool initLoRa()
|
||||
{
|
||||
std::unique_ptr<RadioInterface> rIf = nullptr;
|
||||
if (rIf != nullptr) {
|
||||
delete rIf;
|
||||
rIf = nullptr;
|
||||
}
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
SPISettings loraSpiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
|
||||
SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
|
||||
#else
|
||||
SPISettings loraSpiSettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
@@ -248,26 +252,26 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
RADIOLIB_PIN_TYPE busy) {
|
||||
switch (portduino_config.lora_module) {
|
||||
case use_rf95:
|
||||
return std::unique_ptr<RadioInterface>(new RF95Interface(hal, cs, irq, rst, busy));
|
||||
return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy);
|
||||
case use_sx1262:
|
||||
return std::unique_ptr<RadioInterface>(new SX1262Interface(hal, cs, irq, rst, busy));
|
||||
return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy);
|
||||
case use_sx1268:
|
||||
return std::unique_ptr<RadioInterface>(new SX1268Interface(hal, cs, irq, rst, busy));
|
||||
return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy);
|
||||
case use_sx1280:
|
||||
return std::unique_ptr<RadioInterface>(new SX1280Interface(hal, cs, irq, rst, busy));
|
||||
return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy);
|
||||
case use_lr1110:
|
||||
return std::unique_ptr<RadioInterface>(new LR1110Interface(hal, cs, irq, rst, busy));
|
||||
return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy);
|
||||
case use_lr1120:
|
||||
return std::unique_ptr<RadioInterface>(new LR1120Interface(hal, cs, irq, rst, busy));
|
||||
return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy);
|
||||
case use_lr1121:
|
||||
return std::unique_ptr<RadioInterface>(new LR1121Interface(hal, cs, irq, rst, busy));
|
||||
return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy);
|
||||
case use_llcc68:
|
||||
return std::unique_ptr<RadioInterface>(new LLCC68Interface(hal, cs, irq, rst, busy));
|
||||
return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy);
|
||||
case use_simradio:
|
||||
return std::unique_ptr<RadioInterface>(new SimRadio);
|
||||
return (RadioInterface *)new SimRadio;
|
||||
default:
|
||||
assert(0); // shouldn't happen
|
||||
return std::unique_ptr<RadioInterface>(nullptr);
|
||||
return (RadioInterface *)nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -280,7 +284,7 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
delete RadioLibHAL;
|
||||
RadioLibHAL = nullptr;
|
||||
}
|
||||
RadioLibHAL = new LockingArduinoHal(SPI, loraSpiSettings);
|
||||
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
}
|
||||
rIf =
|
||||
loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin,
|
||||
@@ -288,28 +292,27 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str());
|
||||
rIf = nullptr;
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str());
|
||||
}
|
||||
|
||||
#elif defined(HW_SPI1_DEVICE)
|
||||
LockingArduinoHal *loraHal = new LockingArduinoHal(SPI1, loraSpiSettings);
|
||||
RadioLibHAL = loraHal;
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings);
|
||||
#else // HW_SPI1_DEVICE
|
||||
LockingArduinoHal *loraHal = new LockingArduinoHal(SPI, loraSpiSettings);
|
||||
RadioLibHAL = loraHal;
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
#endif
|
||||
|
||||
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
|
||||
#if defined(USE_STM32WLx)
|
||||
if (!rIf) {
|
||||
rIf = std::unique_ptr<STM32WLE5JCInterface>(
|
||||
new STM32WLE5JCInterface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY));
|
||||
rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No STM32WL radio");
|
||||
rIf = nullptr;
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("STM32WL init success");
|
||||
radioType = STM32WLx_RADIO;
|
||||
@@ -319,10 +322,11 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
|
||||
#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = std::unique_ptr<RF95Interface>(new RF95Interface(loraHal, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1));
|
||||
rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No RF95 radio");
|
||||
rIf = nullptr;
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("RF95 init success");
|
||||
radioType = RF95_RADIO;
|
||||
@@ -332,17 +336,17 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
auto sxIf =
|
||||
std::unique_ptr<SX1262Interface>(new SX1262Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY));
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
#endif
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio");
|
||||
rIf = nullptr;
|
||||
delete sxIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success");
|
||||
rIf = std::move(sxIf);
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
@@ -351,25 +355,26 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// try using the specified TCXO voltage
|
||||
auto sxIf =
|
||||
std::unique_ptr<SX1262Interface>(new SX1262Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY));
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = nullptr;
|
||||
delete sxIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = std::move(sxIf);
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instead
|
||||
rIf = std::unique_ptr<SX1262Interface>(new SX1262Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY));
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V");
|
||||
rIf = nullptr;
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success, XTAL, Vref 0.0V");
|
||||
radioType = SX1262_RADIO;
|
||||
@@ -381,24 +386,25 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// try using the specified TCXO voltage
|
||||
auto sxIf =
|
||||
std::unique_ptr<SX1268Interface>(new SX1268Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY));
|
||||
auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = nullptr;
|
||||
delete sxIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = std::move(sxIf);
|
||||
rIf = sxIf;
|
||||
radioType = SX1268_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = std::unique_ptr<SX1268Interface>(new SX1268Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY));
|
||||
rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1268 radio");
|
||||
rIf = nullptr;
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1268 init success");
|
||||
radioType = SX1268_RADIO;
|
||||
@@ -408,10 +414,11 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
|
||||
#if defined(USE_LLCC68)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = std::unique_ptr<LLCC68Interface>(new LLCC68Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY));
|
||||
rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LLCC68 radio");
|
||||
rIf = nullptr;
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LLCC68 init success");
|
||||
radioType = LLCC68_RADIO;
|
||||
@@ -421,11 +428,11 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
|
||||
#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = std::unique_ptr<LR1110Interface>(
|
||||
new LR1110Interface(loraHal, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN));
|
||||
rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LR1110 radio");
|
||||
rIf = nullptr;
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LR1110 init success");
|
||||
radioType = LR1110_RADIO;
|
||||
@@ -435,11 +442,11 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
|
||||
#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1
|
||||
if (!rIf) {
|
||||
rIf = std::unique_ptr<LR1120Interface>(
|
||||
new LR1120Interface(loraHal, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN));
|
||||
rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LR1120 radio");
|
||||
rIf = nullptr;
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LR1120 init success");
|
||||
radioType = LR1120_RADIO;
|
||||
@@ -449,11 +456,11 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
|
||||
#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1
|
||||
if (!rIf) {
|
||||
rIf = std::unique_ptr<LR1121Interface>(
|
||||
new LR1121Interface(loraHal, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN));
|
||||
rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LR1121 radio");
|
||||
rIf = nullptr;
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LR1121 init success");
|
||||
radioType = LR1121_RADIO;
|
||||
@@ -463,10 +470,11 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
|
||||
#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1
|
||||
if (!rIf) {
|
||||
rIf = std::unique_ptr<SX1280Interface>(new SX1280Interface(loraHal, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY));
|
||||
rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1280 radio");
|
||||
rIf = nullptr;
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1280 init success");
|
||||
radioType = SX1280_RADIO;
|
||||
@@ -488,7 +496,7 @@ std::unique_ptr<RadioInterface> initLoRa()
|
||||
rebootAtMsec = millis() + 5000;
|
||||
}
|
||||
}
|
||||
return rIf;
|
||||
return rIf != nullptr;
|
||||
}
|
||||
|
||||
void initRegion()
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "PointerQueue.h"
|
||||
#include "airtime.h"
|
||||
#include "error.h"
|
||||
#include <memory>
|
||||
|
||||
// Forward decl to avoid a direct include of generated config headers / full LoRaConfig definition in this widely-included file.
|
||||
typedef struct _meshtastic_Config_LoRaConfig meshtastic_Config_LoRaConfig;
|
||||
@@ -280,7 +279,7 @@ class RadioInterface
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<RadioInterface> initLoRa();
|
||||
bool initLoRa();
|
||||
|
||||
/// Debug printing for packets
|
||||
void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
|
||||
|
||||
@@ -453,11 +453,8 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
}
|
||||
#endif
|
||||
if (state != RADIOLIB_ERR_NONE) {
|
||||
// Log PacketHeader similar to RadioInterface::printPacket so we can try to match RX errors to other packets in the logs.
|
||||
LOG_ERROR("Ignore received packet due to error=%d (maybe id=0x%08x fr=0x%08x to=0x%08x flags=0x%02x rxSNR=%g rxRSSI=%i "
|
||||
"nextHop=0x%x relay=0x%x)",
|
||||
state, radioBuffer.header.id, radioBuffer.header.from, radioBuffer.header.to, radioBuffer.header.flags,
|
||||
iface->getSNR(), lround(iface->getRSSI()), radioBuffer.header.next_hop, radioBuffer.header.relay_node);
|
||||
LOG_ERROR("Ignore received packet due to error=%d (maybe to=0x%08x, from=0x%08x, flags=0x%02x)", state,
|
||||
radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
|
||||
rxBad++;
|
||||
|
||||
airTime->logAirtime(RX_ALL_LOG, rxMsec);
|
||||
@@ -521,22 +518,6 @@ void RadioLibInterface::startReceive()
|
||||
powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn);
|
||||
}
|
||||
|
||||
void RadioLibInterface::pollMissedIrqs()
|
||||
{
|
||||
// RadioLibInterface::enableInterrupt uses EDGE-TRIGGERED interrupts. Poll as a backup to catch missed edges.
|
||||
if (isReceiving) {
|
||||
checkRxDoneIrqFlag();
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibInterface::checkRxDoneIrqFlag()
|
||||
{
|
||||
if (iface->checkIrq(RADIOLIB_IRQ_RX_DONE)) {
|
||||
LOG_WARN("caught missed RX_DONE");
|
||||
notify(ISR_RX, true);
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibInterface::configHardwareForSend()
|
||||
{
|
||||
powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn);
|
||||
|
||||
@@ -112,11 +112,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
*/
|
||||
virtual void enableInterrupt(void (*)()) = 0;
|
||||
|
||||
/**
|
||||
* Poll as a backup to catch missed edge-triggered interrupts.
|
||||
*/
|
||||
void pollMissedIrqs();
|
||||
|
||||
/**
|
||||
* Debugging counts
|
||||
*/
|
||||
@@ -269,6 +264,4 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
*/
|
||||
|
||||
bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override;
|
||||
|
||||
void checkRxDoneIrqFlag();
|
||||
};
|
||||
|
||||
+2
-3
@@ -8,7 +8,6 @@
|
||||
#include "PointerQueue.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* A mesh aware router that supports multiple interfaces.
|
||||
@@ -21,7 +20,7 @@ class Router : protected concurrency::OSThread, protected PacketHistory
|
||||
PointerQueue<meshtastic_MeshPacket> fromRadioQueue;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<RadioInterface> iface = nullptr;
|
||||
RadioInterface *iface = NULL;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -33,7 +32,7 @@ class Router : protected concurrency::OSThread, protected PacketHistory
|
||||
/**
|
||||
* Currently we only allow one interface, that may change in the future
|
||||
*/
|
||||
void addInterface(std::unique_ptr<RadioInterface> _iface) { iface = std::move(_iface); }
|
||||
void addInterface(RadioInterface *_iface) { iface = _iface; }
|
||||
|
||||
/**
|
||||
* do idle processing
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "PortduinoGlue.h"
|
||||
#endif
|
||||
#if defined(USE_GC1109_PA) && defined(ARCH_ESP32)
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
#endif
|
||||
|
||||
#include "Throttle.h"
|
||||
|
||||
@@ -59,33 +55,14 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
#if defined(USE_GC1109_PA)
|
||||
// GC1109 FEM chip initialization
|
||||
// See variant.h for full pin mapping and control logic documentation
|
||||
//
|
||||
// On deep sleep wake, PA_POWER and PA_EN are held HIGH by RTC latch (set in
|
||||
// enableLoraInterrupt). We configure GPIO registers before releasing the hold
|
||||
// so the pad transitions atomically from held-HIGH to register-HIGH with no
|
||||
// power glitch. On cold boot the hold_dis is a harmless no-op.
|
||||
|
||||
// VFEM_Ctrl (LORA_PA_POWER): Power enable for GC1109 LDO (always on)
|
||||
pinMode(LORA_PA_POWER, OUTPUT);
|
||||
digitalWrite(LORA_PA_POWER, HIGH);
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
|
||||
|
||||
// TLV75733P LDO has ~550us startup time (datasheet tSTR). On cold boot, wait
|
||||
// for VBAT to stabilise before driving CSD/CPS, per GC1109 requirement:
|
||||
// "VBAT must be prior to CSD/CPS/CTX for the power on sequence"
|
||||
// On deep sleep wake the LDO was held on via RTC latch, so no delay needed.
|
||||
#if defined(ARCH_ESP32)
|
||||
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_UNDEFINED) {
|
||||
delayMicroseconds(1000);
|
||||
}
|
||||
#else
|
||||
delayMicroseconds(1000);
|
||||
#endif
|
||||
|
||||
// CSD (LORA_PA_EN): Chip enable - must be HIGH to enable GC1109 for both RX and TX
|
||||
pinMode(LORA_PA_EN, OUTPUT);
|
||||
digitalWrite(LORA_PA_EN, HIGH);
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_EN);
|
||||
|
||||
// CPS (LORA_PA_TX_EN): PA mode select - HIGH enables full PA during TX, LOW for RX (don't care)
|
||||
// Note: TX/RX path switching (CTX) is handled by DIO2 via SX126X_DIO2_AS_RF_SWITCH
|
||||
@@ -194,17 +171,6 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", result);
|
||||
}
|
||||
|
||||
#ifdef USE_GC1109_PA
|
||||
// Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity
|
||||
// on boards with the GC1109 FEM. Sets bit 0 of register 0x8B5.
|
||||
// Reference: https://github.com/meshcore-dev/MeshCore/pull/1398
|
||||
if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) == RADIOLIB_ERR_NONE) {
|
||||
LOG_INFO("Applied SX1262 register 0x8B5 patch for GC1109 RX improvement");
|
||||
} else {
|
||||
LOG_WARN("Failed to apply SX1262 register 0x8B5 patch for GC1109");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
// Read/write a register we are not using (only used for FSK mode) to test SPI comms
|
||||
uint8_t crcLSB = 0;
|
||||
@@ -362,7 +328,6 @@ template <typename T> void SX126xInterface<T>::startReceive()
|
||||
|
||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
||||
enableInterrupt(isrRxLevel0);
|
||||
checkRxDoneIrqFlag();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -271,7 +271,6 @@ template <typename T> void SX128xInterface<T>::startReceive()
|
||||
|
||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
||||
enableInterrupt(isrRxLevel0);
|
||||
checkRxDoneIrqFlag();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen)
|
||||
/**
|
||||
* Read any rx chars from the link and call handleRecStream
|
||||
*/
|
||||
int32_t StreamAPI::readStream(const char *buf, uint16_t bufLen)
|
||||
int32_t StreamAPI::readStream(char *buf, uint16_t bufLen)
|
||||
{
|
||||
if (bufLen < 1) {
|
||||
// Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time
|
||||
@@ -56,7 +56,7 @@ void StreamAPI::writeStream()
|
||||
}
|
||||
}
|
||||
|
||||
int32_t StreamAPI::handleRecStream(const char *buf, uint16_t bufLen)
|
||||
int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen)
|
||||
{
|
||||
uint16_t index = 0;
|
||||
while (bufLen > index) { // Currently we never want to block
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user